Skip to content

Instantly share code, notes, and snippets.

@swbbl
Created March 10, 2023 07:10
Show Gist options
  • Save swbbl/1591467206b70eda61a9afe17bb37538 to your computer and use it in GitHub Desktop.
Save swbbl/1591467206b70eda61a9afe17bb37538 to your computer and use it in GitHub Desktop.
New-LogEntry. An easy way to create and write proper log entries
enum Severity {
INFO
WARNING
ERROR
}
class LogEntry {
[datetime] $DateTime = [datetime]::UtcNow
[Severity] $Severity
[string] $Message
LogEntry() {
$this.Severity = 'INFO'
}
LogEntry([Management.Automation.ErrorRecord] $ErrorRecord) {
$this.Severity = [Severity]::ERROR
$messageText = [System.Text.StringBuilder]::new()
$messageText.AppendLine('Error:')
$messageText.AppendLine("`tMessage:`n`t`t" + ($ErrorRecord.ToString() -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tPSMessageDetails:`n`t`t" + ($ErrorRecord.PSMessageDetails -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tPositionMessage:`n`t`t" + ($ErrorRecord.InvocationInfo.PositionMessage -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tCategoryInfo:`n`t`t" + ($ErrorRecord.CategoryInfo -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tFullyQualifiedErrorId:`n`t`t" + ($ErrorRecord.FullyQualifiedErrorId -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tErrorDetails:`n`t`t" + ($ErrorRecord.ErrorDetails -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tScriptStackTrace:`n`t`t" + ($ErrorRecord.ScriptStackTrace -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tTargetObject:`n`t`t" + ($ErrorRecord.TargetObject -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tPipelineIterationInfo:`n`t`t" + ($ErrorRecord.PipelineIterationInfo -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`t" + ('#' * 75))
$exception = $ErrorRecord.Exception
while ($null -ne $exception) {
$messageText.AppendLine("`tException:`n`t`t" + ($exception.GetType().FullName -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tMessage:`n`t`t" + ($exception.Message -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tSource:`n`t`t" + ($exception.Source -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tHResult:`n`t`t" + ($exception.HResult -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`tStackTrace:`n`t`t" + ($exception.StackTrace -replace '\r?\n', "`n`t`t"))
$messageText.AppendLine("`t" + ('-' * 75))
$exception = $exception.InnerException
}
$this.Message = $messageText.ToString()
}
LogEntry($Severity, $Message) {
$this.Severity = $Severity
$this.Message = $Message
}
[void] ToCsvFile ([string] $FilePath) {
$value = if ((Test-Path -LiteralPath $FilePath) -and (Get-Content -LiteralPath $FilePath -TotalCount 1)) {
$this.ToCsv($true)
} else {
$this.ToCsv($false)
}
Add-Content -LiteralPath $FilePath -Value $value -Encoding UTF8 -Force
}
[string[]] ToCsv () {
return $this.ToCsv($false)
}
[string[]] ToCsv ([bool] $ExcludeHeader) {
return $this |
Select-Object @{n='DateTime'; e= { $_.DateTime.ToString('o') } }, * -ErrorAction Ignore |
ConvertTo-Csv -Delimiter ',' -NoTypeInformation |
Select-Object -Skip ([int]$ExcludeHeader)
}
[LogEntry] AddPrefix ([string] $Prefix) {
if (-not [string]::IsNullOrWhiteSpace($Prefix)) {
$this.Message = $Prefix + "`n" + $this.Message
}
return $this
}
[LogEntry] AddSuffix ([string] $Suffix) {
if (-not [string]::IsNullOrWhiteSpace($Suffix)) {
$this.Message = $this.Message + "`n" + $Suffix
}
return $this
}
}
function New-LogEntry {
<#
.SYNOPSIS
Creates a new log entry.
.DESCRIPTION
Creates a new log entry with the following format:
DateTime|Severity|Message
The returned [LogEntry] class has further methods like converting to CSV (.ToCsv) or
saving it to CSV file (.ToCsvFile(<filePath) → also done when the parameter "-FilePath" has been provided) which will format the message depending on the type.
- An [ErrorRecord] will be formatted in a proper and useful way and it will iterate through each (inner)exception
- An [Object] will be piped to Format-List *
- A [String] will be written as is
If the message is of type [array] it will be enumerated.
If the environment variable $env:PSLogEntryFilePath is set (either local in the script or global in the OS), the parameter -FilePath is set to it by default.
.EXAMPLE
# Return a [LogEntry] (if $env:PSLogEntryFilePath is not set)
Get-Item C:\ | New-LogEntry
.EXAMPLE
# Return a CSV-formatted [LogEntry] (if $env:PSLogEntryFilePath is not set)
(Get-Item C:\ | New-LogEntry).ToCsv()
.EXAMPLE
# Write to CSV file (if $env:PSLogEntryFilePath is not set)
Get-Item C:\ | New-LogEntry -FilePath C:\temp\log.csv
.EXAMPLE
# Write to CSV file with $env:PSLogEntryFilePath
$env:PSLogEntryFilePath = 'C:\temp\envLogFile.csv'
Get-Item C:\ | New-LogEntry
.EXAMPLE
# Catch and trap all errors with $env:PSLogEntryFilePath
$env:PSLogEntryFilePath = 'C:\temp\envLogFile.csv'
trap {
New-LogEntry -Message $_
}
Get-ChildItem 'C:\System Volume Information\'
try {
1 / 0
} catch {
New-LogEntry -Message $_
Write-Error "Error occured. See logfile: $env:PSLogEntryFilePath"
}
#>
param(
# The Message of the log entry.
# The content will be formatted depending on the type.
# - An [ErrorRecord] will be formatted in a proper and useful way and it will iterate through each (inner)exception
# - An [Object] will be piped to Format-List *
# - A [String] will be written as is
[Parameter(Mandatory, ValueFromPipeline)]
[psobject[]] $Message,
# The [Severity] of the log entry (INFO, WARNING, ERROR)
[Parameter()]
[Severity] $Severity = 'INFO',
# The LogFile Path.
# If set, the log entry will be written to the logfile and won't be returned to the caller.
# Set the environment variable "PSLogEntryFilePath" with the desired logfile path as global default setting ($env:PSLogEntryFilePath = '<LogFilePath>').
[Parameter()]
[string] $FilePath = $env:PSLogEntryFilePath,
# An additional prefix for the message.
[Parameter()]
[string] $Prefix,
# An additional suffix for the message.
[Parameter()]
[string] $Suffix,
# If $true/set, an [ErrorRecord] will not be parsed and handled like an object ($_ | Format-List *).
# To handle an [ErrorRecord] like a string, cast it.
[Parameter()]
[switch] $NoErrorRecordParsing
)
foreach ($messageItem in $Message) {
$logEntry = if (-not $NoErrorRecordParsing -and $messageItem -is [Management.Automation.ErrorRecord]) {
[LogEntry]::new($messageItem).AddPrefix($Prefix).AddSuffix($Suffix)
} else {
[LogEntry]::new($Severity, ($messageItem | fl * | Out-String) -replace '^[^\w]+|[^\w]+$').AddPrefix($Prefix).AddSuffix($Suffix)
}
if ($FilePath) {
$logEntry.ToCsvFile($FilePath)
} else {
$logEntry
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment