Public/Logging.ps1

using namespace Pode.Utilities.Logging

<#
.SYNOPSIS
Create a new method of outputting logs.
 
.DESCRIPTION
This function has been deprecated and will be removed in future versions. It creates various Log Methods for outputting logs.
Please use the appropriate new functions for each Log Method:
- New-PodeLogTerminalMethod
- New-PodeLogFileMethod
- New-PodeLogEventViewerMethod
- New-PodeLogCustomMethod
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER Terminal
If supplied, will use the inbuilt Terminal logging output method.
 
.PARAMETER File
If supplied, will use the inbuilt File logging output method.
 
.PARAMETER Path
The File Path of where to store the logs.
 
.PARAMETER Name
The File Name to prepend new log files using.
 
.PARAMETER EventViewer
If supplied, will use the inbuilt Event Viewer logging output method.
 
.PARAMETER EventLogName
Optional Log Name for the Event Viewer (Default: Application)
 
.PARAMETER Source
Optional Source for the Event Viewer (Default: Pode)
 
.PARAMETER EventID
Optional EventID for the Event Viewer (Default: 0)
 
.PARAMETER Batch
An optional batch size to write log items in bulk (Default: 1)
 
.PARAMETER BatchTimeout
An optional batch timeout, in seconds, to send items off for writing if a log item isn't received (Default: 0)
 
.PARAMETER MaxDays
The maximum number of days to keep logs, before Pode automatically removes them.
 
.PARAMETER MaxSize
The maximum size of a log file, before Pode starts writing to a new log file.
 
.PARAMETER Custom
If supplied, will allow you to create a Custom Logging output method.
 
.PARAMETER ScriptBlock
The ScriptBlock that defines how to output a log item.
 
.PARAMETER ArgumentList
An array of arguments to supply to the Custom Logging output method's ScriptBlock.
 
.EXAMPLE
$term_logging_id = New-PodeLoggingMethod -Terminal
 
.EXAMPLE
$file_logging_id = New-PodeLoggingMethod -File -Path ./logs -Name 'requests'
 
.EXAMPLE
$custom_logging_id = New-PodeLoggingMethod -Custom -ScriptBlock { /* logic */ }
#>

function New-PodeLoggingMethod {
    [CmdletBinding(DefaultParameterSetName = 'Terminal')]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter(ParameterSetName = 'Terminal')]
        [switch]
        $Terminal,

        [Parameter(ParameterSetName = 'File')]
        [switch]
        $File,

        [Parameter(ParameterSetName = 'File')]
        [string]
        $Path = './logs',

        [Parameter(Mandatory = $true, ParameterSetName = 'File')]
        [string]
        $Name,

        [Parameter(ParameterSetName = 'EventViewer')]
        [switch]
        $EventViewer,

        [Parameter(ParameterSetName = 'EventViewer')]
        [string]
        $EventLogName = 'Application',

        [Parameter(ParameterSetName = 'EventViewer')]
        [string]
        $Source = 'Pode',

        [Parameter(ParameterSetName = 'EventViewer')]
        [int]
        $EventID = 0,

        [Parameter()]
        [int]
        $Batch = 1,

        [Parameter()]
        [int]
        $BatchTimeout = 0,

        [Parameter(ParameterSetName = 'File')]
        [ValidateRange(0, [int]::MaxValue)]
        [int]
        $MaxDays = 0,

        [Parameter(ParameterSetName = 'File')]
        [ValidateRange(0, [int]::MaxValue)]
        [int]
        $MaxSize = 0,

        [Parameter(ParameterSetName = 'Custom')]
        [switch]
        $Custom,

        [Parameter(Mandatory = $true, ParameterSetName = 'Custom')]
        [ValidateScript({
                if (Test-PodeIsEmpty $_) {
                    # A non-empty ScriptBlock is required for the Custom logging output method
                    throw ($PodeLocale.nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage)
                }

                return $true
            })]
        [scriptblock]
        $ScriptBlock,

        [Parameter(ParameterSetName = 'Custom')]
        [object[]]
        $ArgumentList
    )

    # batch details
    $batchInfo = New-PodeLogBatchInfo -Size $Batch -Timeout $BatchTimeout

    # return info on appropriate Log Type
    switch ($PSCmdlet.ParameterSetName.ToLowerInvariant()) {
        'terminal' {
            # WARNING: Function `New-PodeLoggingMethod` is deprecated.
            Write-PodeHost ($PodeLocale.deprecatedFunctionWarningMessage -f 'New-PodeLoggingMethod', 'New-PodeLogTerminalMethod')  -ForegroundColor Yellow

            return New-PodeLogTerminalMethod -Id $Id -BatchInfo $batchInfo
        }

        'file' {
            # WARNING: Function `New-PodeLoggingMethod` is deprecated.
            Write-PodeHost ($PodeLocale.deprecatedFunctionWarningMessage -f 'New-PodeLoggingMethod', 'New-PodeLogFileMethod')  -ForegroundColor Yellow

            return New-PodeLogFileMethod -Id $Id -Name $Name -Path $Path -MaxDays $MaxDays -MaxSize $MaxSize -BatchInfo $batchInfo
        }

        'eventviewer' {
            # WARNING: Function `New-PodeLoggingMethod` is deprecated.
            Write-PodeHost ($PodeLocale.deprecatedFunctionWarningMessage -f 'New-PodeLoggingMethod', 'New-PodeLogEventViewerMethod')  -ForegroundColor Yellow

            return New-PodeLogEventViewerMethod -Id $Id -EventLogName $EventLogName -Source $Source -EventID $EventID -BatchInfo $batchInfo
        }

        'custom' {
            # WARNING: Function `New-PodeLoggingMethod` is deprecated.
            Write-PodeHost ($PodeLocale.deprecatedFunctionWarningMessage -f 'New-PodeLoggingMethod', 'New-PodeLogCustomMethod')  -ForegroundColor Yellow

            return New-PodeLogCustomMethod -Id $Id -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList -BatchInfo $batchInfo
        }
    }
}

<#
.SYNOPSIS
Enables Request Logging using a supplied output method.
 
.DESCRIPTION
Enables Request Logging using a supplied output method.
 
.PARAMETER Name
An optional Name to assign to the Request Log Type. If not supplied, a default name will be used.
 
.PARAMETER Method
One or more Log Method IDs to use for outputting the log entry.
 
.PARAMETER UsernameProperty
An optional property path within the $WebEvent.Auth.User object for the user's Username. (Default: Username).
 
.PARAMETER Format
The format to use for the log output. (Default: Combined)
 
.PARAMETER W3CInfo
An optional hashtable of W3C log configuration information, built using New-PodeLogW3CInfo.
If not supplied, but -Format=W3C, then default W3C fields will be used.
 
.PARAMETER LogFormat
The format to use for the log output. (Default: Server default, or 'None' if no default set).
 
.PARAMETER LogScriptBlock
A ScriptBlock to use for custom log formatting of the log entry.
 
.PARAMETER SerialiseFormat
Specifies the format to use for serialising the log entry. (Default: Server default, or 'Custom' if no default set, or 'None' if Raw is supplied).
 
.PARAMETER SerialiseScriptBlock
A ScriptBlock to use for custom serialisation of the log entry.
 
.PARAMETER ScriptBlock
A ScriptBlock to use for custom data selection and transforming of the log entry. (Default: inbuilt logic).
 
.PARAMETER ArgumentList
An array of arguments to supply to the Custom Log Type's ScriptBlock and SerialiseScriptBlock.
 
.PARAMETER SyslogInfo
A hashtable containing Syslog configuration information, built using New-PodeSyslogInfo.
 
.PARAMETER RemoteIPHeader
One or more optional headers to check for the client's remote IP address, if behind a reverse proxy.
 
.PARAMETER AsUtc
If supplied, the log entry's timestamp will be in UTC, otherwise it will be in local time.
 
.PARAMETER Raw
If supplied, the log item returned will be the raw Request item as a hashtable and not a string (for Custom methods).
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogRequestType
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogRequestType -AsUtc
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogRequestType -RemoteIPHeader 'X-Forwarded-For'
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogRequestType -Name 'custom-req' -SerialiseFormat 'Json'
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogRequestType -SerialiseFormat 'Custom' -SerialiseScriptBlock {
    param($data)
    return "$($data.Host) $($data.Identifier) $($data.User) [$($data.Date)] `"$($data.RequestLine)`" $($data.StatusCode) $($data.Size) `"$($data.Referrer)`" `"$($data.UserAgent)`""
}
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogRequestType -ScriptBlock {
    param($logEvent)
    return @{
        HttpMethod = $logEvent.Data.HttpMethod
    }
}
 
.EXAMPLE
$syslogInfo = New-PodeLogSyslogInfo -Format RFC3164
New-PodeLogTerminalMethod | Enable-PodeLogRequestType -LogFormat Syslog -SyslogInfo $syslogInfo
#>

function Enable-PodeLogRequestType {
    [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')]
    param(
        [Parameter()]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string[]]
        $Method,

        [Parameter()]
        [string]
        $UsernameProperty,

        [Parameter()]
        [ValidateSet('Common', 'Combined', 'W3C')]
        [string]
        $Format = 'Combined',

        [Parameter()]
        [hashtable]
        $W3CInfo,

        [Parameter()]
        [Pode.Utilities.Logging.PodeLogFormat]
        $LogFormat = 'None',

        [Parameter()]
        [scriptblock]
        $LogScriptBlock,

        [Parameter()]
        [Pode.Utilities.Logging.PodeSerialiseFormat]
        $SerialiseFormat = 'Custom',

        [Parameter()]
        [scriptblock]
        $SerialiseScriptBlock,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [scriptblock]
        $ScriptBlock,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [object[]]
        $ArgumentList,

        [Parameter()]
        [hashtable]
        $SyslogInfo,

        [Parameter()]
        [string[]]
        $RemoteIPHeader,

        [switch]
        $AsUtc,

        [Parameter(ParameterSetName = 'Raw')]
        [switch]
        $Raw
    )

    begin {
        # error if it's already enabled
        $name = Protect-PodeValue -Value $Name -Default ([PodeLogger]::REQUEST_LOG_TYPE_NAME)

        # setup array for Log Methods being piped in
        $pipelineMethods = @()
    }

    process {
        $pipelineMethods += $_
    }

    end {
        # multiple methods from pipeline?
        if ($pipelineMethods.Length -gt 1) {
            $Method = $pipelineMethods
        }

        # base params for adding the Request Log Type
        $params = @{
            Name           = $name
            Method         = $Method
            Levels         = @('Informational')
            LogScriptBlock = $LogScriptBlock
            SyslogInfo     = $SyslogInfo
            XmlRootName    = 'Request'
            AsUtc          = $AsUtc.IsPresent
        }

        # username property
        if ([string]::IsNullOrWhiteSpace($UsernameProperty)) {
            $UsernameProperty = 'Username'
        }

        # add base metadata
        $params['Metadata'] = @{
            Username       = $UsernameProperty
            RemoteIPHeader = $RemoteIPHeader
            Format         = $Format.ToLowerInvariant()
        }

        # handle w3c fields
        if ($Format -ieq 'W3C') {
            # use default fields, if none supplied
            if (Test-PodeIsEmpty $W3CInfo) {
                $W3CInfo = Get-PodeRequestLogW3CDefaultInfo
            }

            # build log header directives
            if (!$W3CInfo.NoLogHeader) {
                $params['LogHeader'] = @(
                    "#Software: Pode $($PodeContext.Server.Version)"
                    "#Version: $($W3CInfo.Version)"
                    "#Date: $([datetime]::Now.ToString('yyyy-MM-dd HH:mm:ss'))"
                    "#Fields: $($W3CInfo.Fields.FieldName -join ' ')"
                )
            }

            # add the fields to the metadata
            $params['Metadata']['W3CFields'] = $W3CInfo.Fields
        }

        # add LogFormat if supplied
        if ($PSBoundParameters.ContainsKey('LogFormat')) {
            $params['LogFormat'] = $LogFormat
        }

        # add SerialiseFormat if supplied, otherwise use local default
        if ($PSBoundParameters.ContainsKey('SerialiseFormat')) {
            $params['SerialiseFormat'] = $SerialiseFormat
        }
        else {
            $defSerialiseFormat = Get-PodeLogDefaultSerialiseFormat
            if ($null -eq $defSerialiseFormat) {
                if ($Raw) {
                    $params['SerialiseFormat'] = 'None'
                }
                else {
                    $params['SerialiseFormat'] = $SerialiseFormat
                }
            }
        }

        # add SerialiseScriptBlock if supplied, otherwise use local default
        if ($null -eq $SerialiseScriptBlock) {
            $SerialiseScriptBlock = Get-PodeLoggingInbuiltSerialiseType -Type Requests
        }
        $params['SerialiseScriptBlock'] = $SerialiseScriptBlock

        # do we only want the raw data?
        if ($Raw) {
            $params['Raw'] = $true
        }

        # if not, add the scriptblock for transforming request data
        else {
            if ($null -eq $ScriptBlock) {
                $ScriptBlock = Get-PodeLoggingInbuiltType -Type Requests
            }
            $params['ScriptBlock'] = $ScriptBlock
            $params['ArgumentList'] = $ArgumentList
            $params['Version'] = 2
        }

        # add the type internally
        $typeInfo = Add-PodeLogTypeInternal @params

        # register the type with the logger
        $logType = [PodeLogRequestType]::new($typeInfo.Name, $typeInfo.Options.Formatting.AsUtc)
        $PodeContext.Server.Logging.Logger.RegisterType($logType)
    }
}

if (!(Test-Path Alias:Enable-PodeRequestLogging)) {
    New-Alias Enable-PodeRequestLogging -Value Enable-PodeLogRequestType
}

<#
.SYNOPSIS
Disables Request Log Type.
 
.DESCRIPTION
Disables Request Log Type.
 
.PARAMETER Name
An optional Name to assign to the Request Log Type. If not supplied, a default name will be used.
 
.EXAMPLE
Disable-PodeLogRequestType
 
.EXAMPLE
Disable-PodeLogRequestType -Name 'custom-req'
#>

function Disable-PodeLogRequestType {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Name
    )

    $Name = Protect-PodeValue -Value $Name -Default ([PodeLogger]::REQUEST_LOG_TYPE_NAME)
    Remove-PodeLogType -Name $Name
}

if (!(Test-Path Alias:Disable-PodeRequestLogging)) {
    New-Alias Disable-PodeRequestLogging -Value Disable-PodeLogRequestType
}

<#
.SYNOPSIS
Enables Error Log Type using a supplied Log Method.
 
.DESCRIPTION
Enables Error Log Type using a supplied Log Method.
 
.PARAMETER Name
An optional Name to assign to the Error Log Type. If not supplied, a default name will be used.
 
.PARAMETER Method
One or more Log Method IDs to use for outputting the log entry.
 
.PARAMETER Levels
The Levels of errors that should be logged (Default: Emergency, Alert, Critical, Error)
 
.PARAMETER Kind
The Kinds of errors that should be logged (Default: Server)
 
.PARAMETER LogFormat
The format to use for the log output. (Default: Server default, or 'None' if no default set).
 
.PARAMETER LogScriptBlock
A ScriptBlock to use for custom log formatting of the log entry.
 
.PARAMETER SerialiseFormat
Specifies the format to use for serialising the log entry. (Default: Server default, or 'Custom' if no default set, or 'None' if Raw is supplied).
 
.PARAMETER SerialiseScriptBlock
A ScriptBlock to use for custom serialisation of the log entry.
 
.PARAMETER ScriptBlock
A ScriptBlock to use for custom data selection and transforming of the log entry. (Default: inbuilt logic).
 
.PARAMETER ArgumentList
An array of arguments to supply to the Custom Log Type's ScriptBlock and SerialiseScriptBlock.
 
.PARAMETER SyslogInfo
A hashtable containing Syslog configuration information, built using New-PodeSyslogInfo.
 
.PARAMETER AsUtc
If supplied, the log entry's timestamp will be in UTC, otherwise it will be in local time.
 
.PARAMETER Raw
If supplied, the log item returned will be the raw Error item as a hashtable and not a string (for Custom methods).
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogErrorType
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogErrorType -Kind Server, Client
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogErrorType -AsUtc
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogErrorType -Name 'custom-error' -SerialiseFormat 'Json'
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogErrorType -SerialiseFormat 'Custom' -SerialiseScriptBlock {
    param($data)
    return $data | ConvertTo-PodeString
}
 
.EXAMPLE
New-PodeLogTerminalMethod | Enable-PodeLogErrorType -ScriptBlock {
    param($logEvent)
    return @{
        StackTrace = $logEvent.Data.StackTrace
    }
}
 
.EXAMPLE
$syslogInfo = New-PodeLogSyslogInfo -Format RFC3164
New-PodeLogTerminalMethod | Enable-PodeLogErrorType -LogFormat Syslog -SyslogInfo $syslogInfo
#>

function Enable-PodeLogErrorType {
    [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')]
    param(
        [Parameter()]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string[]]
        $Method,

        [Parameter()]
        [Alias('Level')]
        [ValidateSet('Emergency', 'Alert', 'Critical', 'Error', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug', '*')]
        [string[]]
        $Levels = @('Emergency', 'Alert', 'Critical', 'Error'),

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [Pode.Protocols.Common.Requests.PodeRequestExceptionKind[]]
        $Kind = @([Pode.Protocols.Common.Requests.PodeRequestExceptionKind]::Server),

        [Parameter()]
        [Pode.Utilities.Logging.PodeLogFormat]
        $LogFormat = 'None',

        [Parameter()]
        [scriptblock]
        $LogScriptBlock,

        [Parameter()]
        [Pode.Utilities.Logging.PodeSerialiseFormat]
        $SerialiseFormat = 'Custom',

        [Parameter()]
        [scriptblock]
        $SerialiseScriptBlock,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [scriptblock]
        $ScriptBlock,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [object[]]
        $ArgumentList,

        [Parameter()]
        [hashtable]
        $SyslogInfo,

        [switch]
        $AsUtc,

        [Parameter(ParameterSetName = 'Raw')]
        [switch]
        $Raw
    )

    begin {
        # get error log type name
        $name = Protect-PodeValue -Value $Name -Default ([PodeLogger]::ERROR_LOG_TYPE_NAME)

        # setup array for Log Methods being piped in
        $pipelineMethods = @()
    }

    process {
        $pipelineMethods += $_
    }

    end {
        # multiple methods from pipeline?
        if ($pipelineMethods.Length -gt 1) {
            $Method = $pipelineMethods
        }

        # base params for adding the Error Log Type
        $params = @{
            Name           = $name
            Method         = $Method
            Levels         = $Levels
            LogScriptBlock = $LogScriptBlock
            SyslogInfo     = $SyslogInfo
            XmlRootName    = 'Error'
            AsUtc          = $AsUtc.IsPresent
        }

        # add LogFormat if supplied
        if ($PSBoundParameters.ContainsKey('LogFormat')) {
            $params['LogFormat'] = $LogFormat
        }

        # add SerialiseFormat if supplied, otherwise use local default
        if ($PSBoundParameters.ContainsKey('SerialiseFormat')) {
            $params['SerialiseFormat'] = $SerialiseFormat
        }
        else {
            $defSerialiseFormat = Get-PodeLogDefaultSerialiseFormat
            if ($null -eq $defSerialiseFormat) {
                if ($Raw) {
                    $params['SerialiseFormat'] = 'None'
                }
                else {
                    $params['SerialiseFormat'] = $SerialiseFormat
                }
            }
        }

        # add SerialiseScriptBlock if supplied, otherwise use local default
        if ($null -eq $SerialiseScriptBlock) {
            $SerialiseScriptBlock = Get-PodeLoggingInbuiltSerialiseType -Type Errors
        }
        $params['SerialiseScriptBlock'] = $SerialiseScriptBlock

        # do we only want the raw data?
        if ($Raw) {
            $params['Raw'] = $true
        }

        # if not, add the scriptblock for transforming error data
        else {
            if ($null -eq $ScriptBlock) {
                $ScriptBlock = Get-PodeLoggingInbuiltType -Type Errors
            }
            $params['ScriptBlock'] = $ScriptBlock
            $params['ArgumentList'] = $ArgumentList
            $params['Version'] = 2
        }

        # add types as additional options
        $params['Options'] = @{
            Kinds = $Kind
        }

        # add the type internally
        $typeInfo = Add-PodeLogTypeInternal @params

        # register the type with the logger
        $logType = [PodeLogErrorType]::new($typeInfo.Name, $typeInfo.Levels, $Kind, $typeInfo.Options.Formatting.AsUtc)
        $PodeContext.Server.Logging.Logger.RegisterType($logType)
    }
}

if (!(Test-Path Alias:Enable-PodeErrorLogging)) {
    New-Alias Enable-PodeErrorLogging -Value Enable-PodeLogErrorType
}

<#
.SYNOPSIS
Disables Error Log Type.
 
.DESCRIPTION
Disables Error Log Type.
 
.PARAMETER Name
An optional Name to assign to the Error Log Type. If not supplied, a default name will
 
.EXAMPLE
Disable-PodeLogErrorType
 
.EXAMPLE
Disable-PodeLogErrorType -Name 'custom-error'
#>

function Disable-PodeLogErrorType {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Name
    )

    $Name = Protect-PodeValue -Value $Name -Default ([PodeLogger]::ERROR_LOG_TYPE_NAME)
    Remove-PodeLogType -Name $Name
}

if (!(Test-Path Alias:Disable-PodeErrorLogging)) {
    New-Alias Disable-PodeErrorLogging -Value Disable-PodeLogErrorType
}

<#
.SYNOPSIS
Adds a custom Log Type for parsing custom log items.
 
.DESCRIPTION
Adds a custom Log Type for parsing custom log items.
 
.PARAMETER Name
A unique Name for the Log Type.
 
.PARAMETER Method
The Log Method ID to use for outputting the log entry.
 
.PARAMETER ScriptBlock
The ScriptBlock defining logic that transforms an item, and returns it for outputting.
 
.PARAMETER Levels
The Levels of log items that should be logged. (Default: Informational)
 
.PARAMETER ArgumentList
An array of arguments to supply to the Custom Log Type's ScriptBlock and SerialiseScriptBlock.
 
.PARAMETER Version
The version of the Log Type, this determines the arguments which are supplied to the ScriptBlock. (Default: 1)
Arguments supplied depending on the version:
 
- Version 1: Log Event Data, ArgumentList
- Version 2: Log Event, ArgumentList
 
Under Version 2, the "Log Event" contains references to the log event's data, level, timestamp, and metadata.
 
.PARAMETER LogFormat
The format to use for the log output. (Default: Server default, or 'None' if no default set).
 
.PARAMETER LogScriptBlock
A ScriptBlock to use for custom log formatting of the log entry.
 
.PARAMETER SerialiseFormat
Specifies the format to use for serialising the log entry. (Default: Server default, or 'None' if no default set).
 
.PARAMETER SerialiseScriptBlock
A ScriptBlock defining custom logic for serialising the log data.
 
.PARAMETER SyslogInfo
A hashtable containing Syslog configuration information, built using New-PodeSyslogInfo.
 
.PARAMETER XmlRootName
An optional name to use for the root element of the log item when serialising to XML.
 
.PARAMETER Metadata
A hashtable of metadata to associate with the Log Type. This data can be retrieved via Get-PodeLogType.
 
.PARAMETER LogHeader
An optional array of strings to use as the header(s) for the log, such as W3C log directives.
 
.PARAMETER AsUtc
If supplied, will be set in the Log Type's Formatting Options as to whether to use UTC timestamps or local timestamps.
 
.PARAMETER Raw
If supplied, the log item returned will be the raw Request item as a hashtable and not a string.
 
.EXAMPLE
New-PodeLogTerminalMethod | Add-PodeLogType -Name 'LogTypeName' -ScriptBlock { /* logic */ }
 
.EXAMPLE
New-PodeLogTerminalMethod | Add-PodeLogType -Name 'LogTypeName' -Raw
 
.EXAMPLE
New-PodeLogTerminalMethod | Add-PodeLogType -Name 'LogTypeName' -SerialiseFormat 'Json'
 
.EXAMPLE
New-PodeLogTerminalMethod | Add-PodeLogType -Name 'LogTypeName' -SerialiseFormat 'Custom' -SerialiseScriptBlock {
    param($data)
    return $data | ConvertTo-PodeString
}
#>

function Add-PodeLogType {
    [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')]
    param(
        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string[]]
        $Method,

        [Parameter(Mandatory = $true, ParameterSetName = 'ScriptBlock')]
        [ValidateScript({
                if (Test-PodeIsEmpty $_) {
                    # A non-empty ScriptBlock is required for the Log Method
                    throw ($PodeLocale.nonEmptyScriptBlockRequiredForLoggingMethodExceptionMessage)
                }

                return $true
            })]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [Alias('Level')]
        [ValidateSet('Emergency', 'Alert', 'Critical', 'Error', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug', '*')]
        [string[]]
        $Levels = @('Informational'),

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [object[]]
        $ArgumentList,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [ValidateSet(1, 2)]
        [int]
        $Version = 1,

        [Parameter()]
        [Pode.Utilities.Logging.PodeLogFormat]
        $LogFormat = 'None',

        [Parameter()]
        [scriptblock]
        $LogScriptBlock,

        [Parameter()]
        [Pode.Utilities.Logging.PodeSerialiseFormat]
        $SerialiseFormat = 'None',

        [Parameter()]
        [scriptblock]
        $SerialiseScriptBlock,

        [Parameter()]
        [hashtable]
        $SyslogInfo,

        [Parameter()]
        [string]
        $XmlRootName,

        [Parameter()]
        [hashtable]
        $Metadata,

        [Parameter()]
        [string[]]
        $LogHeader,

        [switch]
        $AsUtc,

        [Parameter(ParameterSetName = 'Raw')]
        [switch]
        $Raw
    )

    begin {
        # setup array for Log Methods being piped in
        $pipelineMethods = @()
    }

    process {
        $pipelineMethods += $_
    }

    end {
        # multiple methods from pipeline?
        if ($pipelineMethods.Length -gt 1) {
            $Method = $pipelineMethods
        }

        # add the type internally
        $typeInfo = Add-PodeLogTypeInternal @PSBoundParameters -Levels $Levels

        # register the type with the logger
        $logType = [PodeLogType]::new($typeInfo.Name, $typeInfo.Levels, $typeInfo.Options.Formatting.AsUtc)
        $PodeContext.Server.Logging.Logger.RegisterType($logType)
    }
}

if (!(Test-Path Alias:Add-PodeLogger)) {
    New-Alias Add-PodeLogger -Value Add-PodeLogType
}

<#
.SYNOPSIS
Removes a configured Log Type.
 
.DESCRIPTION
Removes a configured Log Type.
 
.PARAMETER Name
The Name of the Log Type to remove.
 
.EXAMPLE
Remove-PodeLogType -Name 'LogTypeName'
#>

function Remove-PodeLogType {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Name
    )

    process {
        # get the Log Type
        $type = Get-PodeLogType -Name $Name
        if ($null -eq $type) {
            return
        }

        # unregister Log Type from method
        foreach ($methodId in $type.Method) {
            Unregister-PodeLogTypeFromMethod -TypeName $Name -MethodId $methodId
        }

        # remove the Log Type
        $null = $PodeContext.Server.Logging.Types.Remove($Name)
        $PodeContext.Server.Logging.Logger.UnregisterType($Name)
    }
}

if (!(Test-Path Alias:Remove-PodeLogger)) {
    New-Alias Remove-PodeLogger -Value Remove-PodeLogType
}

<#
.SYNOPSIS
Removes a configured Log Method.
 
.DESCRIPTION
Removes a configured Log Method.
 
.PARAMETER Id
The ID of the Log Method to remove.
 
.EXAMPLE
Remove-PodeLogMethod -Id '<log-method-id>'
#>

function Remove-PodeLogMethod {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [string]
        $Id
    )

    process {
        # get the Log Method
        $method = Get-PodeLogMethod -Id $Id
        if ($null -eq $method) {
            return
        }

        # close the runspace and reduce max count (not below 1)
        $method.Runspace.Pipeline.Stop()
        $method.Runspace.Pipeline.Dispose()

        $maxRunspaces = $PodeContext.RunspacePools.Logs.Pool.GetMaxRunspaces()
        if ($maxRunspaces -gt 1) {
            $PodeContext.RunspacePools.Logs.Pool.SetMaxRunspaces($maxRunspaces - 1)
        }

        # dispose the Log Method queue
        if ($null -ne $method.Queue) {
            $method.Queue.Dispose()
        }

        # unregister Log Method from types that reference it
        foreach ($typeName in $method.Types) {
            Unregister-PodeLogMethodFromType -TypeName $typeName -MethodId $Id
        }

        # remove the Log Method
        $null = $PodeContext.Server.Logging.Methods.Remove($Id)
    }
}

<#
.SYNOPSIS
Clears all Log Types that have been configured.
 
.DESCRIPTION
Clears all Log Types that have been configured.
 
.EXAMPLE
Clear-PodeLogTypes
#>

function Clear-PodeLogTypes {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $typeNames = $PodeContext.Server.Logging.Types.Keys
    foreach ($typeName in $typeNames) {
        Remove-PodeLogType -Name $typeName
    }
}

if (!(Test-Path Alias:Clear-PodeLoggers)) {
    New-Alias Clear-PodeLoggers -Value Clear-PodeLogTypes
}

<#
.SYNOPSIS
Clears all Log Methods that have been configured.
 
.DESCRIPTION
Clears all Log Methods that have been configured.
 
.EXAMPLE
Clear-PodeLogMethods
#>

function Clear-PodeLogMethods {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')]
    [CmdletBinding()]
    param()

    $methodIds = $PodeContext.Server.Logging.Methods.Keys
    foreach ($methodId in $methodIds) {
        Remove-PodeLogMethod -Id $methodId
    }

    # dispose of any Log Method queues
    foreach ($method in $PodeContext.Server.Logging.Methods.Values) {
        if ($null -ne $method.Queue) {
            $method.Queue.Dispose()
        }
    }
}

<#
.SYNOPSIS
Writes and Exception or ErrorRecord using the inbuilt error logging.
 
.DESCRIPTION
Writes and Exception or ErrorRecord using the inbuilt error logging.
 
.PARAMETER Exception
An Exception to write.
 
.PARAMETER ErrorRecord
An ErrorRecord to write.
 
.PARAMETER Message
A Message to write.
 
.PARAMETER Level
The Level of the error being logged. (Default: Error)
 
.PARAMETER Metadata
An optional hashtable of Metadata to include with the log item.
 
.PARAMETER CheckInnerException
If supplied, any exceptions are check for inner exceptions. If one is present, this is also logged.
 
.EXAMPLE
try { /* logic */ } catch { $_ | Write-PodeErrorLog }
 
.EXAMPLE
[System.Exception]::new('error message') | Write-PodeErrorLog
 
.EXAMPLE
Write-PodeErrorLog -Message 'error message' -Level 'Warning'
#>

function Write-PodeErrorLog {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Exception')]
        [System.Exception]
        $Exception,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Error')]
        [System.Management.Automation.ErrorRecord]
        $ErrorRecord,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Message')]
        [string]
        $Message,

        [Parameter()]
        [ValidateSet('Emergency', 'Alert', 'Critical', 'Error', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug')]
        [string]
        $Level = 'Error',

        [Parameter()]
        [hashtable]
        $Metadata,

        [Parameter(ParameterSetName = 'Exception')]
        [switch]
        $CheckInnerException
    )

    process {
        # do nothing if error logging isn't setup
        if (!$PodeContext.Server.Logging.Logger.IsErrorLoggingEnabled) {
            return
        }

        # attempt to get current contextId
        $contextId = Get-PodeLoggingContextId

        # log error object appropriately based on parameter set
        switch ($PSCmdlet.ParameterSetName) {
            'Exception' {
                $PodeContext.Server.Logging.Logger.AddException(
                    $Exception,
                    $contextId,
                    $Level,
                    $Metadata,
                    [int]$ThreadId
                )
            }

            'Error' {
                $PodeContext.Server.Logging.Logger.AddException(
                    $ErrorRecord.CategoryInfo.ToString(),
                    $ErrorRecord.Exception.Message,
                    $ErrorRecord.ScriptStackTrace,
                    $contextId,
                    $Level,
                    [Pode.Protocols.Common.Requests.PodeRequestExceptionKind]::Server,
                    $Metadata,
                    [int]$ThreadId
                )
            }

            'Message' {
                $category = (Get-PSCallStack)[1].Location
                $PodeContext.Server.Logging.Logger.AddException(
                    $category,
                    $Message,
                    [string]::Empty,
                    $contextId,
                    $Level,
                    [Pode.Protocols.Common.Requests.PodeRequestExceptionKind]::Server,
                    $Metadata,
                    [int]$ThreadId
                )
            }
        }

        # for exceptions, check the inner exception
        if ($CheckInnerException -and ($null -ne $Exception.InnerException) -and ![string]::IsNullOrWhiteSpace($Exception.InnerException.Message)) {
            $Exception.InnerException | Write-PodeErrorLog
        }
    }
}

<#
.SYNOPSIS
Write an object to a configured custom Log Method.
 
.DESCRIPTION
Write an object to one or more configured custom Log Methods.
 
.PARAMETER Name
One or more custom Log Type Names.
 
.PARAMETER Level
The Level of the log item being logged. (Default: Informational)
 
.PARAMETER InputObject
The Object to write.
 
.PARAMETER Metadata
An optional hashtable of Metadata to include with the log item.
 
.EXAMPLE
$object | Write-PodeLog -Name 'LogTypeName'
 
.EXAMPLE
$object | Write-PodeLog -Name 'LogTypeName1', 'LogTypeName2'
 
.EXAMPLE
$object | Write-PodeLog -Name 'LogTypeName' -Level 'Debug' -Metadata @{ Key = 'Value' }
#>

function Write-PodeLog {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string[]]
        $Name,

        [Parameter()]
        [ValidateSet('Emergency', 'Alert', 'Critical', 'Error', 'Warning', 'Notice', 'Informational', 'Verbose', 'Debug')]
        [string]
        $Level = 'Informational',

        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [object]
        $InputObject,

        [Parameter()]
        [hashtable]
        $Metadata
    )

    process {
        foreach ($n in $Name) {
            $PodeContext.Server.Logging.Logger.Add($n, $Level, $InputObject, $Metadata)
        }
    }
}

<#
.SYNOPSIS
Masks values within a log item to protect sensitive information.
 
.DESCRIPTION
Masks values within a log item, or any string, to protect sensitive information.
Patterns, and the Mask, can be configured via the server.psd1 configuration file.
 
.PARAMETER Item
The string Item to mask values.
 
.EXAMPLE
$value = Protect-PodeLogItem -Item 'Username=Morty, Password=Hunter2'
#>

function Protect-PodeLogItem {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(ValueFromPipeline = $true)]
        [string]
        $Item
    )

    process {
        # do nothing if there are no masks
        if (($null -eq $PodeContext.Server.Logging.Masking.Patterns) -or ($PodeContext.Server.Logging.Masking.Patterns.Count -eq 0)) {
            return $Item
        }

        # do nothing if the item is null or empty
        if ([string]::IsNullOrWhiteSpace($Item)) {
            return $Item
        }

        # attempt to apply each mask
        foreach ($mask in $PodeContext.Server.Logging.Masking.Patterns) {
            if ($Item -inotmatch $mask) {
                continue
            }

            # has both keep before/after
            if ($Matches.ContainsKey('keep_before') -and $Matches.ContainsKey('keep_after')) {
                $Item = ($Item -ireplace $mask, "`${keep_before}$($PodeContext.Server.Logging.Masking.Mask)`${keep_after}")
            }

            # has just keep before
            elseif ($Matches.ContainsKey('keep_before')) {
                $Item = ($Item -ireplace $mask, "`${keep_before}$($PodeContext.Server.Logging.Masking.Mask)")
            }

            # has just keep after
            elseif ($Matches.ContainsKey('keep_after')) {
                $Item = ($Item -ireplace $mask, "$($PodeContext.Server.Logging.Masking.Mask)`${keep_after}")
            }

            # normal mask
            else {
                $Item = ($Item -ireplace $mask, $PodeContext.Server.Logging.Masking.Mask)
            }
        }

        return $Item
    }
}

<#
.SYNOPSIS
Automatically loads logging ps1 files
 
.DESCRIPTION
Automatically loads logging ps1 files from either a /logging folder, or a custom folder. Saves space dot-sourcing them all one-by-one.
 
.PARAMETER Path
Optional Path to a folder containing ps1 files, can be relative or literal.
 
.EXAMPLE
Use-PodeLogging
 
.EXAMPLE
Use-PodeLogging -Path './my-logging'
#>

function Use-PodeLogging {
    [CmdletBinding()]
    param(
        [Parameter()]
        [string]
        $Path
    )

    Use-PodeFolder -Path $Path -DefaultPath 'logging'
}

<#
.SYNOPSIS
Create a new batch info object for logging.
 
.DESCRIPTION
Creates a new batch info object for logging, which can be used to configure batch processing of log items.
 
.PARAMETER Size
The number of log items to process in a single batch. (Default: 1)
 
.PARAMETER Timeout
The maximum amount of time, in seconds, to wait before processing a batch of log items. (Default: 0, which means no timeout)
 
.EXAMPLE
$batchInfo = New-PodeLogBatchInfo -Size 10 -Timeout 10
#>

function New-PodeLogBatchInfo {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter()]
        [int]
        $Size = 1,

        [Parameter()]
        [int]
        $Timeout = 0
    )

    return @{
        Size    = $Size
        Timeout = $Timeout
    }
}

<#
.SYNOPSIS
Create a new Terminal Log Method.
 
.DESCRIPTION
Creates a new Terminal Log Method for outputting log items to the terminal.
Can be used with Enable-PodeLogRequestType, Enable-PodeLogErrorType, or Add-PodeLogType.
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER BatchInfo
An optional hashtable containing batch configuration for writing log items in bulk.
Should be created using New-PodeLogBatchInfo.
 
.EXAMPLE
$methodId = New-PodeLogTerminalMethod
 
.EXAMPLE
$batchInfo = New-PodeLogBatchInfo -Size 10 -Timeout 10
$methodId = New-PodeLogTerminalMethod -BatchInfo $batchInfo
#>

function New-PodeLogTerminalMethod {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter()]
        [hashtable]
        $BatchInfo = $null
    )

    # add method to server
    return Add-PodeLogMethodInternal -Id $Id -Batch $BatchInfo -Metadata @{
        Type        = 'Terminal'
        ScriptBlock = Get-PodeLoggingTerminalMethod
        Arguments   = @{}
    }
}

<#
.SYNOPSIS
Create a new File Log Method.
 
.DESCRIPTION
Creates a new File Log Method for outputting log items to files.
Can be used with Enable-PodeLogRequestType, Enable-PodeLogErrorType, or Add-PodeLogType.
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER Name
The File Name to prepend new log files using.
 
.PARAMETER Path
The File Path of where to store the logs.
 
.PARAMETER MaxDays
The maximum number of days to keep logs, before Pode automatically removes them.
 
.PARAMETER MaxSize
The maximum size of a log file, before Pode starts writing to a new log file.
 
.PARAMETER BatchInfo
An optional hashtable containing batch configuration for writing log items in bulk.
Should be created using New-PodeLogBatchInfo.
 
.EXAMPLE
$methodId = New-PodeLogFileMethod -Name 'requests'
 
.EXAMPLE
$batchInfo = New-PodeLogBatchInfo -Size 10 -Timeout 10
$methodId = New-PodeLogFileMethod -Name 'requests' -BatchInfo $batchInfo -MaxDays 7 -MaxSize 10MB
#>

function New-PodeLogFileMethod {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]
        $Path = './logs',

        [Parameter()]
        [ValidateRange(0, [int]::MaxValue)]
        [int]
        $MaxDays = 0,

        [Parameter()]
        [ValidateRange(0, [int]::MaxValue)]
        [int]
        $MaxSize = 0,

        [Parameter()]
        [hashtable]
        $BatchInfo = $null
    )

    # resolve path and ensure it exists
    $Path = Get-PodeRelativePath -Path $Path -JoinRoot
    $null = New-Item -Path $Path -ItemType Directory -Force

    # add method to server
    return Add-PodeLogMethodInternal -Id $Id -Batch $BatchInfo -Metadata @{
        Type        = 'File'
        ScriptBlock = Get-PodeLoggingFileMethod
        Arguments   = @{
            Name          = $Name
            Path          = $Path
            MaxDays       = $MaxDays
            MaxSize       = $MaxSize
            FileId        = 0
            Date          = $null
            NextClearDown = [datetime]::Now.Date
        }
    }
}

<#
.SYNOPSIS
Create a new Event Viewer Log Method.
 
.DESCRIPTION
Creates a new Event Viewer Log Method for outputting log items to the Windows Event Viewer.
Can be used with Enable-PodeLogRequestType, Enable-PodeLogErrorType, or Add-PodeLogType.
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER EventLogName
An Optional Log Name for the Event Viewer (Default: Application)
 
.PARAMETER Source
An Optional Source for the Event Viewer (Default: Pode)
 
.PARAMETER EventID
An Optional EventID for the Event Viewer (Default: 0)
 
.PARAMETER BatchInfo
An optional hashtable containing batch configuration for writing log items in bulk.
Should be created using New-PodeLogBatchInfo.
 
.EXAMPLE
$methodId = New-PodeLogEventViewerMethod
 
.EXAMPLE
$batchInfo = New-PodeLogBatchInfo -Size 10 -Timeout 10
$methodId = New-PodeLogEventViewerMethod -EventLogName 'MyLog' -Source 'MyApp' -EventID 1001 -BatchInfo $batchInfo
#>

function New-PodeLogEventViewerMethod {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter()]
        [string]
        $EventLogName = 'Application',

        [Parameter()]
        [string]
        $Source = 'Pode',

        [Parameter()]
        [int]
        $EventID = 0,

        [Parameter()]
        [hashtable]
        $BatchInfo = $null
    )

    # only windows
    if (!(Test-PodeIsWindows)) {
        # Event Viewer logging only supported on Windows
        throw ($PodeLocale.eventViewerLoggingSupportedOnWindowsOnlyExceptionMessage)
    }

    # create source
    if (![System.Diagnostics.EventLog]::SourceExists($Source)) {
        $null = [System.Diagnostics.EventLog]::CreateEventSource($Source, $EventLogName)
    }

    # add method to server
    return Add-PodeLogMethodInternal -Id $Id -Batch $BatchInfo -Metadata @{
        Type        = 'EventViewer'
        ScriptBlock = Get-PodeLoggingEventViewerMethod
        Arguments   = @{
            LogName = $EventLogName
            Source  = $Source
            ID      = $EventID
        }
    }
}

<#
.SYNOPSIS
Create a new Custom Log Method.
 
.DESCRIPTION
Creates a new Custom Log Method for outputting log items using custom logic defined in a ScriptBlock.
Can be used with Enable-PodeLogRequestType, Enable-PodeLogErrorType, or Add-PodeLogType.
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER ScriptBlock
The ScriptBlock that defines how to output a log item.
 
.PARAMETER ArgumentList
An array of arguments to supply to the Custom Logging output method's ScriptBlock.
 
.PARAMETER Version
The version of the Log Method, this determines the arguments which are supplied to the ScriptBlock. (Default: 1)
Arguments supplied depending on the version:
 
- Version 1: Transformed Log Items, ArgumentList, Raw Log Items (legacy)
- Version 2: Log Items, ArgumentList
 
Under Version 2, the "Log Items" contains references to the transformed and raw log items.
 
.PARAMETER BatchInfo
An optional hashtable containing batch configuration for writing log items in bulk.
Should be created using New-PodeLogBatchInfo.
 
.EXAMPLE
$method = New-PodeLogCustomMethod -ScriptBlock { /* logic */ }
 
.EXAMPLE
$arguments = @('arg1', 'arg2')
$methodId = New-PodeLogCustomMethod -ScriptBlock { param($args) /* logic using $args */ } -ArgumentList $arguments
 
.EXAMPLE
$batchInfo = New-PodeLogBatchInfo -Size 10 -Timeout 10
$methodId = New-PodeLogCustomMethod -ScriptBlock { /* logic */ } -BatchInfo $batchInfo
#>

function New-PodeLogCustomMethod {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter(Mandatory = $true)]
        [ValidateScript({
                if (Test-PodeIsEmpty $_) {
                    # A non-empty ScriptBlock is required for the Custom logging output method
                    throw ($PodeLocale.nonEmptyScriptBlockRequiredForCustomLoggingExceptionMessage)
                }

                return $true
            })]
        [scriptblock]
        $ScriptBlock,

        [Parameter()]
        [object[]]
        $ArgumentList,

        [Parameter(ParameterSetName = 'ScriptBlock')]
        [ValidateSet(1, 2)]
        [int]
        $Version = 1,

        [Parameter()]
        [hashtable]
        $BatchInfo = $null
    )

    # check for scoped vars
    $ScriptBlock, $usingVars = Convert-PodeScopedVariables -ScriptBlock $ScriptBlock -PSSession $PSCmdlet.SessionState

    # add method to server
    return Add-PodeLogMethodInternal -Id $Id -Batch $BatchInfo -Metadata @{
        Type        = 'Custom'
        ScriptBlock = Get-PodeLoggingCustomMethod
        Custom      = @{
            ScriptBlock    = $ScriptBlock
            UsingVariables = $usingVars
        }
        Arguments   = $ArgumentList
        Version     = $Version
    }
}

<#
.SYNOPSIS
Creates a new API Log Method.
 
.DESCRIPTION
Creates a new API Log Method for outputting log items to custom API endpoints.
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER Type
An optional Type to assign to the Log Method. (Default: API)
 
.PARAMETER Url
The URL of the API endpoint to send log items to.
 
.PARAMETER ContentType
An optional Content-Type header to include in the API request. (Default: application/json)
 
.PARAMETER Method
An optional HTTP Method to use for the API request. (Default: POST)
 
.PARAMETER Headers
An optional hashtable of headers to include in the API request, typically used for the Authorization header.
 
.PARAMETER HeadersScriptBlock
An optional ScriptBlock that returns a hashtable of headers to include in the API request.
Useful for dynamically generating headers based on the request body or other elements.
 
.PARAMETER HeadersArguments
An optional hashtable of arguments to pass to the HeadersScriptBlock.
 
.PARAMETER BodyScriptBlock
A ScriptBlock that returns the body of the API request as a valid string, based on a collection of log items.
Protect-PodeLogItem will be automatically applied to the returned string.
 
.PARAMETER BodyArguments
An optional hashtable of arguments to pass to the BodyScriptBlock.
 
.PARAMETER BatchInfo
An optional hashtable containing batch configuration for writing log items in bulk.
Should be created using New-PodeLogBatchInfo.
 
.PARAMETER SkipCertificateCheck
If supplied, the API request will skip certificate validation checks.
 
.PARAMETER Compress
If supplied, the API request will include a Content-Encoding: gzip header and compress the request body.
 
.EXAMPLE
$headers = @{ 'Authorization' = 'Bearer <token>' }
$methodId = New-PodeLogApiMethod -Url 'https://api.example.com/logs' -Headers $headers -Compress -BodyScriptBlock {
    param($logItems)
    return $logItems.Data | ConvertTo-Json -Compress
}
#>

function New-PodeLogApiMethod {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter()]
        [string]
        $Type,

        [Parameter(Mandatory = $true)]
        [string]
        $Url,

        [Parameter()]
        [ValidatePattern('^\w+\/[\w\.\+-]+$')]
        [string]
        $ContentType = 'application/json',

        [Parameter()]
        [ValidateSet('Get', 'Post', 'Put', 'Patch')]
        [string]
        $Method = 'Post',

        [Parameter()]
        [hashtable]
        $Headers = @{},

        [Parameter()]
        [scriptblock]
        $HeadersScriptBlock,

        [Parameter()]
        [hashtable]
        $HeadersArguments,

        [Parameter(Mandatory = $true)]
        [scriptblock]
        $BodyScriptBlock,

        [Parameter()]
        [hashtable]
        $BodyArguments,

        [Parameter()]
        [hashtable]
        $BatchInfo = $null,

        [switch]
        $SkipCertificateCheck,

        [switch]
        $Compress
    )

    # default type
    if ([string]::IsNullOrWhiteSpace($Type)) {
        $Type = 'API'
    }

    # default headers
    if ($null -eq $Headers) {
        $Headers = @{}
    }

    if ($Compress -and !$Headers.ContainsKey('Content-Encoding')) {
        $Headers['Content-Encoding'] = 'gzip'
    }

    # headers scriptblock + using vars
    if ($null -ne $HeadersScriptBlock) {
        $HeadersScriptBlock, $headerUsingVars = Convert-PodeScopedVariables -ScriptBlock $HeadersScriptBlock -PSSession $PSCmdlet.SessionState
    }

    # body scriptblock + using vars
    if ($null -ne $BodyScriptBlock) {
        $BodyScriptBlock, $bodyUsingVars = Convert-PodeScopedVariables -ScriptBlock $BodyScriptBlock -PSSession $PSCmdlet.SessionState
    }

    # add method to server
    return Add-PodeLogMethodInternal -Id $Id -Batch $BatchInfo -Metadata @{
        Type        = $Type
        ScriptBlock = Get-PodeLoggingApiMethod
        Arguments   = @{
            Url                  = $Url
            ContentType          = $ContentType
            Method               = $Method
            Compress             = $Compress.IsPresent
            Headers              = @{
                Value          = $Headers
                ScriptBlock    = $HeadersScriptBlock
                UsingVariables = $headerUsingVars
                Arguments      = $HeadersArguments
            }
            Body                 = @{
                ScriptBlock    = $BodyScriptBlock
                UsingVariables = $bodyUsingVars
                Arguments      = $BodyArguments
            }
            SkipCertificateCheck = $SkipCertificateCheck.IsPresent
        }
    }
}

<#
.SYNOPSIS
Creates a new Splunk Log Method.
 
.DESCRIPTION
Creates a new Splunk Log Method for outputting log items to a Splunk HTTP Event Collector (HEC) endpoint.
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER BaseUrl
The base URL of the Splunk HEC endpoint, e.g., 'https://splunk.example.com:8088'.
 
.PARAMETER Token
The authentication token for the Splunk HEC endpoint.
 
.PARAMETER SourceType
An optional source type to include with the log items.
 
.PARAMETER Source
An optional source to include with the log items. (Default: the server's AppName)
 
.PARAMETER Index
An optional index to include with the log items.
 
.PARAMETER BatchInfo
An optional hashtable containing batch configuration for writing log items in bulk.
Should be created using New-PodeLogBatchInfo.
 
.PARAMETER SkipCertificateCheck
If supplied, the API request will skip certificate validation checks.
 
.EXAMPLE
$methodId = New-PodeLogSplunkMethod -BaseUrl 'https://splunk.example.com:8088' -Token '<token>' -Source 'my_source'
 
.EXAMPLE
$batchInfo = New-PodeLogBatchInfo -Size 10 -Timeout 10
$methodId = New-PodeLogSplunkMethod -BaseUrl 'https://splunk.example.com:8088' -Token '<token>' -SourceType 'syslog' -BatchInfo $batchInfo
 
.EXAMPLE
$methodId = New-PodeLogSplunkMethod -BaseUrl 'https://localhost:8088' -Token '<token>' -SkipCertificateCheck
#>

function New-PodeLogSplunkMethod {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter(Mandatory = $true)]
        [string]
        $BaseUrl,

        [Parameter(Mandatory = $true)]
        [string]
        $Token,

        [Parameter()]
        [string]
        $SourceType,

        [Parameter()]
        [string]
        $Source,

        [Parameter()]
        [string]
        $Index,

        [Parameter()]
        [hashtable]
        $BatchInfo = $null,

        [switch]
        $SkipCertificateCheck
    )

    # default source
    $Source = Protect-PodeValue -Value $Source -Default $PodeContext.Server.AppName

    # build body scriptblock
    $bodyScriptBlock = {
        param($logItems, $options)

        # build array of events
        $events = @(foreach ($item in $logItems) {
                # build base event object
                $evt = @{
                    event  = $item.Data
                    host   = $PodeContext.Server.ComputerName
                    time   = ConvertTo-PodeUnixEpoch -DateTime $item.Event.Timestamp
                    fields = @{
                        severity = ConvertTo-PodeSplunkLevel -Level $item.Event.Level
                    }
                }

                # add source type
                $sourceType = Protect-PodeValue -Value $item.Event.Metadata['SourceType'] -Default $options.SourceType
                if (![string]::IsNullOrEmpty($sourceType)) {
                    $evt.sourcetype = $sourceType
                }

                # add source
                $source = Protect-PodeValue -Value $item.Event.Metadata['Source'] -Default $options.Source
                if (![string]::IsNullOrEmpty($source)) {
                    $evt.source = $source
                }

                # add index
                $index = Protect-PodeValue -Value $item.Event.Metadata['Index'] -Default $options.Index
                if (![string]::IsNullOrEmpty($index)) {
                    $evt.index = $index
                }

                $evt
            })

        # convert to json and return
        return $events | ConvertTo-Json -Compress -Depth 10
    }

    # default headers
    $headers = @{
        Authorization = "Splunk $($Token)"
    }

    # add method to server
    $bodyArgs = @{
        SourceType = $SourceType
        Source     = $Source
        Index      = $Index
    }

    return New-PodeLogApiMethod `
        -Id $Id `
        -Type 'Splunk' `
        -BatchInfo $BatchInfo `
        -Url "$($BaseUrl.TrimEnd('/'))/services/collector" `
        -Headers $headers `
        -BodyScriptBlock $bodyScriptBlock `
        -BodyArguments $bodyArgs `
        -SkipCertificateCheck:$SkipCertificateCheck.IsPresent `
        -Compress
}

<#
.SYNOPSIS
Creates a new Datadog Log Method.
 
.DESCRIPTION
Creates a new Datadog Log Method for outputting log items to a Datadog's HTTP v2 logging API endpoint.
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER BaseUrl
The base URL of the Datadog API endpoint, e.g., 'https://http-intake.logs.datadoghq.com'.
 
.PARAMETER ApiKey
The API key used for authenticating with the Datadog API.
 
.PARAMETER Tags
An optional hashtable of tags to include with the log items.
 
.PARAMETER Service
An optional service name to include with the log items. (Default: the server's AppName)
 
.PARAMETER Source
An optional source name to include with the log items.
 
.PARAMETER BatchInfo
An optional hashtable containing batch configuration for writing log items in bulk.
Should be created using New-PodeLogBatchInfo.
 
.PARAMETER SkipCertificateCheck
If supplied, the API request will skip certificate validation checks.
 
.EXAMPLE
$methodId = New-PodeLogDatadogMethod -BaseUrl 'https://http-intake.logs.datadoghq.com' -ApiKey '<api_key>' -Source 'my_source'
 
.EXAMPLE
$batchInfo = New-PodeLogBatchInfo -Size 10 -Timeout 10
$methodId = New-PodeLogDatadogMethod -BaseUrl 'https://http-intake.logs.datadoghq.eu' -ApiKey '<api_key>' -Tags @{ env = 'prod' } -BatchInfo $batchInfo
 
.EXAMPLE
$methodId = New-PodeLogDatadogMethod -BaseUrl 'https://http-intake.logs.datadoghq.com' -ApiKey '<api_key>' -Service 'my_service' -Source 'my_source' -SkipCertificateCheck
#>

function New-PodeLogDatadogMethod {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter(Mandatory = $true)]
        [string]
        $BaseUrl,

        [Parameter(Mandatory = $true)]
        [string]
        $ApiKey,

        [Parameter()]
        [hashtable]
        $Tags,

        [Parameter()]
        [string]
        $Service,

        [Parameter()]
        [string]
        $Source,

        [Parameter()]
        [hashtable]
        $BatchInfo = $null,

        [switch]
        $SkipCertificateCheck
    )

    # default service
    $Service = Protect-PodeValue -Value $Service -Default $PodeContext.Server.AppName

    # build body scriptblock
    $bodyScriptBlock = {
        param($logItems, $options)

        # build array of events
        $events = @(foreach ($item in $logItems) {
                # build base event object
                $evt = @{
                    message   = $item.Data
                    hostname  = $PodeContext.Server.ComputerName
                    status    = ConvertTo-PodeDatadogLevel -Level $item.Event.Level
                    timestamp = $item.Event.Timestamp.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')
                }

                # add service
                $service = Protect-PodeValue -Value $item.Event.Metadata['Service'] -Default $options.Service
                if (![string]::IsNullOrEmpty($service)) {
                    $evt.service = $service
                }

                # add source
                $source = Protect-PodeValue -Value $item.Event.Metadata['Source'] -Default $options.Source
                if (![string]::IsNullOrEmpty($source)) {
                    $evt.ddsource = $source
                }

                # add tags
                if ($options.Tags.Count -gt 0) {
                    $evt.ddtags = @(foreach ($key in $options.Tags.Keys) { "$($key):$($options.Tags[$key])" }) -join ','
                }

                $evt
            })

        # convert to json and return
        return $events | ConvertTo-Json -Compress -Depth 10
    }

    # default headers
    $headers = @{
        'DD-API-KEY' = $ApiKey
    }

    # default tags
    if ($null -eq $Tags) {
        $Tags = @{}
    }

    # add method to server
    $bodyArgs = @{
        Service = $Service
        Source  = $Source
        Tags    = $Tags
    }

    return New-PodeLogApiMethod `
        -Id $Id `
        -Type 'Datadog' `
        -BatchInfo $BatchInfo `
        -Url "$($BaseUrl.TrimEnd('/'))/api/v2/logs" `
        -Headers $headers `
        -BodyScriptBlock $bodyScriptBlock `
        -BodyArguments $bodyArgs `
        -SkipCertificateCheck:$SkipCertificateCheck.IsPresent `
        -Compress
}

<#
.SYNOPSIS
Creates a new Azure Log Analytics Log Method.
 
.DESCRIPTION
Creates a new Azure Log Analytics Log Method for outputting log items to an Azure Log Analytics workspace.
The Azure Log Method created will either use the legacy Workspace method, or the new Data Collection logic.
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER WorkspaceId
The Workspace ID of the Azure Log Analytics workspace.
 
.PARAMETER SharedKey
The Shared Key of the Azure Log Analytics workspace.
 
.PARAMETER LogType
The Log Type to use for the log items in Azure Log Analytics, used for the Log-Type header.
 
.PARAMETER Endpoint
The Data Collection Endpoint used for ingestion into Azure Monitor/Azure Log Analytics.
 
.PARAMETER ImmutableId
The Data Collection Rule Immutable ID.
 
.PARAMETER StreamName
The Stream Name in the Data Collection Rule that should handle the logs.
 
.PARAMETER ClientId
The Client ID from registering a new app.
 
.PARAMETER ClientSecret
The Client Secret from registering a new app.
 
.PARAMETER TenantId
The Directory/Tenant ID from registering a new app.
 
.PARAMETER Source
An optional source to include with the log items. (Default: the server's AppName)
 
.PARAMETER BatchInfo
An optional hashtable containing batch configuration for writing log items in bulk.
Should be created using New-PodeLogBatchInfo.
 
.PARAMETER SkipCertificateCheck
If supplied, the API request will skip certificate validation checks.
 
.EXAMPLE
$methodId = New-PodeLogAzureMethod -WorkspaceId '<workspace_id>' -SharedKey '<shared_key>' -LogType 'MyCustomLog'
 
.EXAMPLE
$batchInfo = New-PodeLogBatchInfo -Size 10 -Timeout 10
$methodId = New-PodeLogAzureMethod -WorkspaceId '<workspace_id>' -SharedKey '<shared_key>' -LogType 'MyCustomLog' -BatchInfo $batchInfo -Source 'my_source'
#>

function New-PodeLogAzureMethod {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter(Mandatory = $true, ParameterSetName = 'Workspace')]
        [string]
        $WorkspaceId,

        [Parameter(Mandatory = $true, ParameterSetName = 'Workspace')]
        [string]
        $SharedKey,

        [Parameter(Mandatory = $true, ParameterSetName = 'Workspace')]
        [string]
        $LogType,

        [Parameter(Mandatory = $true, ParameterSetName = 'DataCollection')]
        [string]
        $Endpoint,

        [Parameter(Mandatory = $true, ParameterSetName = 'DataCollection')]
        [string]
        $ImmutableId,

        [Parameter(Mandatory = $true, ParameterSetName = 'DataCollection')]
        [string]
        $StreamName,

        [Parameter(Mandatory = $true, ParameterSetName = 'DataCollection')]
        [string]
        $ClientId,

        [Parameter(Mandatory = $true, ParameterSetName = 'DataCollection')]
        [string]
        $ClientSecret,

        [Parameter(Mandatory = $true, ParameterSetName = 'DataCollection')]
        [string]
        $TenantId,

        [Parameter()]
        [string]
        $Source,

        [Parameter()]
        [hashtable]
        $BatchInfo = $null,

        [switch]
        $SkipCertificateCheck
    )

    # default source
    $Source = Protect-PodeValue -Value $Source -Default $PodeContext.Server.AppName

    # return appropriate azure logging
    switch ($PSCmdlet.ParameterSetName) {
        'Workspace' {
            return New-PodeLogAzureWorkspaceMethod `
                -Id $Id `
                -WorkspaceId $WorkspaceId `
                -SharedKey $SharedKey `
                -LogType $LogType `
                -Source $Source `
                -BatchInfo $BatchInfo `
                -SkipCertificateCheck:$SkipCertificateCheck
        }

        'DataCollection' {
            return New-PodeLogAzureDataCollectionMethod `
                -Id $Id `
                -Endpoint $Endpoint `
                -ImmutableId $ImmutableId `
                -StreamName $StreamName `
                -ClientId $ClientId `
                -ClientSecret $ClientSecret `
                -TenantId $TenantId `
                -Source $Source `
                -BatchInfo $BatchInfo `
                -SkipCertificateCheck:$SkipCertificateCheck
        }
    }
}

<#
.SYNOPSIS
Creates a new AWS CloudWatch Log Method.
 
.DESCRIPTION
Creates a new AWS CloudWatch Log Method for outputting log items to an AWS CloudWatch Logs group and stream.
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER LogGroupName
The name of the AWS CloudWatch Logs group to which log events will be sent.
 
.PARAMETER LogStreamName
The name of the AWS CloudWatch Logs stream within the specified log group.
 
.PARAMETER Token
The authentication bearer token for the AWS CloudWatch Logs API.
 
.PARAMETER Region
The AWS region where the CloudWatch Logs group and stream are located.
 
.PARAMETER SourceType
The source type for the log events.
 
.PARAMETER Source
The source for the log events. (Default: the server's AppName)
 
.PARAMETER Index
The index for the log events.
 
.PARAMETER BatchInfo
An optional hashtable containing batch configuration for writing log items in bulk.
Should be created using New-PodeLogBatchInfo.
 
.PARAMETER SkipCertificateCheck
If supplied, the API request will skip certificate validation checks.
 
.EXAMPLE
$methodId = New-PodeLogAwsMethod -LogGroupName 'my-log-group' -LogStreamName 'my-log-stream' -Region 'us-east-1' -Token '<token>'
 
.EXAMPLE
$batchInfo = New-PodeLogBatchInfo -Size 10 -Timeout 10
$methodId = New-PodeLogAwsMethod -LogGroupName 'my-log-group' -LogStreamName 'my-log-stream' -Region 'us-east-1' -Token '<token>' -BatchInfo $batchInfo
#>

function New-PodeLogAwsMethod {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter(Mandatory = $true)]
        [string]
        $LogGroupName,

        [Parameter(Mandatory = $true)]
        [string]
        $LogStreamName,

        [Parameter(Mandatory = $true)]
        [string]
        $Region,

        [Parameter(Mandatory = $true)]
        [string]
        $Token,

        [Parameter()]
        [string]
        $SourceType,

        [Parameter()]
        [string]
        $Source,

        [Parameter()]
        [string]
        $Index,

        [Parameter()]
        [hashtable]
        $BatchInfo = $null,

        [switch]
        $SkipCertificateCheck
    )

    # default source
    $Source = Protect-PodeValue -Value $Source -Default $PodeContext.Server.AppName

    # build body scriptblock
    $bodyScriptBlock = {
        param($logItems, $options)

        # build array of events
        $events = @(foreach ($item in $logItems) {
                # build base event object
                $evt = @{
                    event    = $item.Data
                    host     = $PodeContext.Server.ComputerName
                    time     = ConvertTo-PodeUnixEpoch -DateTime $item.Event.Timestamp
                    severity = ConvertTo-PodeSplunkLevel -Level $item.Event.Level
                }

                # add source type
                $sourceType = Protect-PodeValue -Value $item.Event.Metadata['SourceType'] -Default $options.SourceType
                if (![string]::IsNullOrEmpty($sourceType)) {
                    $evt.sourcetype = $sourceType
                }

                # add source
                $source = Protect-PodeValue -Value $item.Event.Metadata['Source'] -Default $options.Source
                if (![string]::IsNullOrEmpty($source)) {
                    $evt.source = $source
                }

                # add index
                $index = Protect-PodeValue -Value $item.Event.Metadata['Index'] -Default $options.Index
                if (![string]::IsNullOrEmpty($index)) {
                    $evt.index = $index
                }

                $evt
            })

        # convert to json and return
        return $events | ConvertTo-Json -Compress -Depth 10
    }

    # default headers
    $headers = @{
        Authorization = "Bearer $($Token)"
    }

    # add method to server
    $bodyArgs = @{
        SourceType = $SourceType
        Source     = $Source
        Index      = $Index
    }

    return New-PodeLogApiMethod `
        -Id $Id `
        -Type 'AWS' `
        -BatchInfo $BatchInfo `
        -Url "https://logs.$($Region).amazonaws.com/services/collector/event?logGroup=$($LogGroupName)&logStream=$($LogStreamName)" `
        -Headers $headers `
        -BodyScriptBlock $bodyScriptBlock `
        -BodyArguments $bodyArgs `
        -SkipCertificateCheck:$SkipCertificateCheck.IsPresent `
        -Compress
}

<#
.SYNOPSIS
Creates a new network Log Method.
 
.DESCRIPTION
Creates a new network Log Method for outputting log items to a remote server over UDP, TCP, or TLS.
 
.PARAMETER Id
An optional ID to assign to the Log Method. If not supplied, a random ID will be generated.
 
.PARAMETER Server
The address (IP or Hostname) of the remote server to send log items to.
 
.PARAMETER Transport
The transport protocol to use for sending log items. (Default: 'Udp')
 
.PARAMETER Port
The port number to use. (Default: 514)
 
.PARAMETER BatchInfo
An optional hashtable containing batch configuration for writing log items in bulk.
Should be created using New-PodeLogBatchInfo.
 
.PARAMETER SkipCertificateCheck
A switch parameter to skip certificate validation when using TLS transport.
 
.EXAMPLE
$methodId = New-PodeLogNetworkMethod -Server '192.168.1.100' -Transport 'Tcp' -Port 514
 
.EXAMPLE
$methodId = New-PodeLogNetworkMethod -Server 'logs.example.com' -Transport 'Tls' -Port 6514 -SkipCertificateCheck
 
.EXAMPLE
$batchInfo = New-PodeLogBatchInfo -Size 10 -Timeout 10
$methodId = New-PodeLogNetworkMethod -Server 'logs.example.com' -Transport 'Udp' -BatchInfo $batchInfo
#>

function New-PodeLogNetworkMethod {
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter()]
        [string]
        $Id,

        [Parameter(Mandatory = $true)]
        [string]
        $Server,

        [Parameter()]
        [ValidateSet('Udp', 'Tcp', 'Tls')]
        [string]
        $Transport = 'Udp',

        [Parameter()]
        [ValidateRange(1, 65535)]
        [int]
        $Port = 514,

        [Parameter()]
        [hashtable]
        $BatchInfo = $null,

        [switch]
        $SkipCertificateCheck
    )

    # add method to server
    return Add-PodeLogMethodInternal -Id $Id -Batch $BatchInfo -Metadata @{
        Type        = 'Network'
        ScriptBlock = Get-PodeLoggingNetworkMethod
        Arguments   = @{
            Server               = $Server
            Transport            = $Transport
            Port                 = $Port
            SkipCertificateCheck = $SkipCertificateCheck.IsPresent
        }
    }
}

<#
.SYNOPSIS
Creates a new syslog information object.
 
.DESCRIPTION
This function creates a new syslog information object with the specified parameters.
 
.PARAMETER Facility
The syslog facility code. (Default: 16, which corresponds to local0 for web/app logs)
 
.PARAMETER AppName
The name of the application generating the log message. (Default: the server's AppName)
 
.PARAMETER Tags
An optional hashtable of tags to include in the syslog message.
 
.PARAMETER Format
The syslog format to use. (Default: Server default, or 'RFC5424' if no default set)
 
.EXAMPLE
$info = New-PodeLogSyslogInfo -Format 'RFC3164'
 
.EXAMPLE
$info = New-PodeLogSyslogInfo -Facility 18 -AppName 'MyApp' -Tags @{ Environment = 'Production'; Version = '1.0' } -Format 'RFC5424'
 
.EXAMPLE
$info = New-PodeLogSyslogInfo -AppName 'MyApp'
#>

function New-PodeLogSyslogInfo {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter()]
        [ValidateRange(0, 23)]
        [int]
        $Facility = 16, # local0 for web/app logs

        [Parameter()]
        [string]
        $AppName,

        [Parameter()]
        [hashtable]
        $Tags,

        [Parameter()]
        [Pode.Utilities.Logging.PodeSyslogFormat]
        $Format = 'RFC5424'
    )

    # check default format if not supplied
    if (!$PSBoundParameters.ContainsKey('Format')) {
        $Format = Protect-PodeValue -Value (Get-PodeLogDefaultSyslogFormat) -Default $Format -EnumType ([PodeSyslogFormat])
    }

    # return syslog info
    return @{
        Facility = $Facility
        AppName  = $AppName
        Tags     = $Tags
        Format   = $Format
    }
}

<#
.SYNOPSIS
Converts a log message to a syslog formatted string.
 
.DESCRIPTION
Converts a log message to a syslog formatted string based on the specified parameters.
 
.PARAMETER Message
The log message to be converted.
 
.PARAMETER Level
The log level of the message.
 
.PARAMETER Timestamp
The timestamp of the log message.
 
.PARAMETER Facility
The syslog facility code. (Default: 16, which corresponds to local0 for web/app logs)
 
.PARAMETER AppName
The name of the application generating the log message. (Default: the server's AppName)
 
.PARAMETER Tags
A hashtable of tags to include in the syslog message.
 
.PARAMETER Format
The syslog format to use. (Default: Server default, or 'RFC5424' if no default set)
 
.EXAMPLE
$msg = ConvertTo-PodeSyslog -Message "This is a test log message" -Level 'Info' -Timestamp (Get-Date) -Tags @{ Environment = "Production"; Version = "1.0" }
 
.EXAMPLE
$msg = ConvertTo-PodeSyslog -Message "This is a test log message" -Level 'Error' -Timestamp (Get-Date) -Facility 16 -AppName "MyApp" -Tags @{ Environment = "Staging"; Version = "2.0" } -Format 'RFC3164'
#>

function ConvertTo-PodeSyslog {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object]
        $Message,

        [Parameter(Mandatory = $true)]
        [Pode.Utilities.Logging.PodeLogLevel]
        $Level,

        [Parameter(Mandatory = $true)]
        [datetime]
        $Timestamp,

        [Parameter()]
        [ValidateRange(0, 23)]
        [int]
        $Facility = 16, # local0 for web/app logs

        [Parameter()]
        [string]
        $AppName,

        [Parameter()]
        [hashtable]
        $Tags,

        [Parameter()]
        [Pode.Utilities.Logging.PodeSyslogFormat]
        $Format = 'RFC5424'
    )

    process {
        # set default format
        if (!$PSBoundParameters.ContainsKey('Format')) {
            $Format = Protect-PodeValue -Value (Get-PodeLogDefaultSyslogFormat) -Default $Format -EnumType ([PodeSyslogFormat])
        }

        # set default app-name
        $AppName = Protect-PodeValue -Value $AppName -Default $PodeContext.Server.AppName
        if ([string]::IsNullOrWhiteSpace($AppName)) {
            $AppName = '-'
        }

        # generate priority value
        $priority = ($Facility * 8) + (ConvertTo-PodeSyslogLevel -Level $Level)

        # get process ID
        $processId = [System.Diagnostics.Process]::GetCurrentProcess().Id

        # ensure message is a string, and escape newlines and carriage returns in message
        if ($Message -isnot [string]) {
            $Message = $Message | ConvertTo-PodeString
        }

        $Message = $Message.Trim().Replace("`n", '\n').Replace("`r", '\r')

        # build message based on format
        switch ($Format) {
            'RFC3164' {
                $strTimestamp = $Timestamp.ToString('MMM dd HH:mm:ss')
                $result = "<$($priority)>$($strTimestamp) $($PodeContext.Server.ComputerName) $($AppName)[$($processId)]: $($Message)"
            }

            'RFC5424' {
                $strTimestamp = $Timestamp.ToString('yyyy-MM-ddTHH:mm:ss.fffK')

                $strTags = '-'
                if ($Tags.Count -gt 0) {
                    $strTags = @(
                        foreach ($key in $Tags.Keys) {
                            $value = $Tags[$key].Replace('\', '\\').Replace('"', '\"').Replace("`n", '\n').Replace("`r", '\r').Replace(']', '\]')
                            "$($key)=`"$($value)`""
                        }
                    ) -join ' '

                    $strTags = "[$($strTags)]"
                }

                $result = "<$($priority)>1 $($strTimestamp) $($PodeContext.Server.ComputerName) $($AppName) $($processId) - $strTags $($Message)"
            }
        }

        return $result
    }
}

<#
.SYNOPSIS
Retrieve the default log format for logging.
 
.DESCRIPTION
Retrieves the default log format used by the logging system.
 
.EXAMPLE
$format = Get-PodeLogDefaultFormat
#>

function Get-PodeLogDefaultFormat {
    [CmdletBinding()]
    [OutputType([Pode.Utilities.Logging.PodeLogFormat])]
    param()

    return $PodeContext.Server.Logging.Formatting.Log
}

<#
.SYNOPSIS
Sets the default log format for logging.
 
.DESCRIPTION
Sets the default log format used by the logging system.
 
.PARAMETER Format
The log format to set as the default.
 
.EXAMPLE
Set-PodeLogDefaultFormat -Format 'Syslog'
#>

function Set-PodeLogDefaultFormat {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [Pode.Utilities.Logging.PodeLogFormat]
        $Format
    )

    $PodeContext.Server.Logging.Formatting.Log = $Format
}

<#
.SYNOPSIS
Retrieve the default syslog format for logging.
 
.DESCRIPTION
Retrieves the default syslog format used by the logging system.
 
.EXAMPLE
$format = Get-PodeLogDefaultSyslogFormat
#>

function Get-PodeLogDefaultSyslogFormat {
    [CmdletBinding()]
    [OutputType([Pode.Utilities.Logging.PodeSyslogFormat])]
    param()

    return $PodeContext.Server.Logging.Formatting.Syslog
}

<#
.SYNOPSIS
Set the default syslog format for logging.
 
.DESCRIPTION
Sets the default syslog format used by the logging system.
 
.PARAMETER Format
The syslog format to set as the default.
 
.EXAMPLE
Set-PodeLogDefaultSyslogFormat -Format 'RFC5424'
#>

function Set-PodeLogDefaultSyslogFormat {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [Pode.Utilities.Logging.PodeSyslogFormat]
        $Format
    )

    $PodeContext.Server.Logging.Formatting.Syslog = $Format
}

<#
.SYNOPSIS
Retrieve the default serialise format for logging.
 
.DESCRIPTION
Retrieves the default serialise format used by the logging system.
 
.EXAMPLE
$format = Get-PodeLogDefaultSerialiseFormat
#>

function Get-PodeLogDefaultSerialiseFormat {
    [CmdletBinding()]
    [OutputType([Pode.Utilities.Logging.PodeSerialiseFormat])]
    param()

    return $PodeContext.Server.Logging.Formatting.Serialise
}

<#
.SYNOPSIS
Set the default serialise format for logging.
 
.DESCRIPTION
Sets the default serialise format used by the logging system.
 
.PARAMETER Format
The serialise format to set as the default.
 
.EXAMPLE
Set-PodeLogDefaultSerialiseFormat -Format 'Json'
#>

function Set-PodeLogDefaultSerialiseFormat {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [Pode.Utilities.Logging.PodeSerialiseFormat]
        $Format
    )

    $PodeContext.Server.Logging.Formatting.Serialise = $Format
}

<#
.SYNOPSIS
Convert a Pode Log Item or Collection to a string.
 
.DESCRIPTION
Converts a Pode Log Item or a collection of Pode Log Items into a string representation.
 
.PARAMETER Collection
An optional PodeLogItemCollection to convert to a string, converting all items in the collection.
 
.PARAMETER Item
An optional single PodeLogItem to convert to a string.
 
.EXAMPLE
Convert-PodeLogItemToString -Collection $logCollection
 
.EXAMPLE
Convert-PodeLogItemToString -Item $logItem
#>

function Convert-PodeLogItemToString {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Collection')]
        [Pode.Utilities.Logging.IPodeLogItemCollection]
        $Collection,

        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'Item')]
        [Pode.Utilities.Logging.IPodeLogItem]
        $Item
    )

    process {
        switch ($PSCmdlet.ParameterSetName) {
            'Collection' {
                return ($Collection.Items.Data | ConvertTo-PodeString) -join ([Environment]::NewLine)
            }
            'Item' {
                return $Item.Data | ConvertTo-PodeString
            }
        }
    }
}

<#
.SYNOPSIS
Creates a new Pode Log W3C Info object.
 
.DESCRIPTION
Creates a new Pode Log W3C Info object with the specified fields, version, and log header settings.
 
.PARAMETER Fields
The fields to include in the W3C log, created using Add-PodeLogW3CField or Add-PodeLogW3CCustomField.
 
.PARAMETER Version
The version of the W3C log format. (Default: 1.0)
 
.PARAMETER NoLogHeader
If supplied, the W3C log will not include the log header.
 
.EXAMPLE
$fields = @(
    Add-PodeLogW3CField -Name 'date'
    Add-PodeLogW3CField -Name 'time'
    Add-PodeLogW3CField -Name 'c-ip'
    Add-PodeLogW3CField -Name 'cs-method'
    Add-PodeLogW3CField -Name 'cs-uri-stem'
    Add-PodeLogW3CCustomField -Name 'custom-field' -Type 'Request'
)
$info = New-PodeLogW3CInfo -Fields $fields
#>

function New-PodeLogW3CInfo {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [hashtable[]]
        $Fields,

        [Parameter()]
        [version]
        $Version = [version]'1.0',

        [switch]
        $NoLogHeader
    )

    return @{
        Fields      = $Fields
        Version     = $Version
        NoLogHeader = $NoLogHeader.IsPresent
    }
}

<#
.SYNOPSIS
Creates a new Pode Log W3C Field object.
 
.DESCRIPTION
Creates a new Pode Log W3C Field object with the specified name.
 
.PARAMETER Name
The name of the W3C field to create.
 
.EXAMPLE
$field = Add-PodeLogW3CField -Name 'date'
#>

function Add-PodeLogW3CField {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('date', 'time', 'c-ip', 'cs-username', 's-ip', 's-port', 's-computername', 'cs-method', 'cs-uri-stem', 'cs-uri-query', 'sc-status', 'time-taken', 'sc-bytes', 'cs-bytes', 'cs-version', 'cs-host')]
        [string]
        $Name
    )

    return @{
        Type      = 'Standard'
        FieldName = $Name.ToLowerInvariant()
    }
}

<#
.SYNOPSIS
Creates a new Pode Log W3C Custom Field object.
 
.DESCRIPTION
Creates a new Pode Log W3C Custom Field object with the specified name and type.
 
.PARAMETER Name
The name of the custom field to create. Custom field names can only contain alphanumeric characters, hyphens, and underscores.
 
.PARAMETER Type
The type of the custom field, which can be 'Request', 'Response', or 'Environment'.
 
Request: retrieved from the request headers, and wrapped with "cs(...)" in the W3C log.
Response: retrieved from the response headers, and wrapped with "sc(...)" in the W3C log.
Environment: retrieved from the process environment variables, and prefixed with "x-" in the W3C log.
 
.EXAMPLE
$field = Add-PodeLogW3CCustomField -Name 'custom-field' -Type 'Request'
 
.EXAMPLE
$field = Add-PodeLogW3CCustomField -Name 'custom-field' -Type 'Response'
 
.EXAMPLE
$field = Add-PodeLogW3CCustomField -Name 'custom-field' -Type 'Environment'
#>

function Add-PodeLogW3CCustomField {
    [CmdletBinding()]
    [OutputType([hashtable])]
    param(
        [Parameter(Mandatory = $true)]
        [ValidatePattern('^[a-z0-9\-_]+$')]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [ValidateSet('Request', 'Response', 'Environment')]
        [string]
        $Type
    )

    $fieldName = switch ($Type) {
        'Request' { "cs($($Name))" }
        'Response' { "sc($($Name))" }
        'Environment' { "x-$($Name)" }
    }

    return @{
        Type      = $Type
        Name      = $Name
        FieldName = $fieldName
    }
}