Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 14792] ファイルの概要

このコミットは、Go言語の log/syslog パッケージにおいて、RFC5424で規定されているsyslogメッセージのバージョン番号(1)を削除し、より広範なsyslog実装(特にrsyslog)との互換性を向上させることを目的としています。これにより、メッセージはRFC3164(BSD syslog形式)に準拠するようになります。

コミット

commit c09649890fd48f9854120999a109ba968596a021
Author: John Graham-Cumming <jgc@jgc.org>
Date:   Fri Jan 4 10:21:43 2013 -0500

    log/syslog: remove RFC5424 version number for greater compatibility
    
    RFC5424 specifies a version number (currently 1) after the facility and
    severity in a syslog message (e.g. <7>1 TIMESTAMP ...).  This causes
    rsyslog to fail to parse syslog message because the rest of the message
    is not fully compliant with RFC5424.
    
    For the widest compatibility, drop the version (messages are in the
    RFC3164 BSD syslog format (e.g. <7>TIMESTAMP ...). Have tested this with
    syslog-ng, rsyslog and syslogd.
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/7036050
---
 src/pkg/log/syslog/syslog.go      |  4 ++--
 src/pkg/log/syslog/syslog_test.go | 16 ++++++++--------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/pkg/log/syslog/syslog.go b/src/pkg/log/syslog/syslog.go
index c4ad12ffcd..98b9c5f6e8 100644
--- a/src/pkg/log/syslog/syslog.go
+++ b/src/pkg/log/syslog/syslog.go
@@ -202,14 +202,14 @@ func (w *Writer) writeString(p Priority, s string) (int, error) {
 }
 
 // writeString: generates and writes a syslog formatted string. The
-// format is as follows: <PRI>1 TIMESTAMP HOSTNAME TAG[PID]: MSG
+// format is as follows: <PRI>TIMESTAMP HOSTNAME TAG[PID]: MSG
 func (n netConn) writeString(p Priority, hostname, tag, msg string) (int, error) {
 	nl := ""
 	if len(msg) == 0 || msg[len(msg)-1] != '\n' {
 		nl = "\n"
 	}
 	timestamp := time.Now().Format(time.RFC3339)
-	if _, err := fmt.Fprintf(n.conn, "<%d>1 %s %s %s[%d]: %s%s", p, timestamp, hostname,
+	if _, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s", p, timestamp, hostname,
 		tag, os.Getpid(), msg, nl); err != nil {
 		return 0, err
 	}
diff --git a/src/pkg/log/syslog/syslog_test.go b/src/pkg/log/syslog/syslog_test.go
index 67d7103ee4..d1fb1b2383 100644
--- a/src/pkg/log/syslog/syslog_test.go
+++ b/src/pkg/log/syslog/syslog_test.go
@@ -104,16 +104,15 @@ func TestUDPDial(t *testing.T) {
 	}
 	msg := "udp test"
 	l.Info(msg)
-	expected := fmt.Sprintf("<%d>1 ", LOG_USER+LOG_INFO) + "%s %s syslog_test[%d]: udp test\\n"\n+	expected := fmt.Sprintf("<%d>", LOG_USER+LOG_INFO) + "%s %s syslog_test[%d]: udp test\\n"
 	rcvd := <-done
 	var parsedHostname, timestamp string
 	var pid int
 	if hostname, err := os.Hostname(); err != nil {
 		t.Fatalf("Error retrieving hostname")
 	} else {
-		if n, err := fmt.Sscanf(rcvd, expected, &timestamp, &parsedHostname, &pid); n != 3 ||
-			err != nil || hostname != parsedHostname {
-			t.Fatalf("s.Info() = '%q', didn't match '%q'", rcvd, expected)
+		if n, err := fmt.Sscanf(rcvd, expected, &timestamp, &parsedHostname, &pid); n != 3 || err != nil || hostname != parsedHostname {
+			t.Fatalf("'%q', didn't match '%q' (%d, %s)", rcvd, expected, n, err)
 		}
 	}
 }
@@ -146,12 +145,13 @@ func TestWrite(t *testing.T) {
 			t.Fatalf("WriteString() failed: %s", err)
 		}
 		rcvd := <-done
-		test.exp = fmt.Sprintf("<%d>1 ", test.pri) + test.exp
+		test.exp = fmt.Sprintf("<%d>", test.pri) + test.exp
 		var parsedHostname, timestamp string
 		var pid int
-		if n, err := fmt.Sscanf(rcvd, test.exp, &timestamp, &parsedHostname, &pid); n != 3 ||
-			err != nil || hostname != parsedHostname {
-			t.Fatalf("s.Info() = '%q', didn't match '%q'", rcvd, test.exp)
+		if n, err := fmt.Sscanf(rcvd, test.exp, &timestamp, &parsedHostname,
+			&pid); n != 3 || err != nil || hostname != parsedHostname {
+			t.Fatalf("'%q', didn't match '%q' (%d %s)", rcvd, test.exp,
+				n, err)
 		}
 	}
 }

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/c09649890fd48f9854120999a109ba968596a021

元コミット内容

log/syslog パッケージが生成するsyslogメッセージから、RFC5424で規定されているバージョン番号(1)を削除し、より広範なsyslog実装との互換性を確保する。特に、rsyslogがこのバージョン番号を含むメッセージを正しくパースできない問題に対処する。

変更の背景

この変更の背景には、syslogプロトコルの標準化と、その実装における互換性の問題があります。

元々、syslogはRFC3164("The BSD Syslog Protocol")で定義されたシンプルなプロトコルでした。この形式は広く普及し、多くのシステムで利用されてきました。しかし、RFC3164はいくつかの点で曖昧さや機能不足があり、より堅牢で拡張性のあるプロトコルとしてRFC5424("The Syslog Protocol")が策定されました。

RFC5424は、メッセージフォーマットに構造化データやバージョン番号などの新しい要素を導入しました。Go言語の log/syslog パッケージは、当初RFC5424の仕様に従い、プライオリティ(<PRI>)の直後にバージョン番号 1 を含んだ形式でメッセージを生成していました。例えば、<7>1 TIMESTAMP HOSTNAME TAG[PID]: MSG のような形式です。

しかし、コミットメッセージに記載されているように、一部のsyslogデーモン、特に rsyslog は、このRFC5424のバージョン番号を含むメッセージを正しくパースできないという問題がありました。これは、rsyslogがRFC5424の他の部分に完全に準拠していない場合や、特定のパースロジックに依存している場合に発生し得ます。結果として、Goアプリケーションから出力されたsyslogメッセージが、rsyslogによって適切に処理されず、ログが欠落したり、誤って解釈されたりする可能性がありました。

この互換性の問題を解決し、Goの log/syslog パッケージがより多くのsyslog実装(syslog-ng, rsyslog, syslogdなど)で問題なく動作するようにするために、RFC5424のバージョン番号を削除し、RFC3164の形式に回帰するという決定がなされました。RFC3164形式は、<PRI>TIMESTAMP HOSTNAME TAG[PID]: MSG のように、プライオリティの直後にタイムスタンプが続く形式であり、これが最も広く互換性のある形式と見なされていました。

前提知識の解説

Syslog

Syslogは、システムやネットワークデバイスからログメッセージを収集するための標準的なプロトコルです。異なる種類のシステムやアプリケーションが、標準化された方法でイベントログを生成し、中央のログサーバーに送信することを可能にします。これにより、ログの一元管理、監視、分析が容易になります。

Syslogメッセージは、通常、以下の要素で構成されます。

  • PRI (Priority): メッセージの重要度(Severity)と発生源(Facility)を示す数値。<PRI> の形式で表現されます。
  • Header: タイムスタンプ、ホスト名、アプリケーション名、プロセスIDなどが含まれます。
  • Msg (Message): 実際のログメッセージ本文。

RFC3164 (BSD Syslog Protocol)

RFC3164は、伝統的なBSDスタイルのsyslogプロトコルを定義しています。このプロトコルはシンプルで広く普及しており、多くの既存システムがこの形式をサポートしています。

RFC3164のメッセージフォーマットの一般的な例は以下の通りです。

<PRI>MMM DD HH:MM:SS HOSTNAME TAG: MSG

例: <6>Jul 24 10:30:00 myhost kernel: eth0 link up

この形式には、バージョン番号の概念はありません。

RFC5424 (The Syslog Protocol)

RFC5424は、RFC3164の限界を克服するために策定された、より現代的で堅牢なsyslogプロトコルです。RFC5424は、以下のような新機能や改善を導入しました。

  • バージョン番号: メッセージフォーマットのバージョンを示す数値。これにより、将来のプロトコル変更に対応できます。
  • 構造化データ (Structured Data): キーと値のペアで構成される構造化された情報をメッセージに含めることができます。これにより、ログのパースと分析が容易になります。
  • 高精度タイムスタンプ: ミリ秒以下の精度を持つタイムスタンプをサポートします。
  • メッセージID: メッセージの種類を識別するためのID。
  • プロトコル要素の明確な定義: 各フィールドの長さやエンコーディングなどがより厳密に定義されています。

RFC5424のメッセージフォーマットの一般的な例は以下の通りです。

<PRI>VERSION TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [STRUCTURED-DATA] MSG

例: <165>1 2003-10-11T22:14:15.003Z mymachine.example.com evntslog - ID47 [exampleSDID@32473 iut="3" eventSource="Application" eventID="1011"] An application event log entry...

このコミットで問題となったのは、<PRI> の直後に続く VERSION フィールド(この場合は 1)です。

Syslogデーモン

Syslogデーモンは、システム上で動作し、ログメッセージを受信、処理、転送、保存するソフトウェアです。主要なsyslogデーモンには以下のようなものがあります。

  • rsyslog: 非常に高機能で高性能なsyslogデーモン。多くのLinuxディストリビューションでデフォルトとして採用されています。柔軟な設定が可能で、データベースへのログ保存やリモート転送など、多様な機能を提供します。
  • syslog-ng: rsyslogと同様に高機能なsyslogデーモン。柔軟なフィルタリング、ルーティング、フォーマット変換機能を提供します。
  • syslogd: 伝統的なUnix系システムで利用されてきた基本的なsyslogデーモン。機能は限定的ですが、シンプルで軽量です。

これらのデーモンは、RFC3164とRFC5424の両方をサポートするように設計されていますが、実装の詳細や設定によっては、特定のフォーマットのメッセージを正しく処理できない場合があります。特に、RFC5424の新しい要素(バージョン番号など)が導入された初期の段階では、互換性の問題が発生しやすかったと考えられます。

技術的詳細

このコミットの技術的な核心は、Goの log/syslog パッケージが生成するsyslogメッセージのフォーマットから、RFC5424で定義されているバージョン番号 1 を削除することです。

元のコードでは、fmt.Fprintf を使用してsyslogメッセージをフォーマットする際に、プライオリティ(p)の直後にハードコードされた 1 を挿入していました。これは、RFC5424のバージョンフィールドに 1 を設定することに対応していました。

変更前:

fmt.Fprintf(n.conn, "<%d>1 %s %s %s[%d]: %s%s", p, timestamp, hostname, tag, os.Getpid(), msg, nl)

この行は、例えばプライオリティが 7 の場合、<7>1 ... という形式のメッセージを生成します。

変更後:

fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s", p, timestamp, hostname, tag, os.Getpid(), msg, nl)

変更後では、1 が削除され、プライオリティの直後にタイムスタンプが続く形式になります。例えば、プライオリティが 7 の場合、<7>TIMESTAMP ... という形式のメッセージを生成します。これは、RFC3164(BSD syslog形式)に準拠したフォーマットです。

この変更により、Goの log/syslog パッケージが生成するメッセージは、RFC5424の厳密な要件を満たさないものの、RFC3164形式として広く認識され、rsyslogを含む多くの既存のsyslogデーモンで問題なくパースされるようになります。rsyslogがRFC5424のバージョン番号を期待しつつ、その後のメッセージ構造が完全にRFC5424に準拠していない場合にパースエラーを起こすという問題が、この変更によって回避されます。

テストコード (syslog_test.go) も、期待されるメッセージフォーマットからバージョン番号 1 を削除するように更新されています。これにより、変更されたメッセージフォーマットが正しく生成されていることを検証できるようになります。fmt.Sscanf を使用して受信したメッセージをパースする際の期待値も、バージョン番号を含まない形式に修正されています。

コアとなるコードの変更箇所

このコミットでは、以下の2つのファイルが変更されています。

  1. src/pkg/log/syslog/syslog.go: syslogメッセージの生成ロジックが含まれるファイル。

    • func (n netConn) writeString(...) メソッド内の fmt.Fprintf のフォーマット文字列が変更されました。
      • 変更前: fmt.Fprintf(n.conn, "<%d>1 %s %s %s[%d]: %s%s", ...)
      • 変更後: fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s", ...)
      • 具体的には、"<%d>1 " から "<%d>" へと変更され、プライオリティの直後にあった 1 が削除されました。
    • 関連するコメントも更新され、生成されるフォーマットが <PRI>TIMESTAMP HOSTNAME TAG[PID]: MSG であることが明記されました。
  2. src/pkg/log/syslog/syslog_test.go: syslogパッケージのテストファイル。

    • TestUDPDial 関数内の expected 変数の初期化が変更されました。
      • 変更前: expected := fmt.Sprintf("<%d>1 ", LOG_USER+LOG_INFO) + "%s %s syslog_test[%d]: udp test\\n"
      • 変更後: expected := fmt.Sprintf("<%d>", LOG_USER+LOG_INFO) + "%s %s syslog_test[%d]: udp test\\n"
      • ここでも、期待される文字列から 1 が削除されています。
    • TestWrite 関数内のループ内で、test.exp の更新ロジックが変更されました。
      • 変更前: test.exp = fmt.Sprintf("<%d>1 ", test.pri) + test.exp
      • 変更後: test.Sprintf("<%d>", test.pri) + test.exp
      • 同様に、テストケースの期待値から 1 が削除されています。
    • fmt.Sscanf の呼び出しにおけるエラーメッセージも、より詳細な情報を含むように修正されています。

コアとなるコードの解説

src/pkg/log/syslog/syslog.go の変更

syslog.go の変更は、Goの log/syslog パッケージが実際にsyslogメッセージをネットワーク経由で送信する際のフォーマットを直接変更するものです。

func (n netConn) writeString(p Priority, hostname, tag, msg string) (int, error) は、syslogメッセージの各要素(プライオリティ、ホスト名、タグ、メッセージ本文など)を受け取り、それらを組み合わせて最終的なsyslogメッセージ文字列を生成し、ネットワーク接続 (n.conn) に書き込む役割を担っています。

変更前のコードでは、fmt.Fprintf のフォーマット文字列に "%d>1 " が含まれていました。これは、プライオリティの数値 (%d) の直後にリテラルの 1 を出力することを意味します。この 1 がRFC5424のバージョン番号に相当していました。

変更後のコードでは、この 1 が削除され、フォーマット文字列は "%d>" となっています。これにより、生成されるメッセージはプライオリティの直後にタイムスタンプが続く形式となり、RFC3164の仕様に合致するようになります。

この変更は、Goアプリケーションが生成するsyslogメッセージの「ワイヤーフォーマット」を直接変更するため、最も重要な変更点です。これにより、rsyslogのような特定のsyslogデーモンが、RFC5424のバージョン番号の存在によって引き起こされていたパースエラーを回避できるようになります。

src/pkg/log/syslog/syslog_test.go の変更

syslog_test.go の変更は、syslog.go で行われたフォーマット変更が正しく反映されていることを検証するためのものです。

TestUDPDialTestWrite の両方のテスト関数では、Goの log/syslog パッケージが生成するsyslogメッセージを実際に受信し、その内容が期待されるフォーマットと一致するかどうかを fmt.Sscanf を使って検証しています。

fmt.Sscanf は、指定されたフォーマット文字列に基づいて入力文字列をパースし、変数を埋める関数です。syslog.go でメッセージフォーマットから 1 が削除されたため、テストコードで期待されるフォーマット文字列からも同様に 1 を削除する必要があります。

例えば、TestUDPDialexpected 変数の定義では、以前は fmt.Sprintf("<%d>1 ", ...) となっていましたが、これが fmt.Sprintf("<%d>", ...) に変更されました。これにより、テストはバージョン番号を含まないメッセージを期待するようになり、実際の出力と一致するようになります。

これらのテストの変更は、コードの動作変更が意図通りであり、将来のリグレッションを防ぐための重要なステップです。テストが更新されなければ、syslog.go の変更後にテストが失敗するか、あるいは誤ったフォーマットのメッセージが生成されていてもテストがパスしてしまう可能性がありました。

関連リンク

参考にした情報源リンク