Public/Read-ChocoLog.ps1

<#
.SYNOPSIS
  Parses a Chocolatey log into an object that is easier to search and filter.
.DESCRIPTION
  Reads Chocolatey log(s) and creates a new set of custom objects. It highlights
  details that make it easier to search and filter.
.NOTES
  Works for Windows PowerShell and PowerShell Core. This works on Linux.
.LINK
  https://heyitsgilbert.github.io/ChocoLogParse/en-US/Read-ChocoLog/
.EXAMPLE
  Read-ChocoLog

  This will read the latest Chocolatey.log on the machine.
.EXAMPLE
  Read-ChocoLog -NoColor

  This will read the latest Chocolatey.log on the machine without colored output.
.PARAMETER Path
  The log path you want to parse. This will default to the latest local log.
  This can be a directory of logs.
.PARAMETER FileLimit
  The number of files the command should parse given a folder path.
.PARAMETER Filter
  The filter passed to Get Child Item. Default to 'chocolatey*.log.'
.PARAMETER PatternLayout
  The log4net pattern layout used to parse the log. It is very unlikely that you
  need to supply this. The code expects pattern names: time, session, level, and
  message.
.PARAMETER NoColor
  Disables colored output in the formatter. When specified, the output will be
  displayed without ANSI color codes.
#>

function Read-ChocoLog {
  # This makes PlatyPS sad.
  [OutputType([System.Collections.Generic.List[ChocoLog]])]
  param (
    [ValidateScript({
        if (-not ($_ | Test-Path) ) {
          throw "File or folder does not exist"
        }
        return $true
      })]
    [string[]]
    $Path = "$($env:ChocolateyInstall)\logs\",
    [int]
    $FileLimit = 1,
    [String]
    $Filter = 'chocolatey*.log',
    [string]
    $PatternLayout = '%date %thread [%-5level] - %message',
    [switch]
    $NoColor
  )

  # Set module-level variable to control coloring in formatter
  $script:ChocoLogNoColor = $NoColor.IsPresent

  $files = Get-Item -Path $Path
  if ($files.PSIsContainer) {
    $files = Get-ChildItem -Path $Path -Filter $Filter |
      Sort-Object -Property LastWriteTime | Select-Object -Last $FileLimit
  }
  Write-Verbose "Found files: $($files -join ',')"

  $parsed = @{}

  # Get the regex for the Log4Net PatternLayout
  $RegularExpression = Convert-PatternLayout -PatternLayout $PatternLayout
  $files | ForEach-Object -Process {
    $file = $_
    Write-Verbose "Reading over file: $file"
    $raw = [System.IO.File]::ReadAllLines($file.FullName)
    Write-Verbose "Lines read: $($raw.Count)"
    # Iterate over each line
    foreach ($line in $raw) {
      Write-Debug $line
      $m = $RegularExpression.match($line)
      if ($m.Success) {
        [int]$threadMatch = $m.Groups['thread'].Value
        # Replace comma with period to make it a valid datetime
        [datetime]$currentDateTime = $m.Groups['date'].Value -replace ',', '.'
        # Check if thread exists, if not make it.
        if (-not ($parsed.ContainsKey($threadMatch))) {
          Write-Verbose "New thread detected: $threadMatch"
          $null = $parsed.Add(
            $threadMatch,
            [ChocoLog]::new(
              $threadMatch,
              $file
            )
          )
        }
        Write-Verbose "Adding new log line to thread $threadMatch"
        $parsed.Item($threadMatch).AddLogLine(
          [Log4NetLogLine]::new(
            $currentDateTime,
            $threadMatch,
            $m.Groups['level'].Value,
            $m.Groups['message'].Value
          ))
      } else {
        Write-Verbose "Line did not match regex"
        Write-Debug $line
        # if it doesn't match regex, append to the previous
        if ($threadMatch) {
          Write-Verbose "Appending to existing thread: $threadMatch"
          $parsed.Item($threadMatch).AppendLastLogLine($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"
        }
      }
    }
  }

  # Doing this at the end since threads can get mixed
  $parsed.Keys | ForEach-Object {
    Write-Verbose "Parsing special logs for: $_"
    # This updates fields like: cli, environment, and configuration
    $parsed.Item($_).ParseSpecialLogs()
  }

  # Return the whole parsed object
  Write-Verbose "Returning results in descending order. Count: $($parsed.Count)"
  $return = $parsed.Values | Sort-Object -Descending Time
  return $return
}