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

[インデックス 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プロトコルに準拠していなかったという重要な問題がありました。具体的には以下の点が挙げられます。

  1. プライオリティの誤解釈: syslogのプライオリティは、単一の数値ではなく、「ファシリティ(Facility)」と「重要度(Severity)」の2つの要素を組み合わせたものです。しかし、以前のGoの実装では、このプライオリティが単一の数値として扱われており、特にファシリティを設定する方法がありませんでした。このため、Goプログラムから送信されるすべてのsyslogメッセージは、デフォルトのファシリティである LOG_KERN (カーネルメッセージ) として扱われてしまい、ログの分類やフィルタリングが困難になっていました。
  2. メッセージフォーマットの不完全性: 従来のsyslogメッセージフォーマットには、タイムスタンプ、ホスト名、プロセスID (PID) を含む「タグ」といった必須要素が含まれています。しかし、以前のGoの実装では、これらの情報がメッセージから欠落していました。特に、「prefix」という名称で扱われていたフィールドは、実質的にsyslogの「タグ」に相当するものでしたが、その名称が不明瞭であり、PIDの付加も行われていませんでした。
  3. 互換性の問題: 上記のフォーマットの不備により、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> で始まります。
  • HEADER: メッセージのヘッダー部分です。
    • TIMESTAMP: メッセージが生成された時刻。このコミットではRFC3339形式が採用されています。
    • HOSTNAME: メッセージを生成したホストのホスト名。
  • MSG: 実際のログメッセージ本文です。
    • TAG: メッセージを生成したプログラムの名前やプロセスID (PID) を含む識別子。通常は PROGRAM[PID] の形式です。
    • CONTENT: ユーザーが指定する実際のログ内容。

このコミットで修正された新しいフォーマットは、より具体的に以下の形式になります。

<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&facilityMaskWriter の初期化時に設定されたプライオリティからファシリティ部分を抽出し、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 ファイルに集中しています。

  1. 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
    +)
    
  2. 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
     }
    
  3. 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
     }
    
  4. 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
     }
    
  5. 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)
    +}
    
  6. 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メッセージを生成する方法の根本的な改善を反映しています。

  1. プライオリティの正確な表現:

    • severityMaskfacilityMask の導入、およびファシリティ定数の追加により、syslogのプライオリティが単一の数値ではなく、ファシリティと重要度の組み合わせであるという標準的な定義に準拠するようになりました。
    • Writer.writeString メソッド内で (w.priority&facilityMask)|(p&severityMask) というビット演算を行うことで、Writer の初期化時に設定されたファシリティと、個々のログ呼び出しで指定された重要度を正しく結合し、最終的なプライオリティ値を生成します。これにより、ログメッセージが正しいファシリティで分類されるようになり、以前の「すべて LOG_KERN になる」問題が解決されます。
  2. 完全なメッセージフォーマットの実現:

    • Writer 構造体に hostname フィールドが追加され、Dial 関数でホスト名が自動的に取得されるようになりました。
    • prefixtag に変更され、syslogの用語との整合性が向上しました。
    • 最も重要なのは、netConn.writeString メソッド内の fmt.Fprintf のフォーマット文字列が、RFC3339形式のタイムスタンプ、ホスト名、タグ、PIDを含む完全なTraditionalFormat (<PRI>1 TIMESTAMP HOSTNAME TAG[PID]: MSG) に変更された点です。これにより、生成されるログメッセージは、一般的なsyslogデーモンで期待される形式となり、互換性と解析の容易さが大幅に向上します。

これらの変更により、Goアプリケーションは、より標準的で、他のシステムとの連携が容易なsyslogメッセージを生成できるようになりました。

関連リンク

参考にした情報源リンク

  • 上記のRFCドキュメント
  • Go言語の公式ドキュメントとソースコード
  • syslogのプライオリティ、ファシリティ、重要度に関する一般的な技術記事