Create a PowerShell Log Buffer

Introduction to the PowerShell Log Buffer Script

This PowerShell script enhances logging performance by temporarily storing logs in a PowerShell log buffer and periodically writing them to a log file. By minimizing the frequency of disk I/O operations, this script ensures more efficient logging, which is particularly useful in scenarios requiring extensive log generation.

The script effectively manages log data, ensuring system performance remains optimal even with high volumes of logging activity.

Pros:

  • Improved performance
  • Reduced disk I/O operations
  • Thread-safe logging

Cons:

  • Slight delay in log availability

Download Link: PowerShell Buffer Script


Detailed Explanation

Initialization

The script starts by setting up a concurrent queue to store log messages temporarily, a timer to periodically trigger the writing of logs, and a lock object to ensure thread safety during the log writing process. It also defines the path for the log file and ensures the directory exists.

BEGIN {
    Clear-Host

    # Create a buffer to store log messages temporarily
    $script:logBuffer = [System.Collections.Concurrent.ConcurrentQueue[string]]::new()

    # Create a timer with a 5-second interval
    $script:timer = New-Object Timers.Timer
    $script:timer.Interval = 5000 # 5000 milliseconds = 5 seconds

    # Define the log file path
    $logDir = "C:\Windows\Logs"
    if (-not (Test-Path $logDir)) {
        New-Item -ItemType Directory -Path $logDir -Force
    }
    $script:Logfile = "$logDir\log_name.log"

    # Create a lock object for thread safety
    $script:lockObject = New-Object System.Object
}

In this initialization block:

  • $script:logBuffer is a concurrent queue used to temporarily store log messages.
  • $script:timer is set to trigger every 5 seconds to manage log writing.
  • $logDir ensures the log directory exists, creating it if necessary.
  • $script:lockObject is used to synchronize access to the log file, ensuring thread safety.

Logging Function: LogWrite

The LogWrite function is used to add log messages to the buffer. It prepends each log message with a timestamp for accurate tracking.

# Function to add a log message to the buffer
Function LogWrite {
    Param ([string]$logstring)
    $logstring = (Get-Date -Format "MM-dd-yyyy - HH:mm:ss.fff") + " | $logstring"

    # Add the log message to the buffer
    $script:logBuffer.Enqueue($logstring)
}

In this function:

  • Get-Date -Format "MM-dd-yyyy - HH:mm:ss.fff" generates a timestamp for each log entry.
  • $script:logBuffer.Enqueue($logstring) adds the timestamped log message to the buffer.

Buffer Writing Function: WriteLogsBuffer

The WriteLogsBuffer function is responsible for writing the buffered log messages to the log file. It ensures thread safety using a lock.

# Function to write buffered logs to the log file
Function WriteLogsBuffer {
    $lockToken = $false
    try {
        [System.Threading.Monitor]::TryEnter($script:lockObject, [ref]$lockToken) | Out-Null
        if ($lockToken) {
            # Transfer logs from the buffer to a temporary buffer
            $tempBuffer = [System.Collections.Concurrent.ConcurrentQueue[string]]::new()
            $log = $null
            while ($script:logBuffer.TryDequeue([ref]$log)) {
                $tempBuffer.Enqueue($log)
            }

            # If there are logs in the buffer, write them to the log file
            if ($tempBuffer.Count -gt 0) {
                # Create the log file if it doesn't exist
                if (-not (Test-Path $script:Logfile)) {
                    New-Item -ItemType File -Path $script:Logfile -Force
                }
                # Write the buffered logs to the log file
                $logsToWrite = @()
                $log = $null
                while ($tempBuffer.TryDequeue([ref]$log)) {
                    if ($null -ne $log) {
                        $logsToWrite += $log
                    }
                }
                # Convert $logsToWrite to an array of strings
                $logsToWrite = $logsToWrite -as [string[]]
                [System.IO.File]::AppendAllLines($script:Logfile, $logsToWrite)
            }
        }
    }
    catch {
        # Display an error message if writing logs fails
        Write-Host "Error occurred while writing logs to the log file: $($_.Exception.Message)"
    }
    finally {
        if ($lockToken) {
            [System.Threading.Monitor]::Exit($script:lockObject)
        }
    }
}

In this function:

  • [System.Threading.Monitor]::TryEnter($script:lockObject, [ref]$lockToken) attempts to acquire a lock to ensure thread safety.
  • $script:logBuffer.TryDequeue([ref]$log) moves log messages from the buffer to a temporary buffer.
  • [System.IO.File]::AppendAllLines($script:Logfile, $logsToWrite) appends the buffered log messages to the log file.

Final Buffer Write Function: WriteRemainingLogsBuffer

The WriteRemainingLogsBuffer function ensures that any remaining log messages in the buffer are written to the log file when the script ends.

# Function to write remaining buffered logs when the script ends
Function WriteRemainingLogsBuffer {
    $lockToken = $false
    try {
        while (-not [System.Threading.Monitor]::TryEnter($script:lockObject, [ref]$lockToken) | Out-Null) {
            Start-Sleep -Milliseconds 100
        }
        WriteLogsBuffer
    }
    finally {
        if ($lockToken) {
            [System.Threading.Monitor]::Exit($script:lockObject)
        }
    }
}

In this function:

  • [System.Threading.Monitor]::TryEnter($script:lockObject, [ref]$lockToken) attempts to acquire a lock to ensure thread safety.
  • WriteLogsBuffer is called to write any remaining log messages to the log file.
  • Start-Sleep -Milliseconds 100 introduces a short delay if the lock is not immediately available.

This function is called in the END block of the script, ensuring that all remaining logs are flushed from the buffer and written to the file, guaranteeing no log data is lost even if the script is interrupted.

For a complete view of the script and its implementation, visit the GitHub repository.

4 thoughts on “Create a PowerShell Log Buffer”

  1. Why use Write-Host in favor of the default Write-Output? Also when comparing objects against $null it is more performant to put the $null statement first. The same situation exists for Out-Null if you simply assign your statement to $null it will be much faster.

    Reply
    • Thank you for your feedback. I often default to using Write-Host due to habit, but I acknowledge that Write-Output could enhance clarity, particularly in scenarios where outputs need to be captured for testing. Also, your suggestions about optimizing performance by positioning $null on the left in comparisons and preferring direct assignments to $null over using Out-Null are helpful. I will make these adjustments to refine the script.

      Reply
  2. Is it possible to combine everything in one function. In the case of when main script can’t be update or modify.. the flushing our remaining it has to go to the end of the main script.

    Reply

Leave a Comment