ulog.psm1


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
   }
"@
 -ErrorAction SilentlyContinue


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.
.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           = $true,
    [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 = @{}
    if (($Handler.Count -eq 0) -xor ($NoDefaultHandler.IsPresent -eq $true)){
        if ($Source.Contains('.') -and $Source.Substring( $Source.LastIndexOf('.')) -eq '.psm1'){
            #$handlers = @((New-uLogEventLog -Source $Source -Name 'EventLog'))
            $handlers.Add('EventLog' , (New-uLogEventLog -Source $Source -Name 'EventLog'))
        }else{
            #$handlers = @((New-uLogConsole -Source $Source -Name 'Console'))
            $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
        }
    }
    
    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
}
}


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,
    [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 
        }        
    }
    
    # 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 fucntion 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,
    [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
}
 
"@


        $func
    }
}


function Log-Trace{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $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
}

function Log-Verbose{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $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
}

function Log-Debug{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $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
}

function Log-Success{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $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
}

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

function Log-Information{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $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
}

function Log-Warn{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $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
}

function Log-Warning{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $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
}

function Log-Error{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $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
}

function Log-Critical{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $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
}

function Log-Fatal{
param(
    [string]   $Message,
    [int]      $Id                  = 1,
    [string]   $Path                = $null,
    [switch]   $Append              = $false,
    [switch]   $NoDisplayOnTerminal = $false,
    [Object[]] $Exclude             = $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
}