SmartLogging.psm1

Add-Type -TypeDefinition @"
    public enum LogLevel
    {
        Trc = 0,
        Dbg = 1,
        Inf = 2,
        Wrn = 3,
        Err = 4,
        Cri = 5,
        Non = 6
    }
"@


$defaultLogLevel = [LogLevel]::Inf
$defaultLogLevelString = @{
    Short = 'inf'
    Long  = 'info'
    Color = ([ConsoleColor]::White)
}
$consoleLogDateFormat = 'HH:mm:ss'
$fileLogDateFormat = 'yyyy-MM-ddTHH:mm:ss zzz'
$fileLogger = @{}

function GetLogLevel($logLevel) {
    switch ($logLevel) {
        ( { $logLevel -ieq 'trace' -or $logLevel -ieq 'trc' }) { return [LogLevel]::Trc }
        ( { $logLevel -ieq 'debug' -or $logLevel -ieq 'dbg' }) { return [LogLevel]::Dbg }
        ( { $logLevel -ieq 'info' -or $logLevel -ieq 'inf' }) { return [LogLevel]::Inf }
        ( { $logLevel -ieq 'warning' -or $logLevel -ieq 'wrn' }) { return [LogLevel]::Wrn }
        ( { $logLevel -ieq 'error' -or $logLevel -ieq 'err' }) { return [LogLevel]::Err }
        ( { $logLevel -ieq 'critic' -or $logLevel -ieq 'cri' }) { return [LogLevel]::Cri }
        ( { $logLevel -ieq 'none' -or $logLevel -ieq 'non' }) { return [LogLevel]::Non }
        Default { return $defaultLogLevel }
    }
}

function GetLogLevelInfo($logLevel) {
    switch ($logLevel) {
        ([LogLevel]::Trc) {
            return @{
                Short = 'trc'
                Long  = 'trace'
                Color = ([ConsoleColor]::DarkGray)
            }
        }
        ([LogLevel]::Dbg) {
            return @{
                Short = 'dbg'
                Long  = 'debug'
                Color = ([ConsoleColor]::DarkGray)
            }
        }
        ([LogLevel]::Inf) {
            return @{
                Short = 'inf'
                Long  = 'info'
                Color = ([ConsoleColor]::White)
            }
        }
        ([LogLevel]::Wrn) {
            return @{
                Short = 'wrn'
                Long  = 'warning'
                Color = ([ConsoleColor]::Yellow)
            }
        }
        ([LogLevel]::Err) {
            return @{
                Short = 'err'
                Long  = 'error'
                Color = ([ConsoleColor]::Red)
            }
        }
        ([LogLevel]::Cri) {
            return @{
                Short = 'cri'
                Long  = 'critical'
                Color = ([ConsoleColor]::Red)
            }
        }
        ([LogLevel]::Non) {
            return @{
                Short = 'non'
                Long  = 'none'
                Color = ([ConsoleColor]::White)
            }
        }
        Default { return $defaultLogLevelString }
    }
}

function GetUserLogLevel() {
    $userLogLevel = $env:LOGLEVEL
    if (-not $userLogLevel) {
        $userLogLevel = $defaultLogLevel
    }

    return (GetLogLevel $userLogLevel)
}

function ShouldLog($userLogLevel, $logLevel) {
    switch ($userLogLevel) {
        ( { ($logLevel.value__ -ge $userLogLevel.value__) -and ($userLogLevel -ne [LogLevel]::Non) -and ($logLevel -ne [LogLevel]::Non) }) { return $true }
        Default { return $false }
    }
}

function LogConsole($logLevelInfo, $value) {
    Write-Host "[$(Get-Date -Format $consoleLogDateFormat) " -NoNewline -ForegroundColor ([ConsoleColor]::DarkGray)

    Write-Host "$($logLevelInfo.Short.ToUpper())" -NoNewline -ForegroundColor $logLevelInfo.Color
    Write-Host "] " -NoNewline -ForegroundColor ([ConsoleColor]::DarkGray)
    Write-Host $value -ForegroundColor ([ConsoleColor]::White)
}

function LogFile($logLevelInfo, $value) {
    # Check logs folder and create when not exists
    $date = '{0:yyyy-MM-dd}' -f (Get-Date)
    if (-not ( Test-Path -Path $fileLogger.Dir -PathType Container)) {
        New-Item -Path $fileLogger.Dir -ItemType Container > $null
    }

    # Create log file in format '{current_date}_[${filelogger.FileName}].log'
    if ($fileLogger.FileName) {
        $logfile = "$date`_$($fileLogger.FileName).log"
    } else {
        $logfile = "$date.log"
    }

    $logFilePath = Join-Path $fileLogger.Dir $logfile

    $item = "[$(Get-Date -Format $fileLogDateFormat) "
    $item += "$($logLevelInfo.Long.ToUpper())]".PadRight(9)
    $item += " "
    $item += $value

    Add-Content -Path $logFilePath -Value "$item" -Encoding ascii > $null
}

function Log($LogLevel, $Value) {
    # When only one argument, use it as $Value with default $LogLevel
    if (-not $Value) {
        $Value = $LogLevel
        $level = $defaultLogLevel
    } else {
        $level = GetLogLevel $LogLevel
    }

    if (-not (ShouldLog (GetUserLogLevel) $level)) {
        return
    }

    $logLevelInfo = GetLogLevelInfo $level

    LogConsole $logLevelInfo $Value
    if ($fileLogger.IsEnabled) {
        LogFile $logLevelInfo $Value
    }
}

function Set-FileLogger($Dir = 'logs', $FileName = '') {
    $Script:fileLogger.IsEnabled = $true
    $Script:fileLogger.Dir = $Dir
    $Script:fileLogger.FileName = $FileName
}

function Remove-FileLogger() {
    Set-FileLogger
    $Script:fileLogger.IsEnabled = $false
}

function Write-Status {
    [CmdletBinding()]
    param (
        [int] $Current,
        [int] $Total,
        [string] $Statustext,
        [string] $CurrentStatusText,
        [int] $ProgressbarLength = 35
    )

    # Save current Cursorposition for later
    [int]$XOrg = $Host.UI.RawUI.CursorPosition.X

    # Create Progressbar
    [string]$progressbar = ""
    for ($i = 0 ; $i -lt $([System.Math]::Round($(([System.Math]::Round(($($Current) / $Total) * 100, 2) * $ProgressbarLength) / 100), 0)); ++$i) {
        $progressbar = $progressbar + $([char]9608)
    }

    for ($i = 0 ; $i -lt ($ProgressbarLength - $([System.Math]::Round($(([System.Math]::Round(($($Current) / $Total) * 100, 2) * $ProgressbarLength) / 100), 0))); ++$i) {
        $progressbar = $progressbar + $([char]9617)
    }

    # Overwrite Current Line with the current Status
    Write-Host -NoNewline "`r$Statustext $progressbar [$($Current.ToString("#,###").PadLeft($Total.ToString("#,###").Length)) / $($Total.ToString("#,###"))] ($($(($Current / $Total) * 100).ToString("##0.00").PadLeft(6)) %) $CurrentStatusText"

    # There might be old Text behing the current Currsor, so let's write some blanks to the Position of $XOrg
    [int]$XNow = $Host.UI.RawUI.CursorPosition.X
    for ([int]$i = $XNow; $i -lt $XOrg; ++$i) {
        Write-Host -NoNewline " "
    }

    # Just for optical reasons: Go back to the last Position of current Line
    for ([int]$i = $XNow; $i -lt $XOrg; ++$i) {
        Write-Host -NoNewline "`b"
    }
}