[インデックス 14506] ファイルの概要
このコミットは、Go言語の log/syslog
パッケージにおけるsyslogメッセージのフォーマットを修正し、標準的なsyslogプロトコル(特にTraditionalFormat)に準拠させることを目的としています。これにより、生成されるログメッセージが、syslogデーモンや他のログ解析ツールで正しく解釈されるようになります。
コミット
commit 4228eb791523d4641b5c1c2434b347f596f6760e
Author: John Graham-Cumming <jgc@jgc.org>
Date: Tue Nov 27 10:21:43 2012 -0500
log/syslog: correct message format
The syslog implementation was not correctly implementing the
traditional syslog format because it had a confused notion of
'priority'. syslog priority is not a single number but is, in
fact, the combination of a facility number and a severity. The
previous Go syslog implementation had a single Priority that
appeared to be the syslog severity and no way of setting the
facility. That meant that all syslog messages from Go
programs appeared to have a facility of 0 (LOG_KERN) which
meant they all appeared to come from the kernel.
Also, the 'prefix' was in fact the syslog tag (changed the
internal name for clarity as the term tag is more widely used)
and the timestamp and hostname values were missing from
messages.
With this change syslog messages are generated in the correct
format with facility and severity combined into a priority,
the timestamp in RFC3339 format, the hostname, the tag (with
the PID in [] appened) and the message.
The format is now:
<PRI>1 TIMESTAMP HOSTNAME TAG[PID]: MSG
The TIMESTAMP, HOSTNAME and PID fields are filled in
automatically by the package. The TAG and the MSG are supplied
by the user. This is what rsyslogd calls TraditionalFormat and
should be compatible with multiple systems.
R=rsc, jgc, 0xjnml, mikioh.mikioh, bradfitz
CC=golang-dev
https://golang.org/cl/6782118
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/4228eb791523d4641b5c1c2434b347f596f6760e
元コミット内容
log/syslog: correct message format
このコミットは、Go言語の log/syslog
パッケージが従来のsyslogフォーマットを正しく実装していなかった問題を修正します。以前の実装では、「プライオリティ」の概念が混乱しており、syslogプライオリティが単一の数値ではなく、ファシリティ番号と重要度(severity)の組み合わせであるという事実を考慮していませんでした。その結果、Goプログラムからのすべてのsyslogメッセージは、ファシリティが0(LOG_KERN)であるかのように見え、すべてカーネルからのメッセージとして認識されていました。
また、以前の「prefix」は実際にはsyslogの「タグ」であり(明確化のために内部名を変更)、メッセージからタイムスタンプとホスト名の値が欠落していました。
この変更により、syslogメッセージは、ファシリティと重要度がプライオリティに結合され、RFC3339形式のタイムスタンプ、ホスト名、タグ(PIDが[]で付加される)、およびメッセージを含む正しいフォーマットで生成されるようになります。
新しいフォーマットは以下の通りです。
<PRI>1 TIMESTAMP HOSTNAME TAG[PID]: MSG
TIMESTAMP、HOSTNAME、およびPIDフィールドはパッケージによって自動的に入力されます。TAGとMSGはユーザーによって提供されます。これはrsyslogdがTraditionalFormatと呼ぶものであり、複数のシステムと互換性があるはずです。
変更の背景
このコミットが行われた背景には、Go言語の log/syslog
パッケージが生成するログメッセージが、標準的なsyslogプロトコルに準拠していなかったという重要な問題がありました。具体的には以下の点が挙げられます。
- プライオリティの誤解釈: syslogのプライオリティは、単一の数値ではなく、「ファシリティ(Facility)」と「重要度(Severity)」の2つの要素を組み合わせたものです。しかし、以前のGoの実装では、このプライオリティが単一の数値として扱われており、特にファシリティを設定する方法がありませんでした。このため、Goプログラムから送信されるすべてのsyslogメッセージは、デフォルトのファシリティである
LOG_KERN
(カーネルメッセージ) として扱われてしまい、ログの分類やフィルタリングが困難になっていました。 - メッセージフォーマットの不完全性: 従来のsyslogメッセージフォーマットには、タイムスタンプ、ホスト名、プロセスID (PID) を含む「タグ」といった必須要素が含まれています。しかし、以前のGoの実装では、これらの情報がメッセージから欠落していました。特に、「prefix」という名称で扱われていたフィールドは、実質的にsyslogの「タグ」に相当するものでしたが、その名称が不明瞭であり、PIDの付加も行われていませんでした。
- 互換性の問題: 上記のフォーマットの不備により、Goプログラムが生成するsyslogメッセージは、rsyslogdなどの一般的なsyslogデーモンやログ管理システムで正しく解析・処理されない可能性がありました。これにより、Goアプリケーションのログがシステム全体のログと統合されず、運用上の問題を引き起こすことが懸念されました。
これらの問題を解決し、Goのsyslogパッケージがより堅牢で互換性の高いログメッセージを生成できるようにするために、このコミットによる修正が必要とされました。
前提知識の解説
このコミットを理解するためには、以下の前提知識が役立ちます。
1. Syslogとは
Syslogは、システムやアプリケーションからのログメッセージを収集、保存、転送するための標準的なプロトコルです。UNIX系OSで広く利用されており、ネットワーク経由でログを中央のログサーバーに集約することも可能です。
2. Syslogメッセージの構造(TraditionalFormat / BSD Syslog Protocol)
このコミットで言及されている「TraditionalFormat」は、RFC 3164で定義されているBSD Syslog Protocolのメッセージフォーマットを指します。基本的な構造は以下の通りです。
<PRI>HEADER MSG
<PRI>
(Priority): ログメッセージのプライオリティを示す数値です。山括弧 (<>
) で囲まれます。この数値は、ファシリティ (Facility) と 重要度 (Severity) の組み合わせによって決定されます。- ファシリティ (Facility): メッセージを生成したプログラムの種類を示します。例えば、カーネルメッセージ (
LOG_KERN
)、ユーザーレベルのメッセージ (LOG_USER
)、メールシステム (LOG_MAIL
) などがあります。0から23までの値が割り当てられます。 - 重要度 (Severity): メッセージの緊急度や重要性を示します。
LOG_EMERG
(0): システムが使用不能LOG_ALERT
(1): 直ちに行動が必要LOG_CRIT
(2): 致命的な状態LOG_ERR
(3): エラー状態LOG_WARNING
(4): 警告状態LOG_NOTICE
(5): 通常だが重要な状態LOG_INFO
(6): 情報メッセージLOG_DEBUG
(7): デバッグレベルメッセージ
- プライオリティの計算:
PRI = Facility * 8 + Severity
例えば、LOG_USER
(ファシリティ1) のLOG_INFO
(重要度6) のメッセージの場合、プライオリティは1 * 8 + 6 = 14
となり、メッセージは<14>
で始まります。
- ファシリティ (Facility): メッセージを生成したプログラムの種類を示します。例えば、カーネルメッセージ (
HEADER
: メッセージのヘッダー部分です。- TIMESTAMP: メッセージが生成された時刻。このコミットではRFC3339形式が採用されています。
- HOSTNAME: メッセージを生成したホストのホスト名。
MSG
: 実際のログメッセージ本文です。- TAG: メッセージを生成したプログラムの名前やプロセスID (PID) を含む識別子。通常は
PROGRAM[PID]
の形式です。 - CONTENT: ユーザーが指定する実際のログ内容。
- TAG: メッセージを生成したプログラムの名前やプロセスID (PID) を含む識別子。通常は
このコミットで修正された新しいフォーマットは、より具体的に以下の形式になります。
<PRI>1 TIMESTAMP HOSTNAME TAG[PID]: MSG
ここで、1
はRFC 5424 (Syslog Protocol) で導入されたバージョン番号ですが、TraditionalFormatの文脈でこのコミットが言及しているのは、おそらくRFC 3164の拡張または一般的な実装慣行としての形式を指していると考えられます。RFC 3164自体にはバージョン番号の概念はありませんが、多くのsyslogデーモンがこの形式を許容または期待することがあります。
3. RFC3339形式のタイムスタンプ
RFC3339は、日付と時刻のインターネット標準フォーマットです。ISO 8601をベースにしており、タイムゾーン情報を含みます。例: 2012-11-27T10:21:43-05:00
4. Go言語の log
パッケージと log/syslog
パッケージ
log
パッケージ: Go言語の標準ライブラリに含まれる基本的なロギングパッケージです。シンプルなAPIを提供し、出力先(標準出力、ファイルなど)を設定できます。log/syslog
パッケージ:log
パッケージの拡張として、syslogデーモンへのログ出力を可能にするパッケージです。このコミットの対象となっています。
技術的詳細
このコミットにおける技術的な変更点は多岐にわたりますが、主にsyslogメッセージのフォーマットとプライオリティの扱いに関するものです。
1. Priority
型の再定義とマスクの導入
- ファシリティ定数の追加: 以前は重要度(Severity)のみが定義されていましたが、
LOG_KERN
からLOG_LOCAL7
までのファシリティ定数が追加されました。これらの定数は、iota << 3
を使用して定義されており、プライオリティ値のビット構造において、重要度とファシリティが異なるビット位置に格納されることを示唆しています。具体的には、重要度が下位3ビット(0-7)、ファシリティがそれより上位のビット(3ビット目から)を使用するように設計されています。 - マスク定数の導入:
severityMask = 0x07
(バイナリで00000111
) とfacilityMask = 0xf8
(バイナリで11111000
) が導入されました。これらは、プライオリティ値から重要度とファシリティをそれぞれ抽出したり、組み合わせたりするために使用されます。
2. Writer
構造体の変更
prefix string
フィールドがtag string
に変更されました。これは、syslogの用語に合わせて、より明確な名称にしたものです。hostname string
フィールドが追加されました。これにより、ログメッセージにホスト名を含めることができるようになりました。
3. serverConn
インターフェースの変更
writeBytes
メソッドが削除され、writeString
メソッドのシグネチャがwriteString(p Priority, hostname, tag, s string) (int, error)
に変更されました。これにより、メッセージの書き込み時にホスト名とタグが直接渡されるようになりました。
4. Dial
関数の改善
- プライオリティの検証:
Dial
関数に、引数として渡されるpriority
が有効な範囲内であるかどうかのチェックが追加されました (priority < 0 || priority > LOG_LOCAL7|LOG_DEBUG
)。これにより、不正なプライオリティ値が設定されることを防ぎます。 - ホスト名の自動取得:
os.Hostname()
を使用してホスト名を自動的に取得するロジックが追加されました。UNIXドメインソケットを使用する場合でホスト名が取得できない場合は "localhost" が、ネットワーク接続の場合は接続のローカルアドレスが使用されます。 - タグのデフォルト値:
tag
が空文字列の場合、os.Args[0]
(実行中のプログラム名) がデフォルト値として使用されるようになりました。
5. Writer.writeString
メソッドのロジック変更
このメソッドは、Writer
に設定されたファシリティと、ログメッセージごとに指定される重要度を組み合わせて、最終的なプライオリティ値を生成する中心的なロジックを含んでいます。
func (w *Writer) writeString(p Priority, s string) (int, error) {
return w.conn.writeString((w.priority&facilityMask)|(p&severityMask),
w.hostname, w.tag, s)
}
ここで、w.priority&facilityMask
は Writer
の初期化時に設定されたプライオリティからファシリティ部分を抽出し、p&severityMask
は引数 p
(メッセージの重要度) から重要度部分を抽出します。これらをビットOR演算子 (|
) で組み合わせることで、正しいファシリティと重要度を持つプライオリティ値が生成されます。
6. netConn.writeString
メソッドでのメッセージフォーマット生成
このメソッドは、実際にsyslogメッセージの文字列を構築し、ネットワーク接続に書き込む役割を担います。
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,
tag, os.Getpid(), msg, nl); err != nil {
return 0, err
}
return len(msg), nil
}
- タイムスタンプの追加:
time.Now().Format(time.RFC3339)
を使用して、現在の時刻をRFC3339形式で取得し、メッセージに含めるようになりました。 - 新しいフォーマット文字列:
fmt.Fprintf
のフォーマット文字列が、コミットメッセージで示された"<PRI>1 TIMESTAMP HOSTNAME TAG[PID]: MSG"
形式に厳密に一致するように変更されました。<%d>
: プライオリティ1
: バージョン番号(TraditionalFormatの文脈での慣習的な追加)%s %s
: タイムスタンプとホスト名%s[%d]
: タグとプロセスID (PID)%s%s
: メッセージ本文と改行
7. テストコードの更新
syslog_test.go
も、新しいフォーマットとプライオリティの扱いに合わせて大幅に更新されました。特に、生成されるログメッセージの文字列を解析し、タイムスタンプ、ホスト名、PIDが正しく含まれているかを検証するロジックが追加されています。これにより、変更が正しく機能していることが保証されます。
コアとなるコードの変更箇所
このコミットのコアとなる変更は、主に src/pkg/log/syslog/syslog.go
ファイルに集中しています。
-
Priority
型に関連する定数の追加と変更:const severityMask = 0x07
const facilityMask = 0xf8
- ファシリティ定数 (
LOG_KERN
からLOG_LOCAL7
) の追加と定義方法 (iota << 3
)。
--- a/src/pkg/log/syslog/syslog.go +++ b/src/pkg/log/syslog/syslog.go @@ -32,16 +42,47 @@ const ( LOG_DEBUG ) +const ( + // Facility. + + // From /usr/include/sys/syslog.h. + // These are the same up to LOG_FTP on Linux, BSD, and OS X. + LOG_KERN Priority = iota << 3 + LOG_USER + LOG_MAIL + LOG_DAEMON + LOG_AUTH + LOG_SYSLOG + LOG_LPR + LOG_NEWS + LOG_UUCP + LOG_CRON + LOG_AUTHPRIV + LOG_FTP + _ // unused + _ // unused + _ // unused + _ // unused + LOG_LOCAL0 + LOG_LOCAL1 + LOG_LOCAL2 + LOG_LOCAL3 + LOG_LOCAL4 + LOG_LOCAL5 + LOG_LOCAL6 + LOG_LOCAL7 +)
-
Writer
構造体のフィールド変更:prefix
からtag
への変更、hostname
の追加。
--- a/src/pkg/log/syslog/syslog.go +++ b/src/pkg/log/syslog/syslog.go @@ -40,8 +40,9 @@ const ( // A Writer is a connection to a syslog server. type Writer struct { priority Priority - prefix string + tag string + hostname string conn serverConn }
-
serverConn
インターフェースのwriteString
シグネチャ変更:--- a/src/pkg/log/syslog/syslog.go +++ b/src/pkg/log/syslog/syslog.go @@ -50,7 +51,7 @@ type Writer struct { } type serverConn interface { - writeBytes(p Priority, prefix string, b []byte) (int, error) - writeString(p Priority, prefix string, s string) (int, error) + writeString(p Priority, hostname, tag, s string) (int, error) close() error }
-
Dial
関数のロジック変更:- プライオリティ検証、ホスト名取得、タグのデフォルト設定。
--- a/src/pkg/log/syslog/syslog.go +++ b/src/pkg/log/syslog/syslog.go @@ -64,23 +90,27 @@ func New(priority Priority, tag string) (w *Writer, err error) { // Dial establishes a connection to a log daemon by connecting to // address raddr on the network net. Each write to the returned // writer sends a log message with the given facility, severity and // tag. func Dial(network, raddr string, priority Priority, tag string) (w *Writer, err error) { - if prefix == "" { - prefix = os.Args[0] + if priority < 0 || priority > LOG_LOCAL7|LOG_DEBUG { + return nil, errors.New("log/syslog: invalid priority") + } + + if tag == "" { + tag = os.Args[0] } + + hostname, _ := os.Hostname() + var conn serverConn if network == "" { conn, err = unixSyslog() + if hostname == "" { + hostname = "localhost" + } } else { var c net.Conn c, err = net.Dial(network, raddr) conn = netConn{c} + if hostname == "" { + hostname = c.LocalAddr().String() + } + } + if err != nil { + return nil, err } - return &Writer{priority, prefix, conn}, err + + return &Writer{priority: priority, tag: tag, hostname: hostname, conn: conn}, nil }
-
Writer.writeString
メソッドでのプライオリティ結合ロジック:--- a/src/pkg/log/syslog/syslog.go +++ b/src/pkg/log/syslog/syslog.go @@ -130,7 +140,8 @@ func (w *Writer) Write(b []byte) (int, error) { } func (w *Writer) Close() error { return w.conn.close() } - -// Emerg logs a message using the LOG_EMERG priority. +// writeString: generates and writes a syslog formatted string. The +// format is as follows: <PRI>1 TIMESTAMP HOSTNAME TAG[PID]: MSG +func (w *Writer) writeString(p Priority, s string) (int, error) { + return w.conn.writeString((w.priority&facilityMask)|(p&severityMask), + w.hostname, w.tag, s) +}
-
netConn.writeString
メソッドでのメッセージフォーマット生成:--- a/src/pkg/log/syslog/syslog.go +++ b/src/pkg/log/syslog/syslog.go @@ -141,19 +152,19 @@ func (w *Writer) Debug(m string) (err error) { return err } -func (n netConn) writeBytes(p Priority, prefix string, b []byte) (int, error) { - nl := "" - if len(b) == 0 || b[len(b)-1] != '\n' { - nl = "\n" - } - _, err := fmt.Fprintf(n.conn, "<%d>%s: %s%s", p, prefix, b, nl) - if err != nil { - return 0, err - } - return len(b), nil -} - -func (n netConn) writeString(p Priority, prefix string, s string) (int, error) { +// writeString: generates and writes a syslog formatted string. The +// format is as follows: <PRI>1 TIMESTAMP HOSTNAME TAG[PID]: MSG +func (n netConn) writeString(p Priority, hostname, tag, msg string) (int, error) { nl := "" - if len(s) == 0 || s[len(s)-1] != '\n' { + if len(msg) == 0 || msg[len(msg)-1] != '\n' { nl = "\n" } - _, err := fmt.Fprintf(n.conn, "<%d>%s: %s%s", p, prefix, s, nl) - if err != nil { + timestamp := time.Now().Format(time.RFC3339) + if _, err := fmt.Fprintf(n.conn, "<%d>1 %s %s %s[%d]: %s%s", p, timestamp, hostname, + tag, os.Getpid(), msg, nl); err != nil { return 0, err } - return len(s), nil + return len(msg), nil }
コアとなるコードの解説
上記の変更箇所は、Goの log/syslog
パッケージがsyslogメッセージを生成する方法の根本的な改善を反映しています。
-
プライオリティの正確な表現:
severityMask
とfacilityMask
の導入、およびファシリティ定数の追加により、syslogのプライオリティが単一の数値ではなく、ファシリティと重要度の組み合わせであるという標準的な定義に準拠するようになりました。Writer.writeString
メソッド内で(w.priority&facilityMask)|(p&severityMask)
というビット演算を行うことで、Writer
の初期化時に設定されたファシリティと、個々のログ呼び出しで指定された重要度を正しく結合し、最終的なプライオリティ値を生成します。これにより、ログメッセージが正しいファシリティで分類されるようになり、以前の「すべてLOG_KERN
になる」問題が解決されます。
-
完全なメッセージフォーマットの実現:
Writer
構造体にhostname
フィールドが追加され、Dial
関数でホスト名が自動的に取得されるようになりました。prefix
がtag
に変更され、syslogの用語との整合性が向上しました。- 最も重要なのは、
netConn.writeString
メソッド内のfmt.Fprintf
のフォーマット文字列が、RFC3339形式のタイムスタンプ、ホスト名、タグ、PIDを含む完全なTraditionalFormat (<PRI>1 TIMESTAMP HOSTNAME TAG[PID]: MSG
) に変更された点です。これにより、生成されるログメッセージは、一般的なsyslogデーモンで期待される形式となり、互換性と解析の容易さが大幅に向上します。
これらの変更により、Goアプリケーションは、より標準的で、他のシステムとの連携が容易なsyslogメッセージを生成できるようになりました。
関連リンク
- RFC 3164 - The BSD Syslog Protocol: https://datatracker.ietf.org/doc/html/rfc3164
- RFC 3339 - Date and Time on the Internet: Timestamps: https://datatracker.ietf.org/doc/html/rfc3339
- RFC 5424 - The Syslog Protocol: https://datatracker.ietf.org/doc/html/rfc5424
- Go言語
log/syslog
パッケージのドキュメント (変更後のもの): https://pkg.go.dev/log/syslog
参考にした情報源リンク
- 上記のRFCドキュメント
- Go言語の公式ドキュメントとソースコード
- syslogのプライオリティ、ファシリティ、重要度に関する一般的な技術記事