PowerShell でのログ出力を頑張っていたら logger オブジェクトができた

2020/04/18

経緯

この 2 ヶ月、仕事で結構 PowerShell でスクリプトを作っている。
毎回テキトーにログ出していたらログの使い勝手が悪いので、ログ出力をユーザー定義関数として作ったが、状態を持てないのでなんかダサかった。

# 理想
$logger.info("log message")
$logger.error("log message")

# 現実
Put-Log -Message "log message" -File "C:\hoge.txt" -Info 
Put-Log -Message "log message" -File "C:\hoge.txt" -ERROR 

ログファイルのパスをグローバル変数にするとか色々考えていたけど、ふと、「スクリプトブロックを使ったらなんか上手くいくんじゃね?」と思い、車輪の再発明になるのだろうが自作してみた。後悔はしていない。

Claas を使うという事も一瞬頭を過ぎったが、PowerShell 5.0 を仕事で使うのはまだ先になりそうなのですぐに使えるほうを選んだ。

結果(使用方法)

ログを画面出力する(Write-Host 使用)

# ロガーオブジェクト取得
$logger = Get-Logger

# ログレベル Info でログを出力
$logger.info.Invoke("Message")

# ログレベル Warninng でログを出力
$logger.warn.Invoke("Message")

# ログレベル Error でログを出力
$logger.error.Invoke("Message")

画面出力と同時にログファイルを出力

# ログファイルをセットしてロガーオブジェクトを取得
$logger = Get-Logger -Logfile "C:\hoge\hoge.log"

# ログレベル Info でログを出力
$logger.info.Invoke("Message")

画面出力は行わず、ログファイルのみ出力

# ログファイルをセットし、ディスプレイ出力を止めるスイッチを付けてロガーオブジェクトを取得
$logger = Get-Logger -Logfile "C:\hoge\hoge.log" -NoDisplay

# ログレベルを Info  でログを出力
$logger.info.Invoke("Message")

コード

function Global:Get-Logger{
    Param(
        [CmdletBinding()]
        [Parameter()]
        [String]$Delimiter = " ",
        [Parameter()]
        [String]$Logfile,
        [Parameter()]
        [String]$Encoding = "Default",
        [Parameter()]
        [Switch]$NoDisplay
    )
    if (!(Test-Path -LiteralPath (Split-Path $Logfile -parent) -PathType container)) {
        New-Item $Logfile -type file -Force
    }
    $logger = @{}
    $logger.Set_Item('info', (Put-Log -Delimiter $Delimiter -Logfile $logfile -Encoding $Encoding -NoDisplay $NoDisplay -Info))
    $logger.Set_Item('warn', (Put-Log -Delimiter $Delimiter -Logfile $logfile -Encoding $Encoding -NoDisplay $NoDisplay -Warn))
    $logger.Set_Item('error', (Put-Log -Delimiter $Delimiter -Logfile $logfile -Encoding $Encoding -NoDisplay $NoDisplay -Err))
    return $logger
}

function Global:Put-Log
{
    Param(
        [CmdletBinding()]
        [Parameter()]
        [String]$Delimiter = " ",
        [Parameter()]
        [String]$Logfile,
        [Parameter()]
        [String]$Encoding,
        [Parameter()]
        [bool]$NoDisplay,
        [Parameter()]
        [Switch]$Info,
        [Parameter()]
        [Switch]$Warn,
        [Parameter()]
        [Switch]$Err
    )
    return {
        param([String]$msg = "")

        # Initialize variables
        $logparam = @("White", "INFO")
        if ($Warn)  { $logparam = @("Yellow", "WARN") }
        if ($Err) { $logparam = @("Red", "ERROR") }
        $txt = "[$(Get-Date -Format "yyyy/MM/dd HH:mm:ss")]${Delimiter}{0}${Delimiter}{1}" -f $logparam[1], $msg

        # Output Display
        if(!$NoDisplay) {
            Write-Host -ForegroundColor $logparam[0] $txt
        }
        # Output logfile
        if($Logfile) {
            Write-Output $txt | Out-File -FilePath $Logfile -Append -Encoding $Encoding
        }
    }.GetNewClosure()
}

上記のコードはコメントを削っている。最新のコードは GitHub 参照。

考察

結構理想に近い結果になったし、PowerShell 触り始めて 2 ヶ月、PowerShell と少し仲良くなれた気がする。

PowerShell のバージョンを指定して、2.0 でも動いたので、割と使える気がしているので、まずは仕事で使ってみようかな。

参考