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 with color-coded output, 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'. Debug and Verbose levels use their respective PowerShell output streams (Write-Debug, Write-Verbose) for console output. .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 both the console (in red) and 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. .LINK https://ntatschner.github.io/TheCodeSaiyan-PowerShell-tcs.core/ #> function Write-Log { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Coloured console output is the purpose of this function; 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 (-not $NoConsole) { switch ($Level) { 'Info' { Write-Host $formattedMessage -ForegroundColor Cyan } 'Warning' { Write-Host $formattedMessage -ForegroundColor Yellow } 'Error' { Write-Host $formattedMessage -ForegroundColor Red } 'Debug' { Write-Debug $formattedMessage } 'Verbose' { Write-Verbose $formattedMessage } } } 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() } } if ($PassThru) { $formattedMessage } } } |