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

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

このコミットは、Go言語の標準ライブラリ log/syslog パッケージにおいて、ローカルのsyslogデーモンへのログ出力形式を修正するものです。特に、ネットワーク経由でのsyslog出力とローカルソケット経由でのsyslog出力で異なる形式を使用するように変更し、ローカルsyslogデーモンが期待する形式に合わせることを目的としています。

コミット

commit 87a6d75012986fb8867b746afcd42f742c119945
Author: Russ Cox <rsc@golang.org>
Date:   Mon Sep 9 16:17:44 2013 -0400

    log/syslog: use alternate format for logging to local syslog daemon
    
    Fixes #5803.
    Is it correct behavior? Who knows.
    
    R=golang-dev, bradfitz, jgc
    CC=golang-dev, jgc
    https://golang.org/cl/13248048

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

https://github.com/golang/go/commit/87a6d75012986fb8867b746afcd42f742c119945

元コミット内容

log/syslog: use alternate format for logging to local syslog daemon Fixes #5803. Is it correct behavior? Who knows.

このコミットは、ローカルのsyslogデーモンへのログ出力に代替フォーマットを使用することを目的としています。Issue #5803を修正するものであり、その動作が「正しい」かどうかは不明であるというコメントが付されています。

変更の背景

このコミットの背景には、Go言語の log/syslog パッケージがローカルのsyslogデーモンと通信する際に、特定の環境(特にUnix系システム)でログメッセージが正しく解釈されない問題がありました。コミットメッセージにある Fixes #5803 が示すように、この変更は特定のバグ報告に対応するものです。

syslogプロトコルにはRFC 3164(BSD syslogプロトコル)とRFC 5424(syslogプロトコル)という主要な標準が存在し、それぞれメッセージのフォーマットに違いがあります。特に、ローカルのsyslogデーモン(例: syslogdrsyslogdsystemd-journald など)は、ネットワーク経由で受信するログと、ローカルソケット(通常 /dev/log/var/run/syslog)経由で受信するログで、期待するフォーマットが異なる場合があります。

従来の log/syslog パッケージは、ネットワーク経由での出力とローカルソケット経由での出力で同じフォーマットを使用しており、これが一部のローカルsyslogデーモンで問題を引き起こしていました。具体的には、ローカルデーモンはホスト名を含まない、より簡潔な形式を期待することがあります。また、タイムスタンプの形式も異なる場合があります。

コミットメッセージの「Is it correct behavior? Who knows.」という記述は、syslogの実装がOSやバージョンによって多様であり、すべての環境で「正しい」とされる単一のフォーマットを特定することが困難であるという当時の状況を反映しています。この変更は、少なくとも特定の一般的なローカルsyslogデーモンとの互換性を向上させるための試みでした。

前提知識の解説

syslogとは

syslogは、システムやアプリケーションからのログメッセージを収集、保存、管理するための標準的なプロトコルです。Unix系OSで広く利用されており、カーネルメッセージ、システムサービス、ユーザーアプリケーションなど、様々なソースからのログを一元的に扱うことができます。

syslogメッセージは通常、以下の要素を含みます。

  • Priority (PRI): メッセージの重要度とファシリティ(メッセージの発生源)を示す数値。
  • Header: タイムスタンプとホスト名が含まれます。
  • Message: ログの本体。

syslogの通信方法

syslogデーモンとの通信には主に以下の方法があります。

  1. UDP/TCP: ネットワーク経由でログを送信します。リモートのsyslogサーバーにログを集約する際に使用されます。
  2. Unixドメインソケット: ローカルホスト内でログを送信する際に使用されます。通常 /dev/log/var/run/syslog といったパスにソケットファイルが存在します。この方法は、ネットワークオーバーヘッドがなく、ローカルシステム内での高速な通信に適しています。

syslogメッセージフォーマットのバリエーション

syslogメッセージのフォーマットには、主に以下の2つのRFC(Request for Comments)で定義されたものがあります。

  • RFC 3164 (BSD syslog protocol): 伝統的なsyslogフォーマットで、以下のような形式です。 <PRI>Timestamp Hostname Tag: Message 例: <34>Oct 11 22:14:15 myhost myapp: This is a log message. タイムスタンプは MMM DD HH:MM:SS 形式(例: Oct 11 22:14:15)です。

  • RFC 5424 (syslog protocol): RFC 3164を拡張した新しいフォーマットで、より構造化されたデータや高精度なタイムスタンプをサポートします。 <PRI>VERSION TIMESTAMP HOSTNAME APP-NAME PROCID MSGID [STRUCTURED-DATA] MSG 例: <1>1 2003-10-11T22:14:15.003Z myhost myapp - ID47 [exampleSDID@32473 iut=\"3\" eventSource=\"Application\"] This is a log message. タイムスタンプはISO 8601形式(例: 2003-10-11T22:14:15.003Z)です。

ローカルのsyslogデーモンは、Unixドメインソケット経由で受信するメッセージに対して、RFC 3164のような簡潔な形式、特にホスト名を含まない形式を期待することがあります。これは、ローカルで発生したログであるため、ホスト名は自明であるという前提があるためです。

Go言語の log/syslog パッケージ

Go言語の log/syslog パッケージは、Goアプリケーションからsyslogデーモンにログを送信するためのインターフェースを提供します。syslog.New() 関数を使って *syslog.Writer を作成し、WriterCrit(), Err(), Warning(), Info(), Debug() などのメソッドを使ってログを書き込みます。

技術的詳細

このコミットの主要な技術的変更点は、log/syslog パッケージがローカルのsyslogデーモンに接続しているかどうかを識別し、その場合にのみ異なるログメッセージフォーマットを使用するようにしたことです。

  1. netConn 構造体の変更: netConn 構造体に local bool フィールドが追加されました。このフィールドは、現在のネットワーク接続がローカルのsyslogデーモンへのものであるかどうかを示すフラグとして機能します。

    type netConn struct {
        local bool // 追加
        conn  net.Conn
    }
    
  2. Writer.connect() メソッドの変更: Writer.connect() メソッド内で、net.Dial を使って接続が確立された際に、w.conn&netConn{conn: c} を代入するように変更されました。これにより、w.connnetConn の値ではなく、*netConn 型のポインタを保持するようになります。これは、後述の writeString メソッドのレシーバ型変更と関連しています。

  3. unixSyslog() 関数の変更: syslog_unix.go 内の unixSyslog() 関数は、Unixドメインソケット経由でローカルのsyslogデーモンに接続する際に呼び出されます。この関数内で netConn を返す際に、新しく追加された local フィールドを true に設定するように変更されました。

    // 変更前: return netConn{conn}, nil
    // 変更後: return &netConn{conn: conn, local: true}, nil
    

    これにより、ローカル接続であることが明示的にマークされます。

  4. netConn.writeString() メソッドの変更と条件付きフォーマット: 最も重要な変更は netConn.writeString() メソッドです。

    • まず、このメソッドのレシーバが netConn から *netConn に変更されました。これにより、writeString メソッド内で netConn インスタンスのフィールド(local など)を変更できるようになります(ただし、このコミットでは local フィールド自体は変更していません)。
    • メソッドの内部に if n.local { ... } という条件分岐が追加されました。
    • n.localtrue の場合(つまりローカル接続の場合)、以下の変更が適用されます。
      • タイムスタンプのフォーマットが time.RFC3339 から time.Stamp に変更されます。time.StampJan _2 15:04:05 のような形式(例: Sep 9 16:17:44)です。これはRFC 3164のタイムスタンプ形式に近いです。
      • fmt.Fprintf のフォーマット文字列から hostname フィールドが削除されます。これにより、ローカルsyslogデーモンに送信されるメッセージにはホスト名が含まれなくなります。
    // 変更前: func (n netConn) writeString(...)
    // 変更後: func (n *netConn) writeString(...)
    
    func (n *netConn) writeString(p Priority, hostname, tag, msg, nl string) error {
        if n.local { // ローカル接続の場合
            // ネットワーク形式と比較して、変更点は以下:
            // 1. time.RFC3339 の代わりに time.Stamp を使用。
            // 2. Fprintf からホスト名フィールドを削除。
            timestamp := time.Now().Format(time.Stamp) // time.Stamp フォーマット
            _, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s%s", // ホスト名なし
                p, timestamp,
                tag, os.Getpid(), msg, nl)
            return err
        }
        // ネットワーク接続の場合 (変更なし)
        timestamp := time.Now().Format(time.RFC3339)
        _, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s",
            p, timestamp, hostname,
            tag, os.Getpid(), msg, nl)
        return err
    }
    
  5. netConn.close() メソッドのレシーバ変更: netConn.close() メソッドのレシーバも netConn から *netConn に変更されました。これは writeString と同様に、netConn がポインタとして扱われるようになったことによる整合性のための変更です。

これらの変更により、log/syslog パッケージは、ローカルのsyslogデーモンに対してはホスト名を含まず、time.Stamp 形式のタイムスタンプを使用する、より簡潔なメッセージフォーマットでログを送信するようになります。これにより、一部のシステムでローカルsyslogがログを正しく処理できるようになることが期待されます。

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

src/pkg/log/syslog/syslog.go

--- a/src/pkg/log/syslog/syslog.go
+++ b/src/pkg/log/syslog/syslog.go
@@ -103,7 +103,8 @@ type serverConn interface {
 }
 
 type netConn struct {
-	conn net.Conn
+	local bool // 追加
+	conn  net.Conn
 }
 
 // New establishes a new connection to the system log daemon.  Each
@@ -163,7 +164,7 @@ func (w *Writer) connect() (err error) {
 		var c net.Conn
 		c, err = net.Dial(w.network, w.raddr)
 		if err == nil {
-			w.conn = netConn{c}
+			w.conn = &netConn{conn: c} // ポインタに変更
 			if w.hostname == "" {
 				w.hostname = c.LocalAddr().String()
 			}
@@ -282,7 +283,17 @@ func (w *Writer) write(p Priority, msg string) (int, error) {
 	return len(msg), nil
 }
 
-func (n netConn) writeString(p Priority, hostname, tag, msg, nl string) error {
+func (n *netConn) writeString(p Priority, hostname, tag, msg, nl string) error { // レシーバをポインタに変更
+	if n.local { // ローカル接続の場合の条件分岐
+		// Compared to the network form below, the changes are:
+		//	1. Use time.Stamp instead of time.RFC3339.
+		//	2. Drop the hostname field from the Fprintf.
+		timestamp := time.Now().Format(time.Stamp) // time.Stamp フォーマット
+		_, err := fmt.Fprintf(n.conn, "<%d>%s %s[%d]: %s%s", // ホスト名なし
+			p, timestamp,
+			tag, os.Getpid(), msg, nl)
+		return err
+	}
 	timestamp := time.Now().Format(time.RFC3339)
 	_, err := fmt.Fprintf(n.conn, "<%d>%s %s %s[%d]: %s%s",
 		p, timestamp, hostname,
@@ -290,7 +311,7 @@ func (n netConn) writeString(p Priority, hostname, tag, msg, nl string) error {
 	return err
 }
 
-func (n netConn) close() error {
+func (n *netConn) close() error { // レシーバをポインタに変更
 	return n.conn.Close()
 }

src/pkg/log/syslog/syslog_unix.go

--- a/src/pkg/log/syslog/syslog_unix.go
+++ b/src/pkg/log/syslog/syslog_unix.go
@@ -23,7 +23,7 @@ func unixSyslog() (conn serverConn, err error) {
 			if err != nil {
 				continue
 			} else {
-				return netConn{conn}, nil
+				return &netConn{conn: conn, local: true}, nil // local: true を設定
 			}
 		}
 	}

コアとなるコードの解説

このコミットの核心は、netConn 構造体に local フラグを追加し、このフラグに基づいて writeString メソッドが異なるログフォーマットを選択するようにした点です。

  1. netConn 構造体への local フィールド追加: netConn は、syslogデーモンとの実際のネットワーク接続(またはUnixドメインソケット接続)をラップする構造体です。ここに local bool フィールドが追加されたことで、この接続がローカルのsyslogデーモンに対するものか、それともリモートのsyslogサーバーに対するものかを区別できるようになりました。

  2. unixSyslog() での local フラグの設定: syslog_unix.go にある unixSyslog() 関数は、Unix系システムでローカルのsyslogデーモンへの接続を確立する際に使用されます。この関数が netConn インスタンスを返す際に、local: true を明示的に設定するようになりました。これにより、ローカルソケット経由の接続が正しく識別されます。

  3. netConn.writeString() の条件付きロジック: writeString メソッドは、実際にログメッセージをsyslogデーモンに書き込む役割を担います。このメソッド内で if n.local { ... } という条件分岐が導入されました。

    • n.localtrue の場合(ローカル接続)、タイムスタンプは time.Stamp フォーマット(例: Sep 9 16:17:44)で整形され、fmt.Fprintf のフォーマット文字列からホスト名 (%s) が削除されます。これにより、メッセージは <PRI>Timestamp Tag[PID]: Message の形式になります。これは、多くのローカルsyslogデーモンが期待する、より簡潔な形式です。
    • n.localfalse の場合(ネットワーク接続)、従来の time.RFC3339 フォーマット(例: 2013-09-09T16:17:44-04:00)とホスト名を含むフォーマットが維持されます。これは、RFC 5424に準拠した、より詳細なネットワークログに適しています。
  4. レシーバのポインタ化: netConn.writeString()netConn.close() のレシーバが値レシーバ (netConn) からポインタレシーバ (*netConn) に変更されました。これは、Writer.connect()w.conn&netConn{conn: c} のようにポインタが代入されるようになったことと整合性を取るためです。Goでは、メソッドがレシーバのフィールドを変更する場合や、レシーバが大きな構造体である場合にポインタレシーバを使用するのが一般的です。この変更により、writeString メソッドが netConn インスタンスの local フィールドを参照できるようになります。

これらの変更により、Goの log/syslog パッケージは、接続先のsyslogデーモンの種類(ローカルかリモートか)に応じて、適切なログメッセージフォーマットを動的に選択できるようになり、互換性の問題が解決されることが期待されます。

関連リンク

参考にした情報源リンク