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)で区切られます。PRI
とMessage
の間にはHeader
が入るらしいが、手持ちのデバイスからはそんなフィールドは見つからなかったのでスルー。