Files
winutil/functions/private/Write-WinUtilLog.ps1
T
Chris Titus a0d3c719a6 Improve UI startup after runspace overhaul (#5056)
* perf: keep UI startup responsive

* test: make favicon checks runner-safe

* fix: qualify favicon visibility state

* test: isolate favicon visibility fixture

* fix: enforce total favicon deadline

* fix: address favicon review feedback

* fix: recognize disabled SSH firewall rule

* perf: restore favicon download throughput

* docs: allow public PR review uploads

* perf: restore overlapping favicon loading
2026-09-07 11:33:39 -05:00

123 lines
4.2 KiB
PowerShell

function Write-WinUtilLog {
<#
.SYNOPSIS
Writes a timestamped WinUtil log entry to the active session log.
.DESCRIPTION
Called from the interface thread and from every job body. When Start-Transcript owns the
active session log, entries go through the host so the transcript records them without a
competing file write. Standalone callers use a named mutex to serialize direct appends.
.PARAMETER Message
The message to write.
.PARAMETER Level
The severity level for the log entry.
.PARAMETER Component
The WinUtil component producing the log entry.
#>
param (
[Parameter(Mandatory = $true)]
[string]$Message,
[ValidateSet("INFO", "WARN", "ERROR", "DEBUG")]
[string]$Level = "INFO",
[string]$Component = "WinUtil",
# Continuation of an error already counted, such as a stack frame
[switch]$Detail
)
# UI performance diagnostics are useful to developers but are too noisy for the release
# transcript. Compile.ps1 stamps local builds so DEBUG output cannot leak into CI artifacts.
if ($Level -eq "DEBUG" -and ($null -eq $sync -or -not $sync.IsLocalCompile)) {
return
}
if ($Level -eq "ERROR" -and -not $Detail -and $null -ne $sync.LoggedErrors) {
$null = $sync.LoggedErrors.Add("[$Component] $Message")
}
if ($Level -eq "ERROR" -and -not $Detail -and $global:WinUtilIsJobWorker) {
$global:WinUtilJobErrorCount++
}
try {
$logPath = $null
$transcriptPath = $null
if ($null -ne $sync -and $sync.ContainsKey("logPath")) {
$logPath = $sync.logPath
}
if ($null -ne $sync -and $sync.ContainsKey("transcriptPath")) {
$transcriptPath = $sync.transcriptPath
}
if ([string]::IsNullOrWhiteSpace($logPath) -and -not [string]::IsNullOrWhiteSpace($transcriptPath)) {
$logPath = $transcriptPath
}
if ([string]::IsNullOrWhiteSpace($logPath) -and $null -ne $sync -and $sync.ContainsKey("winutildir")) {
$logDirectory = Join-Path $sync.winutildir "logs"
$logPath = Join-Path $logDirectory "winutil_$(Get-Date -Format "yyyy-MM-dd_HH-mm-ss").log"
$sync.logPath = $logPath
}
if ([string]::IsNullOrWhiteSpace($logPath) -and -not [string]::IsNullOrWhiteSpace($env:LocalAppData)) {
if ([string]::IsNullOrWhiteSpace($script:WinUtilLogPath)) {
$logDirectory = Join-Path (Join-Path $env:LocalAppData "winutil") "logs"
$script:WinUtilLogPath = Join-Path $logDirectory "winutil_$(Get-Date -Format "yyyy-MM-dd_HH-mm-ss").log"
}
$logPath = $script:WinUtilLogPath
}
if ([string]::IsNullOrWhiteSpace($logPath)) {
return
}
$logDirectory = Split-Path -Path $logPath -Parent
if (-not (Test-Path $logDirectory)) {
New-Item -Path $logDirectory -ItemType Directory -Force | Out-Null
}
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss.fff"
$line = "[$timestamp] [$Level] [$Component] $Message"
if (-not [string]::IsNullOrWhiteSpace($transcriptPath) -and $logPath -eq $transcriptPath) {
Write-Host $line
return
}
$mutex = [System.Threading.Mutex]::new($false, "WinUtilSessionLog")
$held = $false
try {
try {
$held = $mutex.WaitOne(2000)
} catch [System.Threading.AbandonedMutexException] {
# A thread died holding the mutex; ownership transfers to us either way
$held = $true
}
if (-not $held) {
# Writing anyway is what interleaves lines, and the wait only times out when
# contention is at its worst
Write-Host $line
return
}
Add-Content -Path $logPath -Value $line -Encoding UTF8 -ErrorAction Stop
} catch [System.IO.IOException] {
Write-Host $line
} finally {
if ($held) { $mutex.ReleaseMutex() }
$mutex.Dispose()
}
} catch {
Write-Warning "Unable to write WinUtil log entry: $($_.Exception.Message)"
}
}