車輪の再発明

いったい何番煎じだよ!

PowershellでSyslogサーバを書く

アプライアンス製品の検証してるときにちょっとSyslogサーバが欲しくなったので、どこにでもあるPowershellで書きました。 わざわざサーバ立てるほどでもない場合に割と役立ちます。

前提

  • 今どきのwindowsならほぼほぼ実行できるPowershell V2で実装。
  • TCPは対応してません。

コード

param([int]$port=514,[string]$file)

$ErrorActionPreference="stop"

$enc=New-Object System.Text.ASCIIEncoding

$facility=@("kernel",
            "user",
            "mail",
            "daemon",
            "auth",
            "syslog","
            lpr",
            "news",
            "uucp",
            "cron",
            "auth",
            "ftp",
            "ntp",
            "log audit",
            "log alert",
            "cron",
            "local0",
            "local1",
            "local2",
            "local3",
            "local4",
            "local5",
            "local6",
            "local7")

$severity=@("emerg",
            "alert",
            "crit",
            "err",
            "warning",
            "notice",
            "info",
            "debug")



if($file.Length -gt 0){
    if(!(Test-Path (Split-Path $file))){
        Write-Host "No such path. ($file)"
        exit
    }
}

$client=New-Object System.Net.Sockets.UdpClient($port)
Write-Host "Start listening UDP/$port"

while($true){
    $handler=$client.BeginReceive($null,$null)
    while(!$handler.IsCompleted){
        sleep -Milliseconds 100
    }
    $rcvTime=(Get-Date).ToString("yyyy/MM/dd HH:mm:ss")
    $remoteEnd=New-Object System.Net.IPEndPoint([System.Net.IPAddress]::Any,$null)
    $rcvByte=$client.EndReceive($handler,[ref]$remoteEnd)

    $msg=$enc.GetString($rcvByte,0,$rcvByte.Length)
    if($msg -match "((?<=^<)[0-9]*)>(.*)"){
        $sevCode=[int]$Matches[1]%8
        $facCode=([int]$Matches[1]-$sevCode)/8

        $log=$rcvTime+" "+$facility[$facCode]+" "+$severity[$sevCode]+" :"+$Matches[2]
        Write-Host $log
        if($file.Length -gt 0){
            $log |Out-File -Encoding default -Append $file
        }
    }
}

使い方

Server-Syslog.ps1

終了時はウインドウごと閉じてください。ctrl+cでもいいんですが、リソースの開放($client.close())がうまく実装できてないので、ポートが占有されたままになります。ウインドウを閉じると解放されました。#どなたかいい方法あったら教えてください。

Server-Syslog.ps1 -f C:\example\syslog.log

でファイルに出力できます。フルパスで指定してください。

Server-Syslog.ps1 -p 10514

でポート番号も指定できます。デフォルトは514です。

コード解説

ざっくりいうと、UDP/514で待ち受けしてアスキーに変換し出力しているだけです。 ただ、SyslogはFacilityとSeverityの表現に若干癖があったりします。

    if($msg -match "((?<=^<)[0-9]*)>(.*)"){
        $sevCode=[int]$Matches[1]%8
        $facCode=([int]$Matches[1]-$sevCode)/8

受けとったバイナリをアスキー文字列に変換した後、$msg -match "((?<=^<)[0-9]*)>(.*)"でPRI(後述)とMessageを取り出しています。 Powershellでは-matchでヒットした文字列が自動変数$Matchesに配列として格納されるので、2~3行目でそれらを加工し、FacilityとSeverityの値を取り出しています。

Syslogフォーマット

例によってRFCを読んだわけではないので間違ってるかもですが、、、

アスキーに直すと以下の感じ。

<PRI>Message
  • PRIはFacility×8+Severityで算出される1~3桁の数字が入ります。(Facilityがlocal0、SeverityがwarningならPRIは16×8+4=132)
  • Messageはログの内容でPRIとは<(0x3c)と>(0x3e)で区切られます。
  • PRIMessageの間にはHeaderが入るらしいが、手持ちのデバイスからはそんなフィールドは見つからなかったのでスルー。