Web 主機上 Log 成長頗快,我想寫個小程序將時間較久的 Log 以月為單位壓縮儲存節省空間。 實測壓縮後體積只剩 3%,足足減少 30 倍有餘,估計可省下大量空間。

類似的工具之前寫過 C# 版,這回試試用 PowerShell 解決。

Log 檔一天一個,壓縮排程設在每月第一天。執行時前一個月的 Log 檔不動,再前一個月起的 Log 檔,以月為單位壓成一個檔案。

我想到的演算邏輯為:先用 DateTime.Today 取當月第一天(這樣子就算不是月初跑也沒關係)後 AddMonths(-1) 算出前一個月的第一天作為 保留起始日。DIR *.log 篩選保留起始日之前的所有 Log 檔名,Group By yyyy-MM 再 Distinct 取得待處理月份字串清單。 (第一次執行會將一個月以上的 Log 全部做完) 接著跑迴圈將 yyyy-MM*.log 檔案用 7z.exe 最高壓縮比(-mx9)壓成 yyyy-MM.7z,壓縮完成後刪除 yyyy-MM*.log。

執行結果會像這樣:

前面說的邏輯要我用 C# 寫跟喝水一樣簡單,轉成不熟悉的 PowerShell 得花點腦筋,但只是要爬點文,並不難搞定:

  1. 保留起始日直接用 .NET DateTime 物件計算,在 PS 可寫 [DateTime]::Today.AddDays(...).AddMonths()。
  2. Get-ChildItem *.log 相當於 DIR *.log
  3. 用 Pipeline 串接 Where,用 -lt 比對檔名 ($_.Name) 過濾檔名小於保留起始日的檔案
  4. foreach { $_.Name.Substring(0, 7) } 將檔案物件轉成 yyyy-MM 字串陣列
  5. 對 yyyy-MM 字串陣列執行 Sort-Object -Unique,可得到相當於 LINQ Distinct() 的效果
  6. 最後跑 foreach 迴圈,透過 & "7z.exe" 呼叫外部 EXE 程式,打包參數需用 Split(' ') 拆成字串陣列傳入
  7. Remove-Item 將 yyyy-MM*.log 刪除

完整程式範例如下:

$reserveDate = [DateTime]::Today.AddDays(-[DateTime]::Today.Day+1).AddMonths(-1).ToString("yyyy-MM-dd");
$cmd = "D:\Tools\7z.exe";
Get-ChildItem *.log | Where { $_.Name -lt $reserveDate } |
foreach {$_.Name.Substring(0, 7)} | Sort-Object -Unique |
foreach { 
	$params = "a -mx9 $_.7z $_*.log".Split(" "); 
	& $cmd $params; 
	Remove-Item -Path "$_*.log" 
}

如果是 PowerShell 5.0+,其內建 Compress-Archive 可產生 ZIP 檔不需仰賴外部程式,但我擔心部署主機 PowerShell 版本不夠新,還是決定用 7z.exe 減少部署需求。

另外,以上程式可存成 ArchiveLogsMonthAgo.ps1 檔,在最前方加入一行接收參數切換到 Log 所在路徑執行壓縮,便可用於多個 Log 資料夾。

Set-Location -Path $args[0];

PowerShell example to archive log files earlier than one month.


Comments

# by Pika

如果不考慮練習 PowerShell 為目的的話, 這個 case 我會考慮用 PowerShell 產生時間戳記, 比如 yyyy-MM, 然後呼叫 7z.exe 壓縮 yyyy-MM-*.log 再加上刪除原檔的 switch 參數, 比較簡潔一點的感覺, 供參考. https://sevenzip.osdn.jp/chm/cmdline/switches/sdel.htm

# by Jeffrey

to Pika, 這招好! 學習了。感謝分享。

# by IT DOG

用 gz 會方便 過7z

# by ALIEN.SUN

謝謝您分享,這功能用forfiles+7Z的CMD寫過,您的更是方便啊!!

Post a comment