Skip to content

Commit

Permalink
Merge pull request #116 from tosoikea/rotation
Browse files Browse the repository at this point in the history
Added rotation to file target
  • Loading branch information
EsOsO authored Mar 15, 2021
2 parents 18e35cf + d41df85 commit e30b975
Show file tree
Hide file tree
Showing 12 changed files with 249 additions and 82 deletions.
75 changes: 75 additions & 0 deletions Logging/private/Format-Pattern.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<#
.SYNOPSIS
Replaces the tokens present in the pattern with the values given inside the source (log) object.
.PARAMETER Pattern
Parameter The pattern that defines tokens and possible operations onto them.
.PARAMETER Source
Parameter Log object providing values, if wildcard parameter is not given
.PARAMETER Wildcard
Parameter If this parameter is given, all tokens are replaced by the wildcard character.
.EXAMPLE
Format-Pattern -Pattern %{timestamp} -Wildcard
#>
function Format-Pattern {
[CmdletBinding()]
[OutputType([String])]
param(
[AllowEmptyString()]
[Parameter(Mandatory)]
[string]
$Pattern,
[object]
$Source,
[switch]
$Wildcard
)

[string] $result = $Pattern
[regex] $tokenMatcher = '%{(?<token>\w+?)?(?::?\+(?<datefmtU>(?:%[ABCDGHIMRSTUVWXYZabcdeghjklmnprstuwxy].*?)+))?(?::?\+(?<datefmt>(?:.*?)+))?(?::(?<padding>-?\d+))?}'
$tokenMatches = @()
$tokenMatches += $tokenMatcher.Matches($Pattern)
[array]::Reverse($tokenMatches)

foreach ($match in $tokenMatches) {
$formattedEntry = [string]::Empty
$tokenContent = [string]::Empty

$token = $match.Groups["token"].value
$datefmt = $match.Groups["datefmt"].value
$datefmtU = $match.Groups["datefmtU"].value
$padding = $match.Groups["padding"].value

if ($Wildcard.IsPresent){
$formattedEntry = "*"
}
else{
[hashtable] $dateParam = @{ }
if (-not [string]::IsNullOrWhiteSpace($token)) {
$tokenContent = $Source.$token
$dateParam["Date"] = $tokenContent
}

if (-not [string]::IsNullOrWhiteSpace($datefmtU)) {
$formattedEntry = Get-Date @dateParam -UFormat $datefmtU
}
elseif (-not [string]::IsNullOrWhiteSpace($datefmt)) {
$formattedEntry = Get-Date @dateParam -Format $datefmt
}
else {
$formattedEntry = $tokenContent
}

if ($padding) {
$formattedEntry = "{0,$padding}" -f $formattedEntry
}
}

$result = $result.Substring(0, $match.Index) + $formattedEntry + $result.Substring($match.Index + $match.Length)
}

return $result
}
49 changes: 0 additions & 49 deletions Logging/private/Replace-Token.ps1

This file was deleted.

2 changes: 1 addition & 1 deletion Logging/private/Start-LoggingManager.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function Start-LoggingManager {
}

# Importing functions into runspace
foreach ($Function in 'Replace-Token', 'Initialize-LoggingTarget', 'Get-LevelNumber') {
foreach ($Function in 'Format-Pattern', 'Initialize-LoggingTarget', 'Get-LevelNumber') {
Write-Verbose "Importing function $($Function) into runspace"
$Body = Get-Content Function:\$Function
$f = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function, $Body
Expand Down
2 changes: 1 addition & 1 deletion Logging/public/Add-LoggingTarget.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function Add-LoggingTarget {
}

if ($Script:Logging.Targets[$PSBoundParameters.Name].Init -is [scriptblock]) {
& $Script:Logging.Targets[$PSBoundParameters.Name].Init $Configuration
& $Script:Logging.Targets[$PSBoundParameters.Name].Init $Script:Logging.EnabledTargets[$PSBoundParameters.Name]
}
}
}
2 changes: 1 addition & 1 deletion Logging/targets/Console.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
)

try {
$logText = Replace-Token -String $Configuration.Format -Source $Log
$logText = Format-Pattern -Pattern $Configuration.Format -Source $Log

if (![String]::IsNullOrWhiteSpace($Log.ExecInfo) -and $Configuration.PrintException) {
$logText += "`n{0}" -f $Log.ExecInfo.Exception.Message
Expand Down
2 changes: 1 addition & 1 deletion Logging/targets/ElasticSearch.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
$httpType = "http"
}

$Index = Replace-Token -String $Configuration.Index -Source $Log
$Index = Format-Pattern -Pattern $Configuration.Index -Source $Log
$Uri = '{0}://{1}:{2}/{3}/{4}' -f $httpType, $Configuration.ServerName, $Configuration.ServerPort, $Index, $Configuration.Type

if ($Configuration.Flatten) {
Expand Down
2 changes: 1 addition & 1 deletion Logging/targets/Email.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
To = $Configuration.To.Split(',').Trim()
Port = $Configuration.Port
UseSsl = $Configuration.UseSsl
Subject = Replace-Token -String $Configuration.Subject -Source $Log
Subject = Format-Pattern -Pattern $Configuration.Subject -Source $Log
Body = $Body
BodyAsHtml = $true
}
Expand Down
127 changes: 123 additions & 4 deletions Logging/targets/File.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,132 @@
Encoding = @{Required = $false; Type = [string]; Default = 'ascii' }
Level = @{Required = $false; Type = [string]; Default = $Logging.Level }
Format = @{Required = $false; Type = [string]; Default = $Logging.Format }
# Rotation
## Rotate after the directory contains the given amount of files. A value that is less than or equal to 0 is treated as not configured.
RotateAfterAmount = @{Required = $false; Type = [int]; Default = -1}
## Amount of files to be rotated, when RotateAfterAmount is used.
## In general max(|Files| - RotateAfterAmount, RotateAmount) files are rotated.
RotateAmount = @{Required = $false; Type = [int]; Default = -1}
## Rotate after the difference between the current datetime and the datetime of the file(s) are greater then the given timespan. A value of 0 is treated as not configured.
RotateAfterDate = @{Required = $false; Type = [timespan]; Default = [timespan]::Zero}
## Rotate after the file(s) are greater than the given size in BYTES. A value that is less than or equal to 0 is treated as not configured.
RotateAfterSize = @{Required = $false; Type = [int]; Default = -1}
## Optionally all rotated files can be compressed. Uses patterns, however only datetimes are allows
CompressionPath = @{Required = $false; Type = [string]; Default = [String]::Empty}
}
Init = {
param(
[hashtable] $Configuration
)

[String] $directoryPath = [System.IO.Path]::GetDirectoryName($Configuration.Path)
[string] $directoryPath = [System.IO.Path]::GetDirectoryName($Configuration.Path)
[string] $wildcardBasePath = Format-Pattern -Pattern ([System.IO.Path]::GetFileName($Configuration.Path)) -Wildcard

# We (try to) create the directory if it is not yet given
if (-not [System.IO.Directory]::Exists($directoryPath)){
# "Creates all directories and subdirectories in the specified path unless they already exist."
# https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.createdirectory?view=net-5.0#System_IO_Directory_CreateDirectory_System_String_
[System.IO.Directory]::CreateDirectory($directoryPath) | Out-Null
}

# Allow for the rolling of log files
$mtx = New-Object System.Threading.Mutex($false, 'FileMtx')
[void] $mtx.WaitOne()
try{
# Get existing files
if (-not [System.IO.Directory]::Exists($directoryPath)){
return
}

$rotationDate = $Configuration.RotateAfterDate.Duration()
$currentDateUtc = [datetime]::UtcNow

[string[]] $logFiles = [System.IO.Directory]::GetFiles($directoryPath, $wildcardBasePath)
$toBeRolled = @()
$givenFiles = [System.IO.FileInfo[]]::new($logFiles.Count)

for ([int] $i = 0; $i -lt $logFiles.Count; $i++){
$fileInfo = [System.IO.FileInfo]::new($logFiles[$i])

# 1. Based on file size
if ($Configuration.RotateAfterSize -gt 0 -and $fileInfo.Length -gt $Configuration.RotateAfterSize){
$toBeRolled += $fileInfo
}
# 2. Based on date
elseif ($rotationDate.TotalSeconds -gt 0 -and ($currentDateUtc - $fileInfo.CreationTimeUtc).TotalSeconds -gt $rotationDate.TotalSeconds){
$toBeRolled += $fileInfo
}
# 3. Based on number
else{
$givenFiles[$i] = $fileInfo
}
}

# 3. Based on number
if ($Configuration.RotateAfterAmount -gt 0 -and $givenFiles.Count -gt $Configuration.RotateAfterAmount){
if ($Configuration.RotateAmount -le 0){
$Configuration.RotateAmount = $Configuration.RotateAfterAmount / 2
}

$sortedFiles = $givenFiles | Sort-Object -Property CreationTimeUtc

# Rotate
# a) until sortedFiles = RotateAfterAmount
# b) until RotateAmount files are rotated
for ([int] $i = 0; ($i -lt ($sortedFiles.Count - $Configuration.RotateAfterAmount)) -or ($i -le $Configuration.RotateAmount); $i++){
$toBeRolled += $sortedFiles[$i]
}
}

[string[]] $paths = @()
foreach ($fileInfo in $toBeRolled){
$paths += $fileInfo.FullName
}

if ($paths.Count -eq 0){
return
}

# (opt) compress old files
if (-not [string]::IsNullOrWhiteSpace($Configuration.CompressionPath)){
try{
Add-Type -As System.IO.Compression.FileSystem
}catch{
$ParentHost.UI.WriteErrorLine("ERROR: You need atleast .Net 4.5 for the compression feature.")
return
}

[string] $compressionDirectory = [System.IO.Path]::GetDirectoryName($Configuration.CompressionPath)
[string] $compressionFile = Format-Pattern -Pattern $Configuration.CompressionPath -Source @{
timestamp = [datetime]::now
timestamputc = [datetime]::UtcNow
pid = $PID
}

# We (try to) create the directory if it is not yet given
if (-not [System.IO.Directory]::Exists($compressionDirectory)){
[System.IO.Directory]::CreateDirectory($compressionDirectory) | Out-Null
}

# Compress-Archive not supported for PS < 5
[string] $temporary = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([guid]::NewGuid().ToString())
[System.IO.DirectoryInfo] $tempDir = [System.IO.Directory]::CreateDirectory($temporary)

if ([System.IO.File]::Exists($compressionFile)){
[IO.Compression.ZipFile]::ExtractToDirectory($compressionFile, $tempDir.FullName)
Remove-Item -Path $compressionFile -Force
}

Move-Item -Path $paths -Destination $tempDir.FullName -Force
[IO.Compression.ZipFile]::CreateFromDirectory($tempDir.FullName, $compressionFile, [System.IO.Compression.CompressionLevel]::Fastest, $false)
Remove-Item -Path $tempDir.FullName -Recurse -Force
}else{
Remove-Item -Path $paths -Force
}
}finally{
[void] $mtx.ReleaseMutex()
$mtx.Dispose()
}
}
Logger = {
param(
Expand All @@ -36,7 +148,7 @@
$Log.Remove('Body')
}

$Text = Replace-Token -String $Configuration.Format -Source $Log
$Text = Format-Pattern -Pattern $Configuration.Format -Source $Log

if (![String]::IsNullOrWhiteSpace($Log.ExecInfo) -and $Configuration.PrintException) {
$Text += "`n{0}" -f $Log.ExecInfo.Exception.Message
Expand All @@ -45,10 +157,17 @@

$Params = @{
Append = $Configuration.Append
FilePath = Replace-Token -String $Configuration.Path -Source $Log
FilePath = Format-Pattern -Pattern $Configuration.Path -Source $Log
Encoding = $Configuration.Encoding
}

$Text | Out-File @Params
$mtx = New-Object System.Threading.Mutex($false, 'FileMtx')
[void] $mtx.WaitOne()
try{
$Text | Out-File @Params
}finally{
[void] $mtx.ReleaseMutex()
$mtx.Dispose()
}
}
}
2 changes: 1 addition & 1 deletion Logging/targets/Slack.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
)

$Text = @{
text = Replace-Token -String $Configuration.Format -Source $Log
text = Format-Pattern -Pattern $Configuration.Format -Source $Log
}

if ($Configuration.BotName) { $Text['username'] = $Configuration.BotName }
Expand Down
2 changes: 1 addition & 1 deletion Logging/targets/WebexTeams.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
# Build the Message body
$body = @{
roomId = $Configuration.RoomId
text = $Configuration.Icons[$Log.Level] + " " + $(Replace-Token -String $Configuration.Format -Source $Log)
text = $Configuration.Icons[$Log.Level] + " " + $(Format-Pattern -Pattern $Configuration.Format -Source $Log)
}

# Convert to JSON
Expand Down
Loading

0 comments on commit e30b975

Please sign in to comment.