Private/Write-RaidinessLog.ps1
|
<# The run's log. Before this there was none. A collector that failed produced a warning that scrolled away, and Invoke-Raidiness then reduced the whole list of skip reasons to a count (".Skipped.Count") -- so the one question an operator asks afterwards, "why is this not measured?", had no answer anywhere on disk. Two files, on purpose: raidiness.log what a person reads and pastes into a ticket raidiness.jsonl one object per event, for the run summary and the report Both are appended with Out-File. Not Add-Content or Set-Content: "Add-" and "Set-" are write verbs and scripts/ps-lint.ts refuses them without a documented allow marker, which a log file does not deserve. Out- is not a write verb, which is also why the evidence writer already uses it. Every function no-ops when no log has been opened, so the private helpers can call Write-RaidinessLog unconditionally -- including when someone runs Invoke-RaidinessModule on its own. #> # Set-StrictMode refuses to read a variable that was never assigned, and the # whole point of the helpers below is that they can be called before a log is # opened. Declaring it here, at dot-source time, is what makes that safe. $script:RaidinessLog = $null function Open-RaidinessLog { [CmdletBinding()] param( # The run folder; the two files are written inside it. [Parameter(Mandatory = $true)] [string] $Path, [ValidateSet('Quiet', 'Normal', 'Verbose')] [string] $Level = 'Normal' ) $script:RaidinessLog = @{ Text = Join-Path $Path 'raidiness.log' Events = Join-Path $Path 'raidiness.jsonl' Level = $Level Started = Get-Date Counts = @{ info = 0; warn = 0; error = 0 } } $version = (Get-Module -Name Raidiness | Select-Object -First 1).Version Write-RaidinessLog -Phase 'run' -Message "Raidiness $version on PowerShell $($PSVersionTable.PSVersion) -- read-only run started" $script:RaidinessLog } function Write-RaidinessLog { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string] $Message, [ValidateSet('info', 'warn', 'error', 'debug')] [string] $Level = 'info', # Which part of the run this belongs to: run, connect, collect, module, intake, export. [string] $Phase = 'run', # The collector or module the line is about, when there is one. [string] $Source, [Nullable[double]] $DurationMs, # Anything else worth keeping; goes to the jsonl only. [hashtable] $Data ) $log = $script:RaidinessLog if (-not $log) { return } if ($Level -eq 'debug' -and $log.Level -ne 'Verbose') { return } if ($log.Counts.ContainsKey($Level)) { $log.Counts[$Level]++ } $now = Get-Date $line = '{0} {1,-5} {2,-8} {3}' -f $now.ToString('yyyy-MM-dd HH:mm:ss'), $Level.ToUpperInvariant(), $Phase, $Message if ($Source) { $line = '{0} {1,-5} {2,-8} {3} {4}' -f $now.ToString('yyyy-MM-dd HH:mm:ss'), $Level.ToUpperInvariant(), $Phase, $Source, $Message } if ($null -ne $DurationMs) { $line += ' ({0:n0} ms)' -f $DurationMs } $line | Out-File -FilePath $log.Text -Append -Encoding utf8 $record = [ordered]@{ ts = $now.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ') level = $Level phase = $Phase message = $Message } if ($Source) { $record.source = $Source } if ($null -ne $DurationMs) { $record.durationMs = [math]::Round($DurationMs) } if ($Data) { foreach ($key in $Data.Keys) { $record[$key] = $Data[$key] } } ConvertTo-Json -InputObject $record -Depth 6 -Compress | Out-File -FilePath $log.Events -Append -Encoding utf8 } function Close-RaidinessLog { [CmdletBinding()] [OutputType([pscustomobject])] param() $log = $script:RaidinessLog if (-not $log) { return $null } $elapsed = (Get-Date) - $log.Started Write-RaidinessLog -Phase 'run' -Message ('Finished in {0:n0}s -- {1} warning(s), {2} error(s)' -f $elapsed.TotalSeconds, $log.Counts.warn, $log.Counts.error) $summary = [pscustomobject]@{ PSTypeName = 'Raidiness.LogSummary' Log = $log.Text Events = $log.Events Warnings = $log.Counts.warn Errors = $log.Counts.error Seconds = [math]::Round($elapsed.TotalSeconds, 1) } $script:RaidinessLog = $null $summary } function Get-RaidinessLogPath { [CmdletBinding()] [OutputType([string])] param() $script:RaidinessLog ? $script:RaidinessLog.Text : $null } |