Logging Artikel

Eigene Loggingfunktionen mit PowerShell sinnvoll aufbauen.

Die Kombination aus einer String-Normalisierung und einer klaren Log-Schreibfunktion ist fuer Paketierung sehr wertvoll. Sie macht Logs robuster, lesbarer und besser fuer spaetere Auswertung geeignet.

Warum diese Kombination stark ist

Convert-ToLogSafeString entschraerft problematische Zeichen und Steuerzeichen. Write-Log schreibt daraus einen konsistenten Logeintrag mit Zeit, Datum, Kontext, Thread und Komponente.

Wofuer sich das eignet

Besonders fuer Deploymentskripte, Prechecks, Detection-Skripte, Wrapper-Logs und alle Faelle, in denen spaeter mehrere Logquellen miteinander korreliert werden muessen.

Code

Convert-ToLogSafeString

Diese Funktion bereinigt problematische Zeichen fuer Logausgaben, reduziert Mehrfach-Whitespace und verhindert, dass ein spezielles Log-Muster den Zeilenaufbau zerlegt.

PowerShell
function Convert-ToLogSafeString {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [string]$InputString
    )

    if ($null -eq $InputString) {
        return ''
    }

    if ($InputString.Length -eq 0) {
        return ''
    }

    $sanitized = $InputString
    $sanitized = $sanitized.Replace('ä', 'ae')
    $sanitized = $sanitized.Replace('ö', 'oe')
    $sanitized = $sanitized.Replace('ü', 'ue')
    $sanitized = $sanitized.Replace('Ä', 'Ae')
    $sanitized = $sanitized.Replace('Ö', 'Oe')
    $sanitized = $sanitized.Replace('Ü', 'Ue')
    $sanitized = $sanitized.Replace('ß', 'ss')

    $sanitized = $sanitized.Replace('„', '"')
    $sanitized = $sanitized.Replace('“', '"')
    $sanitized = $sanitized.Replace('‚', "'")
    $sanitized = $sanitized.Replace('‘', "'")
    $sanitized = $sanitized.Replace('’', "'")
    $sanitized = $sanitized.Replace('–', '-')
    $sanitized = $sanitized.Replace('—', '-')
    $sanitized = $sanitized.Replace('…', '...')

    $sanitized = $sanitized.Replace("`r", ' ')
    $sanitized = $sanitized.Replace("`n", ' ')
    $sanitized = $sanitized.Replace("`t", ' ')

    $sanitized = [regex]::Replace($sanitized, '[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '')
    $sanitized = [regex]::Replace($sanitized, '\s{2,}', ' ')
    $sanitized = $sanitized.Replace(']LOG]!>', ']LOG]!_')
    $sanitized = $sanitized.Trim()

    return $sanitized
}

Code

Write-Log

Die Funktion setzt auf die Normalisierung auf und schreibt strukturierte Eintraege mit Typ-Mapping fuer Info, Warning und Error.

PowerShell
function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Path,

        [Parameter(Mandatory = $true)]
        [AllowNull()]
        [string]$Message,

        [Parameter(Mandatory = $true)]
        [string]$Component,

        [Parameter(Mandatory = $true)]
        [ValidateSet("Info", "Warning", "Error")]
        [string]$Type
    )

    switch ($Type) {
        'Info'    { [int]$LogType = 1 }
        'Warning' { [int]$LogType = 2 }
        'Error'   { [int]$LogType = 3 }
    }

    $SafeMessage   = Convert-ToLogSafeString -InputString $Message
    $SafeComponent = Convert-ToLogSafeString -InputString $Component

    $Content = "<![LOG[$SafeMessage]LOG]!>" +
        "<time=`"$(Get-Date -Format 'HH:mm:ss.ffffff')`" " +
        "date=`"$(Get-Date -Format 'yyyy-MM-dd')`" " +
        "component=`"$SafeComponent`" " +
        "context=`"$([System.Security.Principal.WindowsIdentity]::GetCurrent().Name)`" " +
        "type=`"$LogType`" " +
        "thread=`"$([Threading.Thread]::CurrentThread.ManagedThreadId)`" " +
        "file=`"`">"

    $logFolder = Split-Path -Path $Path -Parent
    if ($logFolder -and -not (Test-Path -Path $logFolder)) {
        New-Item -Path $logFolder -ItemType Directory -Force | Out-Null
    }

    Add-Content -Path $Path -Value $Content -Encoding utf8
}

Was die Funktionen gut machen

  • Null- und Leerwerte sauber behandeln
  • Umlaute und typografische Sonderzeichen fuer Logs vereinheitlichen
  • Zeilenumbrueche und Tabs auf eine Zeile bringen
  • Kontext, Thread und Komponente in jeden Eintrag aufnehmen
  • Logordner automatisch anlegen

Warum das in der Praxis hilft

  • Weniger kaputte Logzeilen durch Sonderzeichen
  • Bessere Vergleichbarkeit zwischen Hosts und Benutzern
  • Einheitliches Format fuer Deployment, Detection und Troubleshooting
  • Bessere Lesbarkeit in Logviewern oder bei Textsuche

Einordnung

Worauf man beim Veröffentlichen noch hinweisen sollte

Format-Ziel klar benennen

Der Logeintrag orientiert sich an einem CMTrace-kompatiblen Muster. Das ist wichtig, damit Leser sofort verstehen, warum etwa ]LOG]!> speziell behandelt wird.

Normalisierung ist bewusst verlustbehaftet

Die Funktion macht Logs robuster, ersetzt aber Zeichen absichtlich. Das ist fuer Betrieb und Fehlersuche oft sinnvoller als perfekte Originaltreue.

Komponentenbezeichnung sauber halten

Component sollte nicht frei schwanken. Feste Werte wie Detection, Install, Precheck oder Uninstall machen spaetere Auswertung viel besser.

Moegliche Weiterentwicklung

Als naechste Ausbaustufe koenntest du Dateigroesse/Rotation, optionales Console-Output, LogLevel-Filter oder einen Parameter fuer CMTrace-kompatible Dateinamen ergaenzen.

Erweiterungsartikel lesen

CMTrace-kompatibles Format verstehen

Wenn du Leser nicht nur den Code sehen lassen willst, sondern auch das dahinterliegende Logformat erklaeren moechtest, lohnt sich eine eigene Einordnung des CMTrace-kompatiblen Aufbaus.

Artikel lesen

Von einfacher Funktion zu Betriebswerkzeug

Sobald eine Loggingfunktion dauerhaft im produktiven Einsatz laeuft, werden Rotation, LogLevel und kontrollierter Umgang mit Dateigroesse sehr relevant.

Artikel lesen
PowerShell
$logPath = 'C:\Logs\Packaging\deploy.log'

Write-Log -Path $logPath -Component 'Precheck' -Type Info -Message 'Starte Vorpruefung fuer App X'
Write-Log -Path $logPath -Component 'Install' -Type Warning -Message 'Alte Version wurde gefunden und wird entfernt'
Write-Log -Path $logPath -Component 'Detection' -Type Error -Message 'Dateiversion konnte nicht gelesen werden'