Public/Read-Log4NetLog.ps1

<#
.SYNOPSIS
  Parses a log4net into an object that is easier to search and filter.
.DESCRIPTION
  Reads log4net log(s) and creates a new set of custom objects. It highlights
  details that make it easier to search and filter logs.
.NOTES
  Works for Windows PowerShell and PowerShell Core.
.LINK
  TBD
.EXAMPLE
  Read-Log4NetLog

  This will read a .log file in the current directory.
.PARAMETER Path
  The path to the directory/file you want to parse.
.PARAMETER FileLimit
  How many files should we parse if given a folder path?
.PARAMETER Filter
  The filter passed to Get Child Item. Default to '*.log'
.PARAMETER PatternLayout
  The matching pattern layout.

  https://logging.apache.org/log4net/release/sdk/?topic=html/T_log4net_Layout_PatternLayout.htm
#>

function Read-Log4NetLog {
  [OutputType([System.Collections.ArrayList])]
  param (
    [ValidateScript({
        if (-Not ($_ | Test-Path) ) {
          throw "File or folder does not exist"
        }
        return $true
      })]
    [string[]]
    $Path,
    [int]
    $FileLimit = 1,
    [String]
    $Filter = '*',
    [String]
    $PatternLayout = '%date %thread [%-5level] - %message'
  )
  $files = Get-Item -Path $Path
  if ($files.PSIsContainer) {
    $files = Get-ChildItem -Path $Path -Filter $Filter | Sort-Object -Property LastWriteTime | Select-Object -Last $FileLimit
  }

  [System.Collections.ArrayList]$parsed = @()

  # Get the regex for the Log4Net PatternLayout
  $RegularExpression = Convert-PatternLayout -PatternLayout $PatternLayout
  $files | ForEach-Object -Process {
    $file = $_
    $raw = [System.IO.File]::ReadAllLines($file.FullName)

    # Iterate over each line
    foreach ($line in $raw) {
      # Write-Debug $line
      $m = $RegularExpression.match($line)
      if ($m.Success) {
        # If it matches the regex, tag it
        if ( $m.Groups['thread'].Value -ne $currentSession.thread) {
          if ($currentSession) {
            $currentSession.endTime = $currentSession.logs[-1].time
            $parsed.Add($currentSession) > $null
          }

          # This is a different session
          $currentSession = [Log4NetLog]::new(
            $m.Groups['thread'].Value,
            ($m.Groups['date'].Value -replace ',', '.'),
            $file
          )
        }

        $currentSession.logs.Add(
          [Log4NetLogLine]::new(
            [Datetime]($m.Groups['date'].Value -replace ',', '.'),
            $m.Groups['thread'].Value,
            $m.Groups['level'].Value,
            $m.Groups['message'].Value
          )) > $null
      } else {
        # if it doesn't match regex, append to the previous
        if ($currentSession) {
          $currentSession.logs[-1].AppendMessage($line)
        } else {
          # This might happen if the log starts on what should have been a
          # multiline entry... Not very likely
          Write-Warning "No currentSession. File: $File; Line: $Line"
        }
      }
    }
  }
  # Write out the last log line!
  if (-Not $parsed.Contains($currentSession)) {
    $parsed.Add($currentSession) > $null
  }

  # Return the whole parsed object
  $parsed
}