# encoding: ascii
# api: powershell
# title: Logger.psm1
# description: The simplest logger. In your script just import-module Logger and debug, verbose, warnings and errors are logged to file.
# version: 0.2
# type: module
# author: Joel Bennett
# license: CC0
# function: Get-Logger
# x-poshcode-id: 1745
# x-derived-from-id: 1747
# x-archived: 2016-03-23T13:06:40
# x-published: 2011-04-07T21:12:00
#
# This is a simple wrapper around Log4Net which (by default) causes Write-Verbose, Write-Warning, Write-Debug, and Write-Error to log their output when called from (any) script.
# By default it logs to file, but it can log to the event log, console, .net trace, etc by simply using the Get-Logger function to configure new loggers (help is next)
# There are a few extra helper methods in here, including Push-Context/Pop-Context (which you can call in your functions to enable stack tracing), and still more to come.
#
<#
Name : Universal Log4Net Logging Module (Logger.psm1)
Version : 0.2
Author : Joel Bennett (MVP)
Site : http://www.HuddledMasses.org/
Version History:
0.2 - Configless release.
Now configures with inline XML, and supports switches to create "reasonable" default loggers
Changed all the functions to Write-*Log so they don't overwrite the cmdlets
Added -Logger parameter to take the name of the logger to use (it must be created beforehand via Get-Logger)
Created aliases for Write-* to override the cmdlets -- these are easy for users to remove without breaking the module
** NEED to write some docs, but basically, this is stupid simple to use, just:
Import-Module Logger
Write-Verbose "This message will be saved in your profile folder in a file named PowerShellLogger.log (by default)"
To change the defaults for your system, edit the last line in the module!!
0.1 - Initial release. http://poshcode.org/1744 (Required config: http://poshcode.org/1743)
Uses Log4Net : http`://logging.apache.org/log4net/download.html
Documentation: http`://logging.apache.org/log4net/release/sdk/
NOTES:
By default, this overwrites the Write-* cmdlets for Error, Warning, Debug, Verbose, and even Host.
This means that you may end up logging a lot of stuff you didn't intend to log (ie: verbose output from other scripts)
To avoid this behavior, remove the aliases after importing it
Import-Module Logger; Remove-Item Alias:Write-*
Write-Warning "This is your warning"
Write-Debug "You should know that..."
Write-Verbose "Everything would be logged, otherwise"
***** NOTE: IT ONLY OVERRIDES THE DEFAULTS FOR SCRIPTS *****
It currently has no effect on error/verbose/warning that is logged from cmdlets.
#>
Add-Type -Path $PSScriptRoot\log4net.dll
function Get-Logger {
param(
[Parameter(Mandatory=$false)]
[string]$LoggerName = "*"
,
[Parameter(Mandatory=$false)]
[ValidateSet("DEBUG","INFO","WARN","ERROR","FATAL","VERBOSE","HOST","WARNING")]
[string]$LogLevel = "DEBUG"
,
[Parameter(Mandatory=$false)]
[string]$LogFolder = $PSScriptRoot
, [Switch]$Force
, [Switch]$Console
, [Switch]$EventLog
, [Switch]$Trace
, [Switch]$File
, [Switch]$RollingFile
)
$Script:Logger = [log4net.LogManager]::GetCurrentLoggers() | Where-Object { $_.Logger.Name -Like $LoggerName }
if(!$script:Logger -or $Force -and $LoggerName -ne "*") {
if($LogLevel -eq "VERBOSE") { $LogLevel = "INFO" }
if($LogLevel -eq "HOST") { $LogLevel = "INFO" }
if($LogLevel -eq "WARNING") { $LogLevel = "WARN" }
$AppenderRefs = ''
if($EventLog) { $AppenderRefs += "<appender-ref ref=""EventLogAppender"" />`n" }
if($Trace) { $AppenderRefs += "<appender-ref ref=""TraceAppender"" />`n" }
if($File) { $AppenderRefs += "<appender-ref ref=""FileAppender"" />`n" }
if($RollingFile) { $AppenderRefs += "<appender-ref ref=""RollingFileAppender"" />`n" }
if($Console -or ($AppenderRefs.Length -eq 0)) { $AppenderRefs += "<appender-ref ref=""ColoredConsoleAppender"" />`n" }
$Script:Logger = [log4net.LogManager]::GetLogger($LoggerName)
[xml]$config = @"
<log4net>
<appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
<mapping>
<level value="FATAL" />
<foreColor value="Red, HighIntensity" />
<backColor value="White, HighIntensity" />
</mapping>
<mapping>
<level value="ERROR" />
<foreColor value="Red, HighIntensity" />
</mapping>
<mapping>
<level value="DEBUG" />
<foreColor value="Green, HighIntensity" />
</mapping>
<mapping>
<level value="INFO" />
<foreColor value="Cyan, HighIntensity" />
</mapping>
<mapping>
<level value="WARN" />
<foreColor value="Yellow, HighIntensity" />
</mapping>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="EventLogAppender" type="log4net.Appender.EventLogAppender" >
<applicationName value="$LoggerName" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="TraceAppender" type="log4net.Appender.TraceAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="$LogFolder\$LoggerName.Log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="$LogFolder\$LoggerName.Log" />
<appendToFile value="true" />
<maximumFileSize value="100KB" />
<maxSizeRollBackups value="2" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<root>
<level value="DEBUG" />
</root>
<logger name="$LoggerName">
<level value="$LogLevel" />
$AppenderRefs
</logger>
</log4net>
"@
[log4net.Config.XmlConfigurator]::Configure( $config.log4net )
}
return $Script:Logger
}
function Set-Logger {
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[log4net.Core.LogImpl]$Logger
)
$script:Logger = $Logger
}
function Push-LogContext {
param(
[Parameter(Mandatory=$true)]
[string]$Name
)
[log4net.NDC]::Push($Name)
}
function Pop-LogContext {
[log4net.NDC]::Pop()
}
function Write-DebugLog {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
[Alias('Msg')]
[AllowEmptyString()]
[System.String]
${Message}
,
[Parameter(Mandatory=$false, Position=99)]
${Logger})
begin
{
try {
if($PSBoundParameters.ContainsKey("Logger")) {
if($Logger -is [log4net.Core.LogImpl]) { Set-Logger $Logger } else { $script:Logger = Get-Logger "$Logger" }
$null = $PSBoundParameters.Remove("Logger")
}
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Debug', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd @PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$script:logger.debug($Message) #Write-Debug
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Write-Debug
.ForwardHelpCategory Cmdlet
#>
}
function Write-VerboseLog {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
[Alias('Msg')]
[AllowEmptyString()]
[System.String]
${Message}
,
[Parameter(Mandatory=$false, Position=99)]
${Logger})
begin
{
try {
if($PSBoundParameters.ContainsKey("Logger")) {
if($Logger -is [log4net.Core.LogImpl]) { Set-Logger $Logger } else { $script:Logger = Get-Logger "$Logger" }
$null = $PSBoundParameters.Remove("Logger")
}
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Verbose', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd @PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$script:logger.info($Message)
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Write-Verbose
.ForwardHelpCategory Cmdlet
#>
}
function Write-WarningLog {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
[Alias('Msg')]
[AllowEmptyString()]
[System.String]
${Message}
,
[Parameter(Mandatory=$false, Position=99)]
${Logger})
begin
{
try {
if($PSBoundParameters.ContainsKey("Logger")) {
if($Logger -is [log4net.Core.LogImpl]) { Set-Logger $Logger } else { $script:Logger = Get-Logger "$Logger" }
$null = $PSBoundParameters.Remove("Logger")
}
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Warning', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd @PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$script:logger.warn($Message) #Write-Warning
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Write-Warning
.ForwardHelpCategory Cmdlet
#>
}
function Write-ErrorLog {
[CmdletBinding(DefaultParameterSetName='NoException')]
param(
[Parameter(ParameterSetName='WithException', Mandatory=$true)]
[System.Exception]
${Exception},
[Parameter(ParameterSetName='NoException', Mandatory=$true, Position=0, ValueFromPipeline=$true)]
[Parameter(ParameterSetName='WithException')]
[Alias('Msg')]
[AllowNull()]
[AllowEmptyString()]
[System.String]
${Message},
[Parameter(ParameterSetName='ErrorRecord', Mandatory=$true)]
[System.Management.Automation.ErrorRecord]
${ErrorRecord},
[Parameter(ParameterSetName='NoException')]
[Parameter(ParameterSetName='WithException')]
[System.Management.Automation.ErrorCategory]
${Category},
[Parameter(ParameterSetName='WithException')]
[Parameter(ParameterSetName='NoException')]
[System.String]
${ErrorId},
[Parameter(ParameterSetName='NoException')]
[Parameter(ParameterSetName='WithException')]
[System.Object]
${TargetObject},
[System.String]
${RecommendedAction},
[Alias('Activity')]
[System.String]
${CategoryActivity},
[Alias('Reason')]
[System.String]
${CategoryReason},
[Alias('TargetName')]
[System.String]
${CategoryTargetName},
[Alias('TargetType')]
[System.String]
${CategoryTargetType}
,
[Parameter(Mandatory=$false, Position=99)]
${Logger})
begin
{
try {
if($PSBoundParameters.ContainsKey("Logger")) {
if($Logger -is [log4net.Core.LogImpl]) { Set-Logger $Logger } else { $script:Logger = Get-Logger "$Logger" }
$null = $PSBoundParameters.Remove("Logger")
}
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Error', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd @PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$script:logger.error($Message,$Exception) #Write-Error
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Write-Error
.ForwardHelpCategory Cmdlet
#>
}
function Write-HostLog {
[CmdletBinding()]
param(
[Parameter(Position=0, ValueFromPipeline=$true, ValueFromRemainingArguments=$true)]
[System.Object]
${Object},
[Switch]
${NoNewline},
[System.Object]
${Separator} = $OFS,
[System.ConsoleColor]
${ForegroundColor},
[System.ConsoleColor]
${BackgroundColor}
,
[Parameter(Mandatory=$false, Position=99)]
${Logger})
begin
{
try {
if($PSBoundParameters.ContainsKey("Logger")) {
if($Logger -is [log4net.Core.LogImpl]) { Set-Logger $Logger } else { $script:Logger = Get-Logger "$Logger" }
$null = $PSBoundParameters.Remove("Logger")
}
$outBuffer = $null
if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
{
$PSBoundParameters['OutBuffer'] = 1
}
$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Write-Host', [System.Management.Automation.CommandTypes]::Cmdlet)
$scriptCmd = {& $wrappedCmd @PSBoundParameters }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
} catch {
throw
}
}
process
{
try {
$script:logger.info(($Object -join $Separator)) #Write-Verbose
$steppablePipeline.Process($_)
} catch {
throw
}
}
end
{
try {
$steppablePipeline.End()
} catch {
throw
}
}
<#
.ForwardHelpTargetName Write-Host
.ForwardHelpCategory Cmdlet
#>
}
Set-Alias Write-Debug Write-DebugLog
Set-Alias Write-Verbose Write-VerboseLog
Set-Alias Write-Warning Write-WarningLog
Set-Alias Write-Error Write-ErrorLog
#Set-Alias Write-Host Write-HostLog
Export-ModuleMember -Function * -Alias *
$script:Logger = Get-Logger "PowerShellLogger" -RollingFile -LogFolder (Split-Path $Profile.CurrentUserAllHosts)
## THIS IS THE DEFAULT LOGGER. CONFIGURE AS YOU SEE FIT