Skip to content

Instantly share code, notes, and snippets.

@barsv
Last active January 17, 2024 12:39
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 21 You must be signed in to fork a gist
  • Save barsv/85c93b599a763206f47aec150fb41ca0 to your computer and use it in GitHub Desktop.
Save barsv/85c93b599a763206f47aec150fb41ca0 to your computer and use it in GitHub Desktop.
Logging in powershell with log rotation
# all logging settins are here on top
$logFile = "log-$(gc env:computername).log"
$logLevel = "DEBUG" # ("DEBUG","INFO","WARN","ERROR","FATAL")
$logSize = 1mb # 30kb
$logCount = 10
# end of settings
function Write-Log-Line ($line) {
Add-Content $logFile -Value $Line
Write-Host $Line
}
# http://stackoverflow.com/a/38738942
Function Write-Log {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$True)]
[string]
$Message,
[Parameter(Mandatory=$False)]
[String]
$Level = "DEBUG"
)
$levels = ("DEBUG","INFO","WARN","ERROR","FATAL")
$logLevelPos = [array]::IndexOf($levels, $logLevel)
$levelPos = [array]::IndexOf($levels, $Level)
$Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss:fff")
if ($logLevelPos -lt 0){
Write-Log-Line "$Stamp ERROR Wrong logLevel configuration [$logLevel]"
}
if ($levelPos -lt 0){
Write-Log-Line "$Stamp ERROR Wrong log level parameter [$Level]"
}
# if level parameter is wrong or configuration is wrong I still want to see the
# message in log
if ($levelPos -lt $logLevelPos -and $levelPos -ge 0 -and $logLevelPos -ge 0){
return
}
$Line = "$Stamp $Level $Message"
Write-Log-Line $Line
}
# https://gallery.technet.microsoft.com/scriptcenter/PowerShell-Script-to-Roll-a96ec7d4
function Reset-Log
{
# function checks to see if file in question is larger than the paramater specified
# if it is it will roll a log and delete the oldes log if there are more than x logs.
param([string]$fileName, [int64]$filesize = 1mb , [int] $logcount = 5)
$logRollStatus = $true
if(test-path $filename)
{
$file = Get-ChildItem $filename
if((($file).length) -ige $filesize) #this starts the log roll
{
$fileDir = $file.Directory
#this gets the name of the file we started with
$fn = $file.name
$files = Get-ChildItem $filedir | ?{$_.name -like "$fn*"} | Sort-Object lastwritetime
#this gets the fullname of the file we started with
$filefullname = $file.fullname
#$logcount +=1 #add one to the count as the base file is one more than the count
for ($i = ($files.count); $i -gt 0; $i--)
{
#[int]$fileNumber = ($f).name.Trim($file.name) #gets the current number of
# the file we are on
$files = Get-ChildItem $filedir | ?{$_.name -like "$fn*"} | Sort-Object lastwritetime
$operatingFile = $files | ?{($_.name).trim($fn) -eq $i}
if ($operatingfile)
{$operatingFilenumber = ($files | ?{($_.name).trim($fn) -eq $i}).name.trim($fn)}
else
{$operatingFilenumber = $null}
if(($operatingFilenumber -eq $null) -and ($i -ne 1) -and ($i -lt $logcount))
{
$operatingFilenumber = $i
$newfilename = "$filefullname.$operatingFilenumber"
$operatingFile = $files | ?{($_.name).trim($fn) -eq ($i-1)}
write-host "moving to $newfilename"
move-item ($operatingFile.FullName) -Destination $newfilename -Force
}
elseif($i -ge $logcount)
{
if($operatingFilenumber -eq $null)
{
$operatingFilenumber = $i - 1
$operatingFile = $files | ?{($_.name).trim($fn) -eq $operatingFilenumber}
}
write-host "deleting " ($operatingFile.FullName)
remove-item ($operatingFile.FullName) -Force
}
elseif($i -eq 1)
{
$operatingFilenumber = 1
$newfilename = "$filefullname.$operatingFilenumber"
write-host "moving to $newfilename"
move-item $filefullname -Destination $newfilename -Force
}
else
{
$operatingFilenumber = $i +1
$newfilename = "$filefullname.$operatingFilenumber"
$operatingFile = $files | ?{($_.name).trim($fn) -eq ($i-1)}
write-host "moving to $newfilename"
move-item ($operatingFile.FullName) -Destination $newfilename -Force
}
}
}
else
{ $logRollStatus = $false}
}
else
{
$logrollStatus = $false
}
$LogRollStatus
}
# to null to avoid output
$Null = @(
Reset-Log -fileName $logFile -filesize $logSize -logcount $logCount
)
. .\logger.ps1
Write-Log "Started" "INFO"
for ($i = 100; $i -gt 0; $i--)
{
Write-Log "debug message"
Write-Log "debug message2" "DEBUG"
Write-Log "info message" "INFO"
Write-Log "warn message" "WARN"
Write-Log "error message" "ERROR"
Write-Log "fatal message" "FATAL"
Write-Log "message with wrong level" "WRONG"
}
@karlskewes
Copy link

karlskewes commented May 3, 2018

Thank you for this.
I had an issue with logs rotating correctly, and the fix here resolves it: https://gallery.technet.microsoft.com/scriptcenter/PowerShell-Script-to-Roll-a96ec7d4/view/Discussions#content

Adding in the log rotation call into the Write-Log function takes care of rotating logs live without requiring application test.ps1 restart.

    # Check log size before writing and rotate it if oversize. May not be very performant. 
    $Null = @(
        Reset-Log -fileName $logFile -filesize $logSize -logcount $logCount
    )

@ICanCodeABit
Copy link

Thank you very much!

I had to replace line 65:
'$files = Get-ChildItem $filedir | ?{$.name -like "$fn*"} | Sort-Object lastwritetime'
with an explicit array declaration:
'$files = @(Get-ChildItem $filedir | ?{$
.name -like "$fn*"} | Sort-Object lastwritetime)'
because if only one file exists the script on my machine still wrote in the same file although the filesize exceeded.

@yokhoe
Copy link

yokhoe commented Mar 3, 2019

I have a need to specify the location of where the log file writes to so I had to modify the Write-Log-Line function slightly. Perhaps it could be of use to some?

# help: https://stackoverflow.com/questions/48425562/powershell-add-content-should-create-path-but-throws-exception-could-not-find-a
function Write-Log-Line ($line) {
    $logFile |% { 
           If (Test-Path -Path $_) { Get-Item $_ } 
           Else { New-Item -Path $_ -Force } 
    } | Add-Content -Value $Line
    Write-Host $Line
}

@arberg
Copy link

arberg commented Apr 17, 2019

Here's a version which keeps the index, and generates names like 'file.1.log', I also simplified the script a lot, though it might not do quite the same advanced index deletion. It now deletes all above $logcount, and then moves the rest into place
editted 2019.09.11 to fix bug if no extension, and if more than 9 maxCount.

# Example Reset-Log -fileName $logfile -maxSize 1mb -maxCount 5
function Roll-logFile
{
    #function checks to see if file in question is larger than the paramater specified if it is it will roll a log and delete the oldes log if there are more than x logs.
    param([string]$fileName, [int64]$maxSize = 1mb, [int] $maxCount = 9)
    $logRollStatus = $true
    if(test-path $filename) {
        $file = Get-ChildItem $filename
        # Start the log-roll if the file is big enough
        if((($file).length) -ige $maxSize) {
            $fileDir = $file.Directory
            $fbase = $file.BaseName
            $fext = $file.Extension
            $isExtEmpty=($fext.Length -eq 0)
            $fn = $file.name #this gets the name of the file we started with
            # Write-Host "fbase='$fbase'"
            # Write-Host "fext='$fext'"
            # Write-Host "isExtEmpty=$isExtEmpty"

            function fileBase($file) {
                if ($isExtEmpty) {$file.Name} else { $file.BaseName }
            }
            function refresh-log-files {
                # write-host "find logs in dir $filedir matching base '$fbase*' and with extension '$fext'"
                Get-ChildItem $filedir | ?{ fileBase($_) -like "$fbase*" -and ($isExtEmpty -or $_.Extension -eq "$fext") } | Sort-Object lastwritetime
            }
            function getNumberOfFile($theFile) {
                $base=fileBase $theFile
                Write-Host "$base -eq $fbase" ($base -eq $fbase)
                if ($base -eq $fbase) { 0 } else { $base.substring($fbase.length+1) }
            }
            function fileByIndex($index) {
                $files | ?{ (getNumberOfFile $_) -eq $index}
            }
            function getFileNameByNumber($index) {
                "$fbase.$index$fext"
            }

            refresh-log-files | %{
                $num=getNumberOfFile $_
                Write-Host "$($_) with number $num"
                if ([int]$num -ge $maxCount) {
                    write-host "Deleting index $_ due to max=$maxCount"
                    Remove-Item $_
                }
            }
            $files = @(refresh-log-files)

            # Now there should be atmost $maxCount files, and the highest number is one less than count, unless there are badly named files, eg non-numbers
            for ($i = ($files.count); $i -gt 0; $i--) {
                write-host $i
                $newfilename = getFileNameByNumber $i
                if($i -gt 1) {
                    $fileToMove = fileByIndex ($i-1)
                } else {
                    $fileToMove = $file
                }
                if (Test-Path $fileToMove) { # If there are holes in sequence, file by index might not exist. The 'hole' will shift to next number, as files below hole are moved to fill it
                    write-host "moving '$fileToMove' => '$newfilename'"
                    # $fileToMove is a System.IO.FileInfo, but $newfilename is a string. Move-Item takes a string, so we need full path
                    Move-Item ($fileToMove.FullName) -Destination $fileDir\$newfilename -Force
                }
            }
        } else {
            $logRollStatus = $false
        }
    } else {
        $logrollStatus = $false
    }
    $LogRollStatus
}

@mntlfngrs
Copy link

@arberg
It seems like the roll only works when there are more than 1 file to start. Otherwise I get error "The property 'count' cannot be found on this object." Am I missing something? Sorry I don't have a fix for it at this point (except that I copy/renamed my log so it would have 2) but I'll be working on it as I get time.

@arberg
Copy link

arberg commented Sep 11, 2019

@mntlfngs

Its probably because a list was one, and powershell interprets that by removing list unless using @(). I couldn't reproduce the problem, but I have added those, to be safe. I also fixed it so it works without extensions on log-file.

@mntlfngrs
Copy link

@arberg
Thanks. I got time to tinker and did some changes that made it work for me. Created a fork with my mods .

@mntlfngrs
Copy link

Just updated my fork with something that I think is working pretty slick now. Quite happy with it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment