JT.WriteLog.psm1
Enum errorLevel { ERROR WARNING SUCCESS INFO DEBUG } Enum logRecycle { Continue Daily Hourly } $_setting = @{ 'DebugMode' = $false } Function Write-Log { <# .SYNOPSIS Write-Log is a Powershell logging script. .PARAMETER ErrorLevel Define level of the message. .PARAMETER Message Log message. .PARAMETER LogRecycling Specific the recycling of the log file (Default value is 'continue' whcih it will write into single file.) .PARAMETER LogPath Specific the path of the log file (The default path is the 'Log' folder under the script locaiton). If the command is not call from the script, it will create a log file '_log.txt' under existing location. If the 'LogPath' is $null, the log message will write to console only. .PARAMETER DebugMode Switch the 'DebugMode' on and off. If debug mode is off, it will not output and message to console and log file when the errorlevel of the message is DEBUG. .PARAMETER RawData Direct write data into log file. (If 'RawData' contain data, it will bypass the data input from 'ErrorLevel' and 'Message') Remark: The Write-Log will not show any messaage on console and add any information (Timestamp, Errorlevel, Call from) when write to file. .EXAMPLE Write-Log -errorLevel INFO -message 'Hello World!' Output (console): yyyy-MM-dd HH:mm:ss.fff | INFO | <ScriptBlock> | Hello World! Output file name: _log.txt .EXAMPLE Write-Log -errorLevel INFO -message 'Hello World!' -logRecycling Daily Output (console): yyyy-MM-dd HH:mm:ss.fff | INFO | <ScriptBlock> | Hello World! Output file name: _yyyy-MM-dd_log.txt .EXAMPLE If wants to collect the log from 'Start-Job' or 'Start-ThreadJob', it suggest create a funciton under script block and disable write log to file temporary. Disable write log to file: $PSDefaultParameterValues.Add('Write-Log:LogPath', $null) Example: [ScriptBlock]$Test-ScriptBlock = { $PSDefaultParameterValues.Add('Write-Log:LogPath', $null) Function Run-ScriptBlock { Write-Log -ErrorLevel INFO -Message 'Log message' } Run-ScriptBlock } Start-ThreadJob -ScriptBlock $Test-ScriptBlock Get-Job | Wait-Job | Receive-Job 6>&1 | Write-Log .NOTES It suggest use '$PSDefaultParameterValues' to pre-define the 'logRecycling' and 'logPath' setting. e.g.: $PSDefaultParameterValues['Write-Log:logRecycling'] = 'daily' $PSDefaultParameterValues['Write-Log:logPath'] = 'C:\Temp\' $PSDefaultParameterValues['Write-Log:debugMode'] = $True #> [CmdletBinding()] param( [errorLevel]$ErrorLevel = 'INFO', [string]$Message, [logRecycle]$LogRecycling = 'Continue', [string]$LogPath = './Log', [switch]$DebugMode, [parameter(ValueFromPipeline = $true)] [string[]]$RawData = $null, [ValidatePattern("^\d+$")] [Parameter(DontShow)] [string]$LengthOfFunctionName = 20 ) Process { $_configure = @{ 'FuncitonName' = ''; 'PsScrtipRoot' = ''; 'LogFileName' = '' } $Stack = @(Get-PSCallStack) If ($_setting.DebugMode) { $Global:CallStack = $Stack } If ($Stack.Count -eq 2) { # Call from console $_configure.FuncitonName = '<Console>' $_configure.PsScrtipRoot = $PWD.ToString() } Else { $_configure.FuncitonName = $Stack[1].Command If ($Stack[-2].FunctionName -eq '<ScriptBlock>') { $_configure.PsScrtipRoot = Split-Path $Stack[-2].ScriptName $_configure.LogFileName = Split-Path $CallStack[-2].ScriptName -LeafBase } Else { $_configure.PsScrtipRoot = $PWD.ToString() } } # MARK: Pre-define setting $_date = Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff' $_logFormat = '{0} | {1, -7} | {2, -xxxx} | {3}'.Replace('xxxx', $LengthOfFunctionName.Trim()) # {yyyy-MM-dd HH:mm:ss.fff} | {ErrorLevel} | {FunctionName} | {Message} $_errorTab = @{ 'ERROR' = 'Red'; 'WARNING' = 'Yellow'; 'SUCCESS' = 'Green'; 'INFO' = 'White'; 'DEBUG' = 'DarkYellow' } # MARK: Log file cycling Switch ($LogRecycling) { 'Daily' { $_logRecyclingValue = Get-Date -Format '_yyyy-MM-dd'; break } 'Hourly' { $_logRecyclingValue = Get-Date -Format '_yyyy-MM-dd-HH'; break } Default { $_logRecyclingValue = '' } } # MARK: Log file location If ([string]::IsNullOrEmpty($LogPath)) { $_logPath = $null } Else { If (-not $LogPath.Contains(':')) { $LogPath = "$($_configure.PsScrtipRoot)\$($LogPath)" } # Check the log folder and try to create it if not exist. If (Test-Path $LogPath -PathType Container) { $_logPath = $LogPath } Else { Try { $_logPath = (New-Item $LogPath -ItemType Directory -Force -ErrorAction SilentlyContinue).ToString() } Catch { Write-Host -ForegroundColor Red "<<<<< Error for create log folder ($($LogPath))!! >>>>>" } } } # MARK: Define log file name If ([string]::IsNullOrEmpty($_logPath)) { $_logFile = $null } Else { $_logFile = [string]::Concat( "$($_logPath)\", $_configure.LogFileName, $_logRecyclingValue, '_log.txt') } If ([string]::IsNullOrEmpty($RawData)) { ## ---------------------------- ## MARK: Generate log message ## ---------------------------- # Define call from If ($Stack[1].Command -ne '<ScriptBlock>') { $_functionName = $Stack[1].Command } Else { $_functionName = '<Console>' } # Prepare log message If (!$debugMode -and ($ErrorLevel -eq 'DEBUG')) { $_logMessage = $null } Else { $_logMessage = [string]::Format( $_logFormat, $_date, $ErrorLevel.ToString().ToUpper(), $_functionName, $Message ) } # Write message to console If (![string]::IsNullOrEmpty($_logMessage)) { If ($DebugMode) { If ($ErrorLevel -eq 'INFO') { Write-Host $_logMessage } Else { Write-Host $_logMessage -ForegroundColor $_errorTab.($ErrorLevel.ToString()) } } Else { If ($ErrorLevel -eq 'INFO') { Write-Host $Message } Else { Write-Host $Message -ForegroundColor $_errorTab.($ErrorLevel.ToString()) } } } } Else { ## ---------------------------- ## MARK: Write log from 'RawData' ## ---------------------------- $_logMessage = $RawData } # MARK: Write message to log file If (![string]::IsNullOrEmpty($_logFile)) { $_logMessage | Out-File -Append $_logFile } } } Export-ModuleMember -Function 'Write-Log' |