functions/Write-Log.ps1
|
function Write-Log { <# .SYNOPSIS Standard event logging helper. .DESCRIPTION Writes structured log lines to a default log directory located in the parent folder's "logs" subfolder. If this script lives in ...\functions\, the logs default to ...\<parent-folder>\logs\. Creates the logs folder if missing. Supports levels, optional JSON output, console output, and pipeline input. .PARAMETER Message The text to log. Accepts pipeline input. .PARAMETER Level Log level (INFO, WARN, ERROR, DEBUG, TRACE). Default: INFO. .PARAMETER LogPath Override default file path. If not provided, default is: <parent-folder>\logs\<parentFolder>_yyyyMMdd.log .PARAMETER TimestampFormat Format string passed to Get-Date. Default: yyyy-MM-ddTHH:mm:ss.fffK. .PARAMETER Json Emit a JSON object per line instead of a plain text line. .PARAMETER WriteToHost Also write the formatted message to the host/console. .PARAMETER fileNamePrefix Optional prefix for the log file name. If provided, it will be used instead of the script name. .EXAMPLE "Something happened" | Write-Log -Level Info Write-Log -Message "Started process" -Level Debug -WriteToHost #> [CmdletBinding()] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, Position = 0)] [string]$Message, [ValidateSet('INFO', 'WARN', 'WARNING', 'ERROR', 'DEBUG', 'TRACE')] [string]$Level = 'INFO', [string]$LogPath, [string]$TimestampFormat = 'yyyy-MM-ddTHH:mm:ss.fffK', [switch]$Json, [switch]$WriteToHost, [string]$fileNamePrefix ) begin { # Determine script location and default log directory (parent\logs) $scriptDir = if ($PSScriptRoot) { $PSScriptRoot } elseif ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { # fallback to current directory (Get-Location).ProviderPath } $parentDir = Split-Path -Parent $scriptDir if (-not $parentDir) { $parentDir = $scriptDir } # defensive # standard metadata $computer = $env:COMPUTERNAME $user = $env:USERNAME $processId = $PID # determine script name for log filename if ($fileNamePrefix) { $scriptName = $fileNamePrefix } else { if ($MyInvocation.MyCommand.Path) { $scriptName = Split-Path -Leaf $MyInvocation.MyCommand.Path } elseif ($PSCommandPath) { $scriptName = Split-Path -Leaf $PSCommandPath } else { # fallback to invocation/name when no script path is available (interactive session) $scriptName = if ($MyInvocation.InvocationName) { $MyInvocation.InvocationName } else { $MyInvocation.MyCommand.Name } } } if (-not $LogPath) { $logDir = Join-Path $parentDir 'logs' # ensure folder exists if (-not (Test-Path -LiteralPath $logDir)) { New-Item -Path $logDir -ItemType Directory -Force | Out-Null } $LogPath = Join-Path $logDir ("{0}_{1}_{2}.log" -f $scriptName, $processId, (Get-Date).ToString('yyyyMMdd')) } else { # If a folder was provided, ensure it exists and append default filename if ((Test-Path -LiteralPath $LogPath) -and (Get-Item -LiteralPath $LogPath).PSIsContainer) { $LogPath = Join-Path $logDir ("{0}_{1}_{2}.log" -f $scriptName, $processId, (Get-Date).ToString('yyyyMMdd')) } else { # ensure dir for provided file exists $ld = Split-Path -Parent $LogPath if ($ld -and -not (Test-Path -LiteralPath $ld)) { New-Item -Path $ld -ItemType Directory -Force | Out-Null } } } } process { if (-not $Message) { return } # ignore empty pipeline items $ts = (Get-Date).ToString($TimestampFormat) $levelUp = $Level.ToUpper() if ($Json) { $obj = [PSCustomObject]@{ Timestamp = $ts Level = $levelUp Message = $Message User = $user Computer = $computer PID = $processId } $line = $obj | ConvertTo-Json -Depth 5 -Compress } else { # Plain text format: TIMESTAMP [LEVEL] [USER@COMPUTER] Message (Script: name PID: ####) $line = "{0} [{1}] [{2}@{3}] {4}" -f $ts, $levelUp, $user, $computer, $Message } # Write to log file (append). Use Out-File to ensure encoding and atomic append behavior. try { $line | Out-File -FilePath $LogPath -Append -Encoding UTF8 -Force } catch { # If writing fails, write to host error stream Write-Error "Write-Log: failed to write to $LogPath - $($_.Exception.Message)" } if ($WriteToHost) { if ($Json) { Write-Output $line } else { switch -Regex ($levelUp) { 'ERROR' { Write-Host $line -ForegroundColor Red } 'WARN|WARNING' { Write-Host $line -ForegroundColor Yellow } 'DEBUG' { Write-Host $line -ForegroundColor DarkGray } default { Write-Host $line } } } } # also output the constructed object for downstream consumption if ($Json) { # return object for easier programmatic consumption $obj } } } |