Public/New-HDTLogContext.ps1
|
function New-HDTLogContext { <# .SYNOPSIS Builds the log context every Write-HDTLog call is written through. .DESCRIPTION A log context is DATA: the run id, the phase, the log directory, the two injected services, the verbosity, the monotonic seq counter and the step currently executing. It performs NO I/O when it is built, which is what lets the engine construct one in WinPE before a disk exists and hand the same object to every step afterwards. THE SEQ COUNTER LIVES HERE rather than in a module variable because it is seeded from state.json on resume. seq is required to survive reboots, "so the ordering of a multi-leg deployment is unambiguous even when timestamps skew across a clock change during specialize" - and a counter that reset on every leg would be exactly the ambiguity it exists to remove. Properties: RunId, Phase, LogPath, FileSystem, Clock, Level, Seq, StepIndex, StepName, StepType, StepLogPath, Component, ThreadId, JsonlPath, MasterLogPath. Methods: SetStep($index, $name, $type, $stepLogPath) ClearStep() NextSeq() SetStep is what makes this true - "entries carry the step name automatically, so a custom step's output is attributable without the author doing anything". It is a [pscustomobject] with ScriptMethod members rather than a PowerShell class: classes dot-sourced into the module are the known flaky path across -Force re-imports (see 01-03). .PARAMETER RunId The deployment run id, also _HDTRunId. .PARAMETER Phase WinPE or FullOS. .PARAMETER LogPath The log directory, from Get-HDTLogPath. .PARAMETER FileSystem An IFileSystem - New-HDTFileSystem in production, the fake in a test. .PARAMETER Clock An IClock - New-HDTClock in production, the fake in a test. .PARAMETER Level The verbosity floor: Error, Warning, Info or Debug. A call below it is dropped without consuming a seq number. Defaults to Info. .PARAMETER Seq The last seq number already written, restored from state.json on resume. Defaults to 0. .PARAMETER Component The default component for calls that name none. Defaults to Engine. .PARAMETER ThreadId The thread id stamped into every CMTrace line. Defaults to the managed thread id of the thread building the context; a test supplies one so it can assert an exact line. .OUTPUTS System.Management.Automation.PSCustomObject .EXAMPLE $context = New-HDTLogContext -RunId $runId -Phase WinPE ` -LogPath (Get-HDTLogPath -Phase WinPE) ` -FileSystem (New-HDTFileSystem) -Clock (New-HDTClock) Write-HDTLog -Context $context -Message 'Starting' -Event run.start #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Builds an in-memory context object; it changes no state and performs no I/O.')] [CmdletBinding()] [OutputType([object])] param( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $RunId, [Parameter(Mandatory = $true)] [ValidateSet('WinPE', 'FullOS')] [string] $Phase, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] $LogPath, [Parameter(Mandatory = $true)] [ValidateNotNull()] [object] $FileSystem, [Parameter(Mandatory = $true)] [ValidateNotNull()] [object] $Clock, [Parameter()] [ValidateSet('Error', 'Warning', 'Info', 'Debug')] [string] $Level = 'Info', [Parameter()] [long] $Seq = 0, [Parameter()] [ValidateNotNullOrEmpty()] [string] $Component = 'Engine', [Parameter()] [int] $ThreadId = [System.Threading.Thread]::CurrentThread.ManagedThreadId ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $trimmed = $LogPath.TrimEnd('\', '/') $context = [pscustomobject] @{ RunId = $RunId Phase = $Phase LogPath = $trimmed FileSystem = $FileSystem Clock = $Clock Level = $Level Seq = $Seq StepIndex = 0 # HOW MANY STEPS THE RUN HAS, which is a fact about the SEQUENCE rather # than about the step - so it is set once by Invoke-HDTTaskSequence and # left alone by SetStep and ClearStep. It is here so the status heartbeat # can carry it: "step 7" is a number nobody can act on, and the console # tailing Logs\_active\ cannot work the total out from a share it is # only reading. StepCount = 0 StepName = $null StepType = $null StepLogPath = $null Component = $Component ThreadId = $ThreadId JsonlPath = ('{0}\HDT.jsonl' -f $trimmed) MasterLogPath = ('{0}\HDT.log' -f $trimmed) } $context | Add-Member -MemberType ScriptMethod -Name SetStep -Value { param([int] $Index, [string] $Name, [string] $Type, [string] $StepLogPath) $this.StepIndex = $Index $this.StepName = $Name $this.StepType = $Type $this.StepLogPath = $StepLogPath } $context | Add-Member -MemberType ScriptMethod -Name ClearStep -Value { $this.StepIndex = 0 $this.StepName = $null $this.StepType = $null $this.StepLogPath = $null } $context | Add-Member -MemberType ScriptMethod -Name NextSeq -Value { $this.Seq = $this.Seq + 1 return $this.Seq } return $context } |