Public/Core/Write-Log.ps1

<#
.SYNOPSIS
   The IDBridge logging function: timestamped entries to the log file, the in-memory buffer, and the console.
.DESCRIPTION
   Every IDBridge log line goes through Write-Log. Each message is written to three places:
   the log file (Paths.LogFile by default, created if missing and rotated by
   Initialize-IDBridge past 5 MB), the in-memory buffer read via Get-IDBridgeLogs (used by
   Push-LogsToSheet for Google Sheet logging), and the console (Error/Warn map to the
   error/warning streams; Info/Trace to the verbose stream).
 
   Trace-level messages are dropped entirely unless Debug.TraceLogging is $true. Requires an
   initialized session (Initialize-IDBridge) — the config supplies the default log path.
.PARAMETER Message
   The message to log (alias LogContent). Accepts pipeline input by property name.
.PARAMETER Path
   The log file to write to (alias LogPath). Defaults to Paths.LogFile from the config.
   The path and file are created if they do not exist.
.PARAMETER Level
   Severity: Error, Warn, Info (default), or Trace. Trace is suppressed unless
   Debug.TraceLogging is enabled.
.PARAMETER NoClobber
   Throw instead of writing when the log file already exists.
.EXAMPLE
   Write-Log -Message 'AD: Fetching Users' -Level Trace
.EXAMPLE
   Write-Log -Message ($_.Exception.Message) -Level Error
.NOTES
   Based on Write-Log by Jason Wasser @wasserja; adapted for IDBridge (Trace level,
   in-memory buffer, config-derived default path).
.LINK
   https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0
#>

function Write-Log { 
    [CmdletBinding()] 
    Param 
    ( 
        [Parameter(Mandatory=$true, 
            ValueFromPipelineByPropertyName=$true)] 
        [ValidateNotNullOrEmpty()] 
        [Alias("LogContent")] 
        [string]$Message, 
 
        [Parameter(Mandatory=$false)]
        [Alias('LogPath')]
        [string]$Path,
         
        [Parameter(Mandatory=$false)] 
        [ValidateSet("Error","Warn","Info","Trace")] 
        [string]$Level="Info", 
         
        [Parameter(Mandatory=$false)] 
        [switch]$NoClobber
    ) 
 
    Begin 
    { 
        # Set VerbosePreference to Continue so that verbose messages are displayed.
        $VerbosePreference = 'Continue' 

        #Import Configuration
        try { $IDConfig = Get-IDBridgeConfig } catch { Throw $_ }

        # Apply Trace override if specified. This allows us to write trace messages without enabling verbose logging for the entire script.
        if ($Level -eq "Trace" -and (-not $IDConfig.Debug.TraceLogging -or $IDConfig.Debug.TraceLogging -eq $false)) {
            $skip = $true
            return
        }

        if ([string]::IsNullOrWhiteSpace($Path)) {
            $Path = $IDConfig.Paths.LogFile
        }

        # If the file already exists and NoClobber was specified, do not write to the log.
        if ((Test-Path $Path) -and $NoClobber) {
            throw "Log file $Path already exists and NoClobber was specified."
        }

        # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path.
        if (-not (Test-Path $Path)) {
            Write-Verbose "Creating $Path." 
            New-Item $Path -Force -ItemType File | Out-Null
        }

        # In-memory log buffer for this run (e.g. for Google Sheet logging) so we
        # don't have to re-read the file. Ordered, append-only, one object per entry.
        if ($null -eq $script:Logs) {
            $script:Logs = [System.Collections.Generic.List[PSCustomObject]]::new()
        }

    } 
    Process 
    { 
        # If verbose logging is disabled and the log level is set to Info or Trace, skip writing to the log file. This allows us to write Info messages without enabling verbose logging for the entire script.
        if ($skip) { return }

        # Format Date for our Log File
        $MessageTime = Get-Date
        $FormattedDate = $MessageTime.ToString("yyyy-MM-dd HH:mm:ss")
 
        # Write message to error, warning, or verbose pipeline and specify $LevelText
        switch ($Level) { 
            'Error' { 
                Write-Error $Message 
                $LevelText = 'ERROR:' 
            } 
            'Warn' { 
                Write-Warning $Message 
                $LevelText = 'WARNING:' 
            } 
            'Info' { 
                $LevelText = 'INFO:' 
                Write-Verbose "$($LevelText) $($Message)"
            } 
            'Trace' {
                $LevelText = 'TRACE:'
                Write-Verbose "$($LevelText) $($Message)"
            }
        }

        # Add to log variable for potential use elsewhere in the script (e.g. for Google Sheet logging) without having to read from the log file.
        $script:Logs.Add([PSCustomObject]@{
            Timestamp = $FormattedDate
            Level     = $Level
            Message   = $Message
        })
         
        # Write log entry to $Path
        "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append 
    } 
    End { } 
}