DictLogger.psm1
using namespace System.Collections using namespace System.Collections.Concurrent function Initialize-PSJobLoggerDict { [CmdletBinding()] [OutputType([ConcurrentDictionary[String, PSObject]])] param( [String]$Name = 'PSJobLogger', [String]$Logfile = '', [Switch]$UseQueues, [int]$ProgressParentId = -1 ) [ConcurrentDictionary[String, PSObject]]$logDict = [ConcurrentDictionary[String, PSObject]]::new() $dictElements = @{ Name = $Name Logfile = $Logfile ShouldLogToFile = $false VerbosePref = $VerbosePreference DebugPref = $DebugPreference UseQueues = $UseQueues ProgressParentId = $ProgressParentId } foreach ($key in $dictElements.Keys) { if (-not($logDict.TryAdd($key, $dictElements.$key))) { Write-Error "could not add element ${key} to dict" } } if ($Logfile -ne '' -and -not(Test-Path $Logfile)) { $null = New-Item $Logfile -ItemType File -Force -ErrorAction SilentlyContinue if (-not($Error[0])) { $logDict.ShouldLogToFile = $true } } $streams = [ConcurrentDictionary[int, ICollection]]::new() foreach ($stream in $PSJobLoggerLogStreams.Keys) { switch ($stream) { $PSJobLoggerStreamProgress { if (-not($streams.TryAdd($stream, [ConcurrentDictionary[String, ConcurrentDictionary[String, PSObject]]]::new()))) { Write-Error 'could not create new ConcurrentDictionary for progress stream' } } default { if ($UseQueues) { if (-not($streams.TryAdd($stream, [ConcurrentQueue[String]]::new()))) { Write-Error "could not create new ConcurrentQueue for $($PSJobLoggerLogStreams.$stream) stream" } } } } } if (-not($logDict.TryAdd('Streams', $streams))) { Write-Error 'could not add streams to dict' } return $logDict } function Set-Logfile { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [String]$Filename ) if ($Filename -ne '' -and -not(Test-Path $Filename)) { $null = New-Item $Filename -ItemType File -Force -ErrorAction 'SilentlyContinue' if ($Error[0]) { $logfileError = $Error[0] Write-Error "Unable to create log file ${Filename}: ${logfileError}" return } } $LogDict.Logfile = $Filename $LogDict.ShouldLogToFile = $LogDict.Logfile -ne '' } function Write-MessageToLogfile { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][int]$Stream, [Parameter(Mandatory)][String]$Message ) if ($LogDict.ShouldLogToFile) { Add-Content -Path $LogDict.Logfile -Value $(Format-LogMessage -LogDict $LogDict -Stream $Stream -Message $Message) -ErrorAction 'Continue' } } function Format-LogMessage { [CmdletBinding()] [OutputType([String])] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][int]$Stream, [Parameter(Mandatory)][String]$Message ) return $(Get-Date -Format FileDateUniversal -ErrorAction 'Continue'), "[$($LogDict.Name)]", "($($PSJobLoggerLogStreams.$Stream))", $Message -join ' ' } function Add-LogMessageToQueue { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][int]$Stream, [Parameter(Mandatory)][String]$Message ) Write-MessageToLogfile -LogDict $LogDict -Stream $Stream -Message $Message if ($LogDict.UseQueues) { [ConcurrentQueue[String]]$messageQueue = $LogDict.Streams.$Stream $messageQueue.Enqueue($Message) } Write-LogMessagesToStream -Stream $Stream -Messages @($Message) } function Write-LogOutput { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][String]$Message ) Add-LogMessageToQueue -LogDict $LogDict -Stream $PSJobLoggerStreamSuccess -Message $Message } function Write-LogError { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][String]$Message ) Add-LogMessageToQueue -LogDict $LogDict -Stream $PSJobLoggerStreamError -Message $Message } function Write-LogWarning { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][String]$Message ) Add-LogMessageToQueue -LogDict $LogDict -Stream $PSJobLoggerStreamWarning -Message $Message } function Write-LogVerbose { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][String]$Message ) Add-LogMessageToQueue -LogDict $LogDict -Stream $PSJobLoggerStreamVerbose -Message $Message } function Write-LogDebug { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][String]$Message ) Add-LogMessageToQueue -LogDict $LogDict -Stream $PSJobLoggerStreamDebug -Message $Message } function Write-LogInformation { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][String]$Message ) Add-LogMessageToQueue -LogDict $LogDict -Stream $PSJobLoggerStreamInformation -Message $Message } function Write-LogHost { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][String]$Message ) Add-LogMessageToQueue -LogDict $LogDict -Stream $PSJobLoggerStreamHost -Message $Message } function Write-LogProgress { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][String]$Id, [Parameter(Mandatory)][Hashtable]$ArgumentMap ) if ($null -eq $ArgumentMap) { Write-Error 'ArgumentMap cannot be null' return } [ConcurrentDictionary[String, ConcurrentDictionary[String, PSObject]]]$progressTable = $LogDict.Streams.$PSJobLoggerStreamProgress [ConcurrentDictionary[String, PSObject]]$progressArgs = $progressTable.GetOrAdd($Id, [ConcurrentDictionary[String, PSObject]]::new()) foreach ($key in $ArgumentMap.Keys) { if ($null -eq $ArgumentMap.$key -and $progressArgs.ContainsKey($key)) { [PSObject]$removedValue = $null if (-not($progressArgs.TryRemove($key, [ref]$removedValue))) { Write-Error "could not remove key ${key} from progress arg map" } continue } $progressArgs.$key = $ArgumentMap.$key } $progressParentId = $LogDict.GetOrAdd('ProgressParentId', -1) if ($progressParentId -ge 0) { if (-not($progressArgs.ContainsKey('ParentId'))) { $null = $progressArgs.TryAdd('ParentId', $progressParentId) } else { $progressArgs.ParentId = $progressParentId } } } function Show-LogProgress { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict ) [ConcurrentDictionary[String, ConcurrentDictionary[String, PSObject]]]$progressQueue = $LogDict.Streams.$PSJobLoggerStreamProgress # write progress records foreach ($recordKey in $progressQueue.Keys) { if ($null -eq $progressQueue.$recordKey) { Write-Warning "no queue record for ${recordKey}; skipping it" continue } $progressArgs = $progressQueue.$recordKey if ($null -ne $progressArgs.Id -and $null -ne $progressArgs.Activity -and $progressArgs.Activity -ne '') { Write-Progress @progressArgs -ErrorAction 'Continue' } # If the arguments included `Completed = $true`, remove the key from the progress stream dictionary if ($progressArgs.GetOrAdd('Completed', $false)) { if (-not($progressQueue.TryRemove($recordKey, [ref]@{}))) { Write-Error "failed to remove progress stream record ${recordKey}" } } } } function Show-LogFromOneStream { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict, [Parameter(Mandatory)][int]$Stream ) if ($Stream -eq $PSJobLoggerStreamProgress) { Show-LogProgress -LogDict $LogDict return } if (-not($LogDict.UseQueues)) { return } [String[]]$messages = @() [ConcurrentQueue[String]]$messageQueue = $LogDict.Streams.$Stream $dequeuedMessage = '' while ($messageQueue.Count -gt 0) { if (-not($messageQueue.TryDequeue([ref]$dequeuedMessage))) { Write-Error "unable to dequeue message from $($PSJobLoggerLogStreams.$Stream); queue count = $($messageQueue.Count)" break } $messages += $dequeuedMessage } # write messages to the desired stream Write-LogMessagesToStream -Stream $Stream -Messages $messages } function Show-Log { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict ) foreach ($stream in $PSJobLoggerLogStreams.Keys) { Show-LogFromOneStream -LogDict $LogDict -Stream $stream } } function Show-PlainTextLog { [CmdletBinding()] param( [Parameter(Mandatory)][ConcurrentDictionary[String, PSObject]]$LogDict ) foreach ($stream in $PSJobLoggerPlainTextLogStreams.Keys) { Show-LogFromOneStream -LogDict $LogDict -Stream $stream } } function Write-LogMessagesToStream { [CmdletBinding()] param( [Parameter(Mandatory)][int]$Stream, [Parameter(Mandatory)][String[]]$Messages ) foreach ($message in $Messages) { $formattedMessage = Format-LogMessage -LogDict $LogDict -Stream $Stream -Message $Message switch ($Stream) { ($PSJobLoggerStreamSuccess) { Write-Output -InputObject $formattedMessage -ErrorAction 'Continue' } ($PSJobLoggerStreamError) { Write-Error -Message $formattedMessage } ($PSJobLoggerStreamWarning) { Write-Warning -Message $formattedMessage -ErrorAction 'Continue' } ($PSJobLoggerStreamVerbose) { $VerbosePreference = $LogDict.VerbosePref Write-Verbose -Message $formattedMessage -ErrorAction 'Continue' } ($PSJobLoggerStreamDebug) { $DebugPreference = $LogDict.DebugPref Write-Debug -Message $formattedMessage -ErrorAction 'Continue' } ($PSJobLoggerStreamInformation) { Write-Information -MessageData $formattedMessage -ErrorAction 'Continue' } ($PSJobLoggerStreamHost) { $formattedMessage | Out-Host -ErrorAction 'Continue' } ($PSJobLoggerStreamProgress) { # The Progress stream is handled in a different function Write-Error "reached PSJobLoggerStreamProgress in Write-LogMessagesToStream; this is unexpected. message: ${formattedMessage}" } default { Write-Error "unexpected stream ${Stream}; message: ${formattedMessage}" } } } } |