log4jを意識したインターフェースで作成しています。
使用方法などは実装コードのコメント、プロパティ、単体テストコードを参照いただけると幸いです。
実装
Logger.ps1
<#
.SYNOPSIS
指定されたフォーマットで指定されたディレクトリにログを書き込むためのLoggerクラス。
.DESCRIPTION
Loggerクラスは、各種レベル(デバッグ、情報、警告、エラー)でログを書き込むメソッドを提供します。ログは、logDirフィールドで指定されたディレクトリに、logLayoutフィールドで指定されたフォーマットで書き込まれます。
.PARAMETER encoding
ログファイルの文字エンコーディング。デフォルトは "Default"。
.PARAMETER logLayout
ログメッセージのレイアウト。デフォルトは "%d %s %p %m"。
.PARAMETER datetimeLayout
日時のフォーマット。デフォルトは "yyyyMMddTHHmmssZ"。
.METHOD debug
デバッグレベルのログを書き込みます。
.METHOD info
情報レベルのログを書き込みます。
.METHOD warn
警告レベルのログを書き込みます。
.METHOD error
エラーレベルのログを書き込みます。
.EXAMPLE
$logger = New-Object Logger($MyInvocation)
$logger.info("これは情報レベルのログメッセージです。")
$logger.error("これはエラーレベルのログメッセージです。")
#>
class Logger {
# Properties(Required)
[string] $encoding = "Default"
[string] $logLayout = "%d %s %p %m"
[string] $datetimeLayout = "yyyyMMddTHHmmssZ"
# Properties(Auto-Configured)
[string] $scriptName
[string] $logCreatedDatetime
[string] $logDir
[string] $logFilename
Logger ($InvokingScriptMyInvocation) {
$this.scriptName = (Split-Path -Leaf $InvokingScriptMyInvocation.MyCommand.Name).replace(".ps1", "")
$this.logCreatedDatetime = Get-Date -Format yyyyMMdd
$this.logDir = (Split-Path -Parent $InvokingScriptMyInvocation.MyCommand.Path)
$this.logFilename = (Split-Path -Leaf $InvokingScriptMyInvocation.MyCommand.Name).replace(".ps1", "-" + $this.logCreatedDatetime + ".log")
}
[void] debug($msg) {
$this.writeLog($msg, "DEBUG")
}
[void] info($msg) {
$this.writeLog($msg, "INFO")
}
[void] warn($msg) {
$this.writeLog($msg, "WARN")
}
[void] error($msg) {
$this.writeLog($msg, "ERROR")
}
[void] writeLog($msg, $logLevel) {
$replacedLayout = $this.logLayout
$replacedLayout = $replacedLayout.replace("%d", (Get-Date -Format $this.datetimeLayout))
$replacedLayout = $replacedLayout.replace("%p", $logLevel)
$replacedLayout = $replacedLayout.replace("%s", $this.scriptName)
$replacedLayout = $replacedLayout.replace("%m", $msg)
Write-Output $replacedLayout | Out-File -FilePath (Join-Path $this.logDir $this.logFilename) -Append -Encoding $this.encoding
}
}
使用方法と簡単な単体テスト
Logger.Tests.ps1
# Needed "Install-Module -Name Pester -Force -SkipPublisherCheck"
# Load Logger script ( need to store the Logger.ps1 in the same directory )
. ($PSCommandPath.replace(".Tests", ""))
# $MyInvocation of this script cannot be obtained in the It section.
$testScriptMyInvocation = $MyInvocation
Describe "LoggerUnitTest" {
It "Regular-TestCase" {
$Logger = New-Object Logger($testScriptMyInvocation)
$msg = "test-message"
$Logger.debug($msg)
Get-Content (Join-Path $Logger.logDir $Logger.logFilename) -Tail 1 | Should -Match ".*DEBUG.*$msg.*"
$Logger.info($msg)
Get-Content (Join-Path $Logger.logDir $Logger.logFilename) -Tail 1 | Should -Match ".*INFO.*$msg.*"
$Logger.warn($msg)
Get-Content (Join-Path $Logger.logDir $Logger.logFilename) -Tail 1 | Should -Match ".*WARN.*$msg.*"
$Logger.error($msg)
Get-Content (Join-Path $Logger.logDir $Logger.logFilename) -Tail 1 | Should -Match ".*ERROR.*$msg.*"
}
It "Exception-TestCase" {
$Logger = New-Object Logger($testScriptMyInvocation)
# Specific Non-Existing Directory
$Logger.logDir = (Join-Path $Logger.logDir "DummyDummyDir")
{ $Logger.debug($msg) } | Should -Throw -ExceptionType System.IO.DirectoryNotFoundException
}
}
同一ディレクトリに実装とテストスクリプトを格納し実行
PS c:\logger> (Invoke-Pester ./Logger.Tests.ps1 -CodeCoverage ./Logger.ps1 -PassThru).CodeCoverage
Logger.Tests-20230524.log (ログ出力結果)
20230524T153012Z Logger.Tests DEBUG test-message
20230524T153012Z Logger.Tests INFO test-message
20230524T153012Z Logger.Tests WARN test-message
20230524T153012Z Logger.Tests ERROR test-message