Assets/Cleanup-OpsLogs.ps1
|
<# .SYNOPSIS Cleans up old Ops logs. .DESCRIPTION Deletes transcript logs older than the retention window and trims the per-job NDJSON history files down to the same window. The history files are appended to on every run and are never rotated otherwise, so they grow without bound. .PARAMETER DaysToKeep Number of days to keep logs. Default 30. #> param ( [ValidateRange(0, 3650)] [int]$DaysToKeep = 30 ) $ErrorActionPreference = "Stop" # The Ops root defaults to C:\Ops. OPS_ROOT overrides it for development and # testing, matching the dashboard server's existing convention. $OpsRoot = if ([string]::IsNullOrWhiteSpace($env:OPS_ROOT)) { "C:\Ops" } else { $env:OPS_ROOT } $LogDir = Join-Path $OpsRoot "Logs" if (-not (Test-Path -Path $LogDir)) { Write-Output "Log directory '$LogDir' does not exist. Nothing to do." return } $CutoffDate = (Get-Date).AddDays(-$DaysToKeep) Write-Output "Cleaning up logs older than $CutoffDate in $LogDir" # Only transcripts are deleted outright. The previous version deleted every file # in the directory, which would have taken the job history with it. Get-ChildItem -Path $LogDir -Filter "*.log" -File | Where-Object { $_.LastWriteTime -lt $CutoffDate } | ForEach-Object { try { Remove-Item -Path $_.FullName -Force -ErrorAction Stop Write-Output "Deleted: $($_.Name)" } catch { Write-Error "Failed to delete $($_.Name): $($_.Exception.Message)" } } # Trim history files in place, keeping entries inside the retention window. Get-ChildItem -Path $LogDir -Filter "*.history.json" -File | ForEach-Object { $HistoryFile = $_ try { $Kept = foreach ($Line in (Get-Content -Path $HistoryFile.FullName -ErrorAction Stop)) { if ([string]::IsNullOrWhiteSpace($Line)) { continue } $Entry = $null try { $Entry = $Line | ConvertFrom-Json } catch { } $EntryDate = [datetime]::MinValue if ($Entry -and $Entry.Timestamp) { # Written by Write-OpsLog / ops-utils.js in this exact shape. [void][datetime]::TryParseExact( [string]$Entry.Timestamp, "yyyy-MM-dd HH:mm:ss", [cultureinfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::None, [ref]$EntryDate) } # Unparseable lines are dropped along with expired ones rather than # accumulating forever. if ($EntryDate -ge $CutoffDate) { $Line } } $Kept = @($Kept) if ($Kept.Count -eq 0) { Remove-Item -Path $HistoryFile.FullName -Force -ErrorAction Stop Write-Output "Deleted empty history: $($HistoryFile.Name)" } else { Set-Content -Path $HistoryFile.FullName -Value $Kept -Encoding UTF8 -ErrorAction Stop Write-Output "Trimmed history: $($HistoryFile.Name) ($($Kept.Count) entries kept)" } } catch { Write-Error "Failed to trim $($HistoryFile.Name): $($_.Exception.Message)" } } |