Private/Classes/50-HttpLogEntry.ps1

<#
    Base class for log file entires
#>

class ServerLogEntry
{
    [HttpRequest]$Request

    [HttpResponse]$Response

    [DateTime]$RequestTimestamp

    [int]$RequestDuration

    [System.Net.IPEndpoint]$Server

    [System.Net.IPEndpoint]$Client

    ServerLogEntry([HttpRequest]$request, [HttpResponse]$response, [DateTime]$requestTimestamp, [int]$requestDuration, [System.Net.Sockets.Socket]$socket)
    {
        $this.Request = $request
        $this.Response = $response
        $this.RequestTimestamp = $requestTimestamp
        $this.RequestDuration = $requestDuration
        $this.Server = $socket.LocalEndPoint
        $this.Client = $socket.RemoteEndPoint
    }
}

<#
    Formats an entry for the HTTP server log
#>

class HttpLogEntry : ServerLogEntry
{
    HttpLogEntry([HttpRequest]$request, [HttpResponse]$response, [DateTime]$requestTimestamp, [int]$requestDuration, [System.Net.Sockets.Socket]$socket) : base($request, $response, $requestTimestamp, $requestDuration, $socket)
    {
    }

    [string]ToString()
    {
        # Format a log line
        # https://stackify.com/how-to-interpret-iis-logs/

        $query = $(
            if ($null -ne $this.Request.QueryParameters)
            {
                $this.Request.QueryParameters.ToString()
            }
            else
            {
                '-'
            }
        )

        $host = ($this.GetRequestHeaderValue('Host') -split ' ')[0]

        return "{0} {1} {2} {3} {4} {5} {6} {7} {8} {9} {10} {11} {12} {13} {14}`n" -f $this.RequestTimestamp.ToString('yyyy-MM-dd HH:mm:ss'),  # date time 0
            [System.Environment]::MachineName,          # s-computername 1
            $this.Server.Address.IPAddressToString,     # s-ip 2
            $this.Request.RequestMethod,                # cs-method 3
            $this.Request.Path,                         # cs-uri-stem 4
            $query,                                     # cs-uri-query 5
            $this.Server.Port,                          # s-port 6
            $this.Client.Address.IPAddressToString,     # c-ip 7
            ('HTTP/' + $this.Request.ProtocolVersion),  # cs-version 8
            $this.GetRequestHeaderValue('User-Agent'),  # cs(User-Agent) 9
            $this.GetRequestHeaderValue('Referer'),     # cs(Referer) 19
            $host,                                      # cs-host 11
            $this.Response.Status.StatusCode,           # sc-status 12
            $this.Response.BytesSent,                   # sc-bytes 13
            $this.RequestDuration                       # time-taken 14
    }

    static [string]GetLogHeader([PSModuleInfo]$moduleInfo)
    {
        return [System.Text.StringBuilder]::new().
            AppendLine("#Software: $($moduleInfo.Name) $($moduleInfo.Version.ToString())").
            AppendLine('#Version: 1.0').
            AppendLine('#Date: {0}').  # [datetime]::UtcNow.ToString('yyyy-MM-dd HH:mm:ss')
            AppendLine('#Fields: date time s-computername s-ip cs-method cs-uri-stem cs-uri-query s-port c-ip cs-version cs(User-Agent) cs(Referer) cs-host sc-status sc-bytes time-taken').
            ToString()
    }

    hidden [string]GetRequestHeaderValue([string]$key)
    {
        if ($this.Request.Headers.ContainsKey($key))
        {
            $value = $this.Request.Headers[$key]

            if ($value -match '\s')
            {
                return "`"$($value)`""
            }

            return $value
        }

        return '-'
    }
}

<#
    Formats an entry for the error log.
#>

class ErrorLogEntry : ServerLogEntry
{
    ErrorLogEntry([HttpRequest]$request, [HttpResponse]$response, [DateTime]$requestTimestamp, [int]$requestDuration, [System.Net.Sockets.Socket]$socket) : base($request, $response, $requestTimestamp, $requestDuration, $socket)
    {
    }

    [string]ToString()
    {
        $query = $(
            if ($null -ne $this.Request.QueryParameters)
            {
                '?' + $this.Request.QueryParameters.ToString()
            }
            else
            {
                [string]::Empty
            }
        )

        $reason = $(
            if ($this.Response.UnderlyingException)
            {
                $this.Response.UnderlyingException.Message.Replace(([System.Environment]::NewLine), ' ')
            }
            else
            {
                $this.Response.Status.StatusMessage
            }
        )

        if ($reason.IndexOf(' ') -gt -1)
        {
            $reason = '"' + $reason.Replace('"', "'") + '"'
        }

        return "{0} {1} {2} {3} {4} {5} {6} {7} {8} {9}`n" -f $this.RequestTimestamp.ToString('yyyy-MM-dd HH:mm:ss'),  # date time 0
            $this.Client.Address.IPAddressToString,     # c-ip 1
            $this.Client.Port,                          # c-port 2
            $this.Server.Address.IPAddressToString,     # s-ip 3
            $this.Server.Port,                          # s-port 4
            ('HTTP/' + $this.Request.ProtocolVersion),  # cs-version 5
            $this.Request.RequestMethod,                         # cs-method 6
            ($this.Request.Path + $query),              # cs-uri 7
            $this.Response.Status.StatusCode,           # sc-status 8
            $reason                                     # s-reason 9
    }

    static [string]GetLogHeader([PSModuleInfo]$moduleInfo)
    {
        return [System.Text.StringBuilder]::new().
            AppendLine("#Software: $($moduleInfo.Name) $($moduleInfo.Version.ToString())").
            AppendLine('#Version: 1.0').
            AppendLine('#Date: {0}').  # [datetime]::UtcNow.ToString('yyyy-MM-dd HH:mm:ss')
            AppendLine('#Fields: date time c-ip c-port s-ip s-port cs-version cs-method cs-uri sc-status s-reason').
            ToString()
    }
}