Private/Write-Log.ps1

#
# Function: Logging with Console Output (thread safe)
#
# This function will log messages to the console and a log file.
# It uses a global lock object to ensure thread safety.
# Input parameters:
# - Message: The message to log.
# - Level: The log level (ERROR, WARNING, INFO, DEBUG, SUCCESS). Default is INFO.
# - Color: Overrides the level's default console color (used for custom-formatted rows/tables).
# - NoPrefix: Skips the "[timestamp] [Level]" prefix (used for pre-formatted lines like table rows).
function Write-Log
{
    param (
        [string]$Message,
        [ValidateSet("ERROR", "WARNING", "INFO", "DEBUG", "SUCCESS")]
        [string]$Level = "INFO",
        [System.ConsoleColor]$Color,
        [switch]$NoPrefix
    )

    # Determine the index of the current log level and the message's log level for filtering purposes.
    $levels = @("ERROR", "WARNING", "INFO", "DEBUG")

    # Get the numeric index of the current log level and the message's log level.
    $currentIndex = $levels.IndexOf($script:LogLevel.ToUpper())
    $messageIndex = $levels.IndexOf($Level.ToUpper())

    # Acquire the global lock to ensure thread-safe logging.
    [System.Threading.Monitor]::Enter($script:LogLockObject)
    try
    {
        # Generate the timestamp and construct the log message with or without the prefix.
        $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        $logMessage = if ($NoPrefix) { $Message } else { "[$timestamp] [$Level] $Message" }

        # Check if the message should be logged based on the current log level and the message's log level.
        # The same filter applies to the console and the log file, so the file mirrors what the console shows.
        if ($messageIndex -le $currentIndex -or $Level -eq "SUCCESS")
        {
            $consoleColor = if ($PSBoundParameters.ContainsKey('Color')) {
                $Color
            }
            else
            {
                switch ($Level)
                {
                    "ERROR" { "Red" }
                    "WARNING" { "Yellow" }
                    "INFO" { "Gray" }
                    "DEBUG" { "Cyan" }
                    "SUCCESS" { "Green" }
                }
            }
            Write-Host $logMessage -ForegroundColor $consoleColor

            # Write the log message to the log file if a log file path is specified.
            if ($script:LogFilePath)
            {
                $fileStream = [System.IO.File]::Open($script:LogFilePath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [System.IO.FileShare]::Read)
                $streamWriter = New-Object System.IO.StreamWriter($fileStream)
                try
                {
                    $streamWriter.WriteLine($logMessage)
                }
                finally
                {
                    $streamWriter.Close()
                    $fileStream.Close()
                }
            }
        }
    }
    finally
    {
        [System.Threading.Monitor]::Exit($script:LogLockObject)
    }
}