uLog.psm1

<#
Try/cath to avaoid error when recreating the type in case we relaunch the script
 
LogFormatter is for compatibility purpose, created at first.
It will be removed in a later version
 
#>


try{
Add-Type -TypeDefinition @"
   public enum LogLevel
   {
      FATAL = 701,
      CRITICAL = 700,
      ERROR = 600,
      WARNING = 501,
      WARN = 500,
      INFORMATION = 401,
      INFO = 400,
      SUCCESS = 300,
      DEBUG = 201,
      VERBOSE = 200,
      TRACE = 100
   }
"@
 
}catch{}

try{
Add-Type -TypeDefinition @"
   public enum uLogLevel
   {
      FATAL = 701,
      CRITICAL = 700,
      ERROR = 600,
      WARNING = 501,
      WARN = 500,
      INFORMATION = 401,
      INFO = 400,
      SUCCESS = 300,
      DEBUG = 201,
      VERBOSE = 200,
      TRACE = 100
   }
"@
 
}catch{}

function Get-Header{
    [PSCustomObject] @{
        ComputerName      = $env:COMPUTERNAME;
        UserName          = $env:USERNAME;
        PowerShellVersion = $PSVersionTable.PSVersion.ToString()
    }
}

<#
.Synopsis
   Instantiates a new log manager
.DESCRIPTION
   Creates a new logger object.
   If no handler is provided, a logger with a console handler is created
   if a script instantiated the logger. It adds an eventlog handler
   if a module instantiated the logger.
    
   Check the test folder in the module folder for some more example.
     
.PARAMETER Append
    Default is true, so the events are appended to the log handlers.
 
.PARAMETER Handler
    List of handlers to pass the the log manager.
 
.PARAMETER NoDefaultHandler
    If specified, no default handler are created, you need to manually add
    handler with -Handler of after the create with AddHandler().
 
.PARAMETER NoHeader
    If specified, no header is written to the handlers.
    The header consist of messages containing username, computername and
    PowerShell version.
 
.PARAMETER Source
    Specify the source of the handler, which can be a file, eventlog name ...
 
.EXAMPLE
   $log = New-uLog
   Log-Info -Message 'Hello'
   This example creates a new log manager and write an information message
   on the console
.EXAMPLE
   $log = New-uLog
   $log.AddLogProvider( (New-uLogEventLog) )
   Log-Info -Message 'Hello'
   This example creates a new log manager, adds a evenlog provider
   the infoemtion message is
#>

function New-uLog
{
param(    
    [switch]   $Append,
    [Object[]] $Handler          = $null,
    [switch]   $NoDefaultHandler,
    [switch]   $NoHeader,
    [string]   $Source           = $MyInvocation.ScriptName
)
Begin{
    # if instanciated from the command line or editor, $MyInvocation.ScriptName
    # is empty, so we configure the source to 'Console'
    if ($Source -eq ''){$Source = 'Console'}
}
Process{
    $global:LOGAPPEND = $Append 
    $handlers = @{} # a dictionary is refered over an array so handlers can be
                    # choosen with autocompletion

    # if the script is run from a psm1 file, the default handler is eventlog
    # else default handler is the console
    if (($Handler.Count -eq 0) -xor ($NoDefaultHandler.IsPresent -eq $true)){
        if ($Source.Contains('.') -and $Source.Substring( $Source.LastIndexOf('.')) -eq '.psm1'){
            $handlers.Add('EventLog' , (New-uLogEventLog -Source $Source -Name 'EventLog'))
        }else{
            $handlers.Add('Console', (New-uLogConsole -Source $Source -Name 'Console'))
        }
    }else{        
        if ($Handler.Count -ne 0){
            $Handler | % {
                $handlers.Add($_.Name, $_)
            }
        }
    }

    $log = [PSCustomObject] @{Source    = $Source;
                              Handlers  = $handlers
                             }

    $log | Add-Member -MemberType ScriptMethod -Name AddLogHandler -Value {
        param($handler)
        $this.Handlers.Add($handler.Name, $handler)
        if ($handler.Name -ne ''){
            $this |Add-Member -MemberType NoteProperty -Name $handler.Name -Value $handler
        }
    }
    
    $log | Add-Member -MemberType ScriptMethod -Name ForceLogLevel -Value {
        param([Parameter(Mandatory=$true)]
              $handler, 
              [Parameter(Mandatory=$true)]
              $Level)
        $this.Handlers | % {
            $_.Level = $Level
        }
        
    }

    $log | Add-Member -MemberType ScriptMethod -Name Close -Value {
        # flush all handlers
        foreach ($h in $this.Handlers.Values){
            # we try to flush the handler, for those who implement this function
            #TOFIX: check the methods with gm ? before using flush ?
            try{                
                $h.Flush()
            }catch{}
        }

    }

    if (-not $NoHeader.IsPresent){
        if ($log.Handlers.Count -gt 0){
            $log.Handlers.Values | % {
                $hd = $_
                $head = Get-Header
                ($head | gm -MemberType NoteProperty).Name | % {
                    $prop = $_                
                    $hd.WriteLog((New-LogRecord -Message "$prop : $($head.$prop)" -Level ([loglevel]::INFO)))
                }
                $hd.WriteLog((New-LogRecord -Message "Source : $Source" -Level ([loglevel]::INFO)))
                                
                if (($hd | gm -MemberType Properties | ? {$_.Name -EQ 'Path'}) -ne $null){
                    $hd.WriteLog((New-LogRecord -Message "Path : $($hd.Path)" -Level ([loglevel]::INFO)))
                }
            }
        }
    }
    
    $global:uLOG = $log
    $global:uLOG
}
End {
    $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
        $global:uLOG.Close()
    }    
}
}


function New-LogRecord
{
param(
    [string] $Message,
    [string] $Level,
    [int]    $Id     = 10000,
    [int]    $Indent = 1
)
    [PSCustomObject] @{Message = $Message;
                       Level   = [LogLevel] $Level;
                       Id      = $Id;
                       Indent  = $Indent
                       }
}

function Write-Log
{
param(
    [string] $Message,
    [ValidateSet('INFO', 'INFORMATION', 'WARN', 'WARNING', 'ERROR', 'FATAL', 'CRITICAL', 'DEBUG', 'TRACE', 'SUCCESS', 'VERBOSE')] 
    $Level,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null,
    [string]   $Source              = ''
)

    $record = New-LogRecord -Message $Message -Level $Level -Id $Id -Indent $Indent

    if ($Log -eq $null){ 
        
        Get-Variable -Name uLog -Scope Global -ErrorAction SilentlyContinue | Out-Null
        if($?){
            $Log = $Global:uLog
        }else{
            if ($Source -eq ''){ 
                if ($MyInvocation.ScriptName -eq ''){
                    $source = $MyInvocation.ScriptName 
                }else{
                    $source = 'Console'
                }
            }

            $Log = New-uLog -Source $Source 
        }        
    }
    
    if ($Include -ne $null){
        # if Include is present, we take only Include handlers
        $handlers = $log.Handlers.Values | ? { $_ -in $Include} 
    }else{        
        # if Include not sp�cified, and we remove the excluded handlers
        $handlers = $log.Handlers.Values | ? { $_ -notin $Exclude} 
    }
    # if no display on terminal, we remove console handlers
    if ($NoDisplayOnTerminal -eq $true){
        $handlers = $handlers | ? {$_.Type -ne 'Console'}
    }

    # we loop through handlers to write the message
    $handlers | % {
        $_.WriteLog($record)
    }

}

<#
Helper function to produce the log-* functions
We can dynamically create fucntions, but it does not work properlly with Intellisense
So we generate the code to copy/paste in the modul with this fucntio
 
For each level of log, we create a log-* function associated with it.
These fucntions are more ergonomic than the Write-Log fonction.
 
To create the functions, we loop through the enum of levels and we dynamically
create the fonctions.
It is more convenient than copy/paste functions definitions, il a new level is
needed, juste add it to the enum and it will be taken into account
#>

function Start-BuildFunctions{
    [LogLevel].GetEnumNames() | % {

# We convert the level name to camel case, which is morre beautiful
$camelCase = $_.Substring(0,1).ToUpper() + $_.Substring(1).ToLower()

$func = @"
function Log-$camelCase{
param(
    [string] `$Message,
    [int] `$Id = 1,
    [string] `$Path = `$null,
    [switch] `$Append = `$false,
    [switch] `$NoDisplayOnTerminal = `$false,
    [Object[]] `$Exclude = `$null,
    [Object[]] `$Include = `$null,
    [int] `$Indent = 1,
    [Object] `$Log = `$null
)
    `$Source = `$MyInvocation.ScriptName
    Write-Log -Message `$Message ``
              -Level $_ ``
              -Id `$Id ``
              -Append `$Append ``
              -NoDisplayOnTerminal:`$NoDisplayOnTerminal ``
              -Indent `$Indent ``
              -Log `$Log ``
              -Source `$Source ``
              -Exclude `$Exclude `
              -Include `$Include
}
 
"@


        $func
    }
}


function Log-Trace{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)
    $Source = $MyInvocation.ScriptName
    Write-Log -Message $Message `
              -Level TRACE `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source `
              -Exclude $Exclude `
              -Include $Include
}

function Log-Verbose{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)
    $Source = $MyInvocation.ScriptName
    Write-Log -Message $Message `
              -Level VERBOSE `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source `
              -Exclude $Exclude `
              -Include $Include
}

function Log-Debug{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)
    $Source = $MyInvocation.ScriptName
    Write-Log -Message $Message `
              -Level DEBUG `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source `
              -Exclude $Exclude `
              -Include $Include
}

function Log-Success{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)
    $Source = $MyInvocation.ScriptName
    Write-Log -Message $Message `
              -Level SUCCESS `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source `
              -Exclude $Exclude `
              -Include $Include
}

function Log-Info{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)

    $Source = $MyInvocation.ScriptName

    $arguments = @{}
    if ($Exclude -ne $null){$arguments['Exclude'] = $Exclude}
    if ($Include -ne $null){$arguments['Include'] = $Include}

    Write-Log -Message $Message `
              -Level INFO `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source @arguments
              
}

function Log-Information{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)
    $Source = $MyInvocation.ScriptName

    Write-Log -Message $Message `
              -Level INFORMATION `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source `
              -Exclude $Exclude `
              -Include $Include
}

function Log-Warn{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)
    $Source = $MyInvocation.ScriptName
    Write-Log -Message $Message `
              -Level WARN `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source `
              -Exclude $Exclude `
              -Include $Include
}

function Log-Warning{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)
    $Source = $MyInvocation.ScriptName
    Write-Log -Message $Message `
              -Level WARNING `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source `
              -Exclude $Exclude `
              -Include $Include
}

function Log-Error{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)
    $Source = $MyInvocation.ScriptName
    Write-Log -Message $Message `
              -Level ERROR `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source `
              -Exclude $Exclude `
              -Include $Include
}

function Log-Critical{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)
    $Source = $MyInvocation.ScriptName
    Write-Log -Message $Message `
              -Level CRITICAL `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source `
              -Exclude $Exclude `
              -Include $Include
}

function Log-Fatal{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $null,
    [Object[]] $Include             = $null,
    [int]      $Indent              = 1,
    [Object]   $Log                 = $null
)
    $Source = $MyInvocation.ScriptName
    Write-Log -Message $Message `
              -Level FATAL `
              -Id $Id `
              -Append $Append `
              -NoDisplayOnTerminal:$NoDisplayOnTerminal `
              -Indent $Indent `
              -Log $Log `
              -Source $Source `
              -Exclude $Exclude `
              -Include $Include
}