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

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

このコミットは、Go言語の標準ライブラリである log/syslog パッケージにおけるバグ修正です。具体的には、syslog ライターがメッセージを書き込む際に fmt.Fprintf から返されるエラーを適切に報告するように変更されています。これにより、syslog への書き込みが失敗した場合に、呼び出し元がそのエラーを検知できるようになります。

コミット

commit 4c2df76b7dd9439566a71f8bb339295a5ef57be6
Author: Rob Pike <r@golang.org>
Date:   Wed May 22 11:03:10 2013 -0700

    log/syslog: report errors from Fprintf
    Thanks to chiparus for identifying this.

    Fixes #5541.

    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/9658043

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

https://github.com/golang/go/commit/4c2df76b7dd9439566a71f8bb339295a5ef57be6

元コミット内容

log/syslog: report errors from Fprintf Thanks to chiparus for identifying this.

Fixes #5541.

R=golang-dev, iant CC=golang-dev https://golang.org/cl/9658043

変更の背景

この変更は、Go言語の log/syslog パッケージにおいて、syslog メッセージの書き込み処理中に発生する可能性のあるエラーが適切に報告されないという問題(Issue #5541)を修正するために行われました。

従来のコードでは、Writer 型の write メソッド内で fmt.Fprintf を呼び出して syslog コネクションにメッセージを書き込んでいましたが、fmt.Fprintf が返す (int, error) の戻り値を無視し、常に (len(msg), nil) を返していました。これは、書き込み操作が実際に失敗した場合でも、呼び出し元には成功したかのように見えてしまうという潜在的なバグを意味します。

例えば、ネットワークの問題や syslog デーモン側の問題により書き込みが失敗した場合でも、write メソッドはエラーを返さなかったため、アプリケーションはログが正常に送信されたと誤解する可能性がありました。このコミットは、このエラーハンドリングの欠陥を修正し、fmt.Fprintf が返す実際のエラーを呼び出し元に透過的に伝えることを目的としています。

前提知識の解説

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

log/syslog パッケージは、GoプログラムからUnix系システムで広く使われているシステムログデーモンである syslog にログメッセージを送信するための機能を提供します。syslog は、システムイベント、エラー、デバッグ情報などを一元的に管理するための標準的なプロトコルです。

fmt.Fprintf 関数

fmt.Fprintf は、Go言語の fmt パッケージに含まれる関数で、指定された io.Writer インターフェースを実装するオブジェクトにフォーマットされた文字列を書き込みます。この関数は、書き込まれたバイト数と、書き込み中に発生したエラーの2つの値を返します。

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
  • w: 書き込み先の io.Writer インターフェース。
  • format: フォーマット文字列(fmt.Printf と同様)。
  • a ...interface{}: フォーマット文字列に埋め込む引数。
  • n: 書き込まれたバイト数。
  • err: 書き込み中に発生したエラー。エラーがなければ nil

エラーハンドリングの重要性

プログラミングにおいて、特にI/O操作(ファイル書き込み、ネットワーク通信など)では、操作が常に成功するとは限りません。ディスクの空き容量不足、ネットワークの切断、パーミッションの問題など、様々な理由でエラーが発生する可能性があります。これらのエラーを適切に検知し、処理することは、堅牢で信頼性の高いアプリケーションを構築するために不可欠です。エラーを無視すると、アプリケーションが予期せぬ動作をしたり、デバッグが困難になったりする原因となります。

技術的詳細

このコミットの技術的な核心は、log/syslog パッケージの Writer 型に定義されている write メソッドの戻り値の変更にあります。

変更前の write メソッドは以下のようになっていました(関連部分のみ抜粋):

func (w *Writer) write(p Priority, msg string) (int, error) {
    // ... (前略) ...

    timestamp := time.Now().Format(time.RFC3339)
    fmt.Fprintf(w.conn, "<%d>%s %s %s[%d]: %s%s",
        p, timestamp, w.hostname,
        w.tag, os.Getpid(), msg, nl)
    return len(msg), nil // ここで常に成功を返していた
}

ここで注目すべきは、fmt.Fprintf の呼び出し結果が変数に代入されず、その戻り値(書き込まれたバイト数 n とエラー err)が完全に無視されている点です。その代わりに、メソッドは常に len(msg)nil(エラーなし)を返していました。これは、fmt.Fprintf が内部でエラーを検出したとしても、write メソッドの呼び出し元にはそのエラーが伝わらないことを意味します。

変更後の write メソッドは以下のようになります:

func (w *Writer) write(p Priority, msg string) (int, error) {
    // ... (前略) ...

    timestamp := time.Now().Format(time.RFC3339)
    return fmt.Fprintf(w.conn, "<%d>%s %s %s[%d]: %s%s", // fmt.Fprintfの戻り値を直接返す
        p, timestamp, w.hostname,
        w.tag, os.Getpid(), msg, nl)
    // return len(msg), nil は削除された
}

この変更により、fmt.Fprintf が返す (int, error) のタプルが、write メソッドの戻り値として直接返されるようになりました。これにより、syslog コネクションへの書き込み中に発生したエラーが、write メソッドの呼び出し元に正確に伝播されるようになります。

この修正は、Go言語におけるエラーハンドリングの慣習(エラーは常に明示的にチェックし、処理する)に則ったものであり、ライブラリの堅牢性を向上させます。

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

変更は src/pkg/log/syslog/syslog.go ファイルの write メソッド内で行われました。

--- a/src/pkg/log/syslog/syslog.go
+++ b/src/pkg/log/syslog/syslog.go
@@ -258,10 +258,9 @@ func (w *Writer) write(p Priority, msg string) (int, error) {
 	}
 
 	timestamp := time.Now().Format(time.RFC3339)
-	fmt.Fprintf(w.conn, "<%d>%s %s %s[%d]: %s%s",
+	return fmt.Fprintf(w.conn, "<%d>%s %s %s[%d]: %s%s",
 	\tp, timestamp, w.hostname,
 	\tw.tag, os.Getpid(), msg, nl)
-	return len(msg), nil
 }
 
 // NewLogger creates a log.Logger whose output is written to

コアとなるコードの解説

変更の中心は、fmt.Fprintf の呼び出し方と、その戻り値の扱い方です。

  • 変更前:

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

    ここでは fmt.Fprintf が呼び出されていますが、その結果(書き込まれたバイト数とエラー)は変数に代入されず、完全に無視されています。その後の return len(msg), nil は、fmt.Fprintf の成否に関わらず、常にメッセージの長さと nil エラーを返していました。これは、syslog への書き込みが失敗しても、呼び出し元には成功したと報告されることを意味します。

  • 変更後:

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

    この変更では、fmt.Fprintf の呼び出し結果が直接 write メソッドの戻り値として使用されています。fmt.Fprintf(int, error) を返すため、この変更により write メソッドも fmt.Fprintf が返した正確なバイト数とエラー情報をそのまま呼び出し元に伝達するようになります。これにより、syslog への書き込みが失敗した場合、そのエラーが適切に報告され、アプリケーション側で適切なエラーハンドリングを行うことが可能になります。

この修正は、一見すると小さな変更ですが、エラーハンドリングの正確性を大幅に向上させ、log/syslog パッケージを使用するアプリケーションの信頼性を高める上で非常に重要です。

関連リンク

参考にした情報源リンク