Public/Write-Log.ps1
|
<# .SYNOPSIS Writes a structured log message to the console and/or a log file. .DESCRIPTION The Write-Log function provides structured logging with severity levels, timestamps, and optional component prefixes. Messages can be written to the console, to a log file, or both. The function supports pipeline input for batch logging and uses appropriate PowerShell output streams for Debug and Verbose levels. .PARAMETER Message The log message to write. Accepts pipeline input, allowing multiple messages to be logged in sequence. .PARAMETER Level The severity level of the log message. Valid values are 'Info', 'Warning', 'Error', 'Debug', and 'Verbose'. Defaults to 'Info'. Info is written to the host in colour; the other levels use their PowerShell streams: Warning uses Write-Warning, Error uses Write-Error (a non-terminating error, so -ErrorAction and $ErrorActionPreference apply), Debug uses Write-Debug and Verbose uses Write-Verbose. .PARAMETER LogPath An optional file path to append the formatted log message to. If the file or its parent directory does not exist, they will be created automatically. .PARAMETER Component An optional component or module name prefix included in the formatted log message. When specified, the message is formatted as [$timestamp][$Level][$Component] $Message. .PARAMETER NoConsole When specified, suppresses console output and only writes to the log file. Requires LogPath to be specified for any output to occur. .PARAMETER DateFormat The timestamp format string used for log entries. Defaults to 'yyyy-MM-dd HH:mm:ss'. Accepts any valid .NET DateTime format string. .PARAMETER UseUtc Writes timestamps in UTC instead of local time. .PARAMETER PassThru Returns the formatted log line. .INPUTS System.String You can pipe one or more strings to Write-Log. .OUTPUTS None, or System.String when PassThru is specified. .EXAMPLE Write-Log -Message "Application started successfully." Writes an Info-level message to the console with a timestamp. .EXAMPLE Write-Log -Message "Connection failed" -Level Error -Component "Network" -LogPath "C:\Logs\app.log" Writes an Error-level message with a component prefix to the error stream and to the specified log file. .EXAMPLE "Step 1 complete", "Step 2 complete" | Write-Log -Level Info -LogPath "C:\Logs\steps.log" -NoConsole Pipes multiple messages to be logged silently to a file without console output. .NOTES Author: Nigel Tatschner Company: TheCodeSaiyan File writes append with shared read/write access, so several processes can log to the same file. Files are written as UTF-8 without a byte order mark. The file is written before the console output. Since tcs.core 0.4.0, Warning and Error levels use the warning and error streams instead of coloured host text, so they can be captured, redirected and suppressed. .LINK https://ntatschner.github.io/TheCodeSaiyan-PowerShell-tcs.core/ #> function Write-Log { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Info messages are coloured host output; Write-Host writes to the information stream (6) on PowerShell 5+.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidOverwritingBuiltInCmdlets', '', Justification = 'Write-Log is not a built-in command in current PowerShell versions; the name is part of the public API.')] [CmdletBinding()] [OutputType([void], [string])] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, HelpMessage = "The log message to write.")] [string]$Message, [Parameter(HelpMessage = "The severity level of the log message.")] [ValidateSet('Info', 'Warning', 'Error', 'Debug', 'Verbose')] [string]$Level = 'Info', [Parameter(HelpMessage = "File path to append the log message to.")] [string]$LogPath, [Parameter(HelpMessage = "Optional component or module name prefix.")] [string]$Component, [Parameter(HelpMessage = "Suppress console output and only write to file.")] [switch]$NoConsole, [Parameter(HelpMessage = "Timestamp format string for log entries.")] [ValidateNotNullOrEmpty()] [string]$DateFormat = 'yyyy-MM-dd HH:mm:ss', [Parameter(HelpMessage = "Write timestamps in UTC.")] [switch]$UseUtc, [Parameter(HelpMessage = "Return the formatted log line.")] [switch]$PassThru ) process { $now = if ($UseUtc) { [datetime]::UtcNow } else { [datetime]::Now } $timestamp = $now.ToString($DateFormat, [System.Globalization.CultureInfo]::InvariantCulture) if ([string]::IsNullOrWhiteSpace($Component)) { $formattedMessage = "[$timestamp][$Level] $Message" } else { $formattedMessage = "[$timestamp][$Level][$Component] $Message" } if ($LogPath) { $logDirectory = Split-Path -Path $LogPath -Parent if ($logDirectory -and -not (Test-Path -Path $logDirectory)) { New-Item -Path $logDirectory -ItemType Directory -Force | Out-Null } $utf8NoBom = New-Object System.Text.UTF8Encoding -ArgumentList $false $bytes = $utf8NoBom.GetBytes($formattedMessage + [Environment]::NewLine) $resolvedLogPath = $PSCmdlet.GetUnresolvedProviderPathFromPSPath($LogPath) $stream = New-Object System.IO.FileStream -ArgumentList $resolvedLogPath, ([System.IO.FileMode]::Append), ([System.IO.FileAccess]::Write), ([System.IO.FileShare]::ReadWrite) try { $stream.Write($bytes, 0, $bytes.Length) } finally { $stream.Dispose() } } # Written after the file, so the line is logged even if -ErrorAction Stop turns the error into an exception if (-not $NoConsole) { switch ($Level) { 'Info' { Write-Host $formattedMessage -ForegroundColor Cyan } 'Warning' { Write-Warning -Message $formattedMessage } 'Error' { Write-Error -Message $formattedMessage -Category NotSpecified -ErrorId 'WriteLogError' } 'Debug' { Write-Debug $formattedMessage } 'Verbose' { Write-Verbose $formattedMessage } } } if ($PassThru) { $formattedMessage } } } |