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

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

このコミットは、Go言語の標準ライブラリである log/syslog パッケージにおける Writer 型の write メソッドの修正に関するものです。具体的には、syslog への書き込み処理において、fmt.Fprintf から返されるエラーを適切に処理するように変更されています。

コミット

commit e17a6d4b9d613918004991ee74ea95a29fc291e7
Author: Rob Pike <r@golang.org>
Date:   Wed May 22 12:45:52 2013 -0700

    log/syslog: report errors from write
    Fixes #5541.
    This time for sure.
    
    R=golang-dev, minux.ma, iant
    CC=golang-dev
    https://golang.org/cl/9668043

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

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

元コミット内容

log/syslog: report errors from write Fixes #5541. This time for sure.

R=golang-dev, minux.ma, iant CC=golang-dev https://golang.org/cl/9668043

変更の背景

このコミットの背景には、log/syslog パッケージの Writersyslog メッセージを書き込む際に発生する可能性のあるエラーが適切に報告されていなかったという問題があります。コミットメッセージにある Fixes #5541. は、このコミットがGoのIssue 5541を修正するものであることを示唆しています。また、This time for sure. という記述から、過去にも同様の問題に対する修正が試みられたが、完全には解決していなかった可能性が伺えます。

io.Writer インターフェースを実装する型は、書き込み操作が成功したバイト数と、発生したエラーを返すことが期待されます。しかし、以前の実装では、fmt.Fprintf の戻り値であるエラーが無視されており、syslog への書き込みが失敗した場合でも、呼び出し元にはそのエラーが伝播しないという問題がありました。これにより、アプリケーションが syslog へのログ出力の失敗を検知できず、重要なログが失われる可能性がありました。

前提知識の解説

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

log/syslog パッケージは、GoプログラムからUnix系のシステムで広く利用されているロギングプロトコルである syslog にメッセージを送信するための機能を提供します。syslog は、システムイベント、アプリケーションログ、セキュリティ情報などを一元的に管理するための標準的な方法です。

io.Writer インターフェース

Go言語の io パッケージには、データの書き込み操作を抽象化するための Writer インターフェースが定義されています。

type Writer interface {
    Write(p []byte) (n int, err error)
}

Write メソッドは、バイトスライス p を書き込み、書き込まれたバイト数 n と、発生したエラー err を返します。このインターフェースを実装する型は、エラーが発生した場合にはそのエラーを返すことが期待されます。

fmt.Fprintf 関数

fmt パッケージの Fprintf 関数は、指定された io.Writer にフォーマットされた文字列を書き込みます。そのシグネチャは以下の通りです。

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)

Fprintf は、書き込まれたバイト数 n と、書き込み中に発生したエラー err を返します。

syslog プロトコル

syslog プロトコルは、メッセージのフォーマットと転送方法を定義します。メッセージは通常、優先度(ファシリティと重要度)、タイムスタンプ、ホスト名、アプリケーション名、プロセスID、メッセージ本文などの情報を含みます。

技術的詳細

このコミットの技術的な核心は、log/syslog パッケージの Writer 型が io.Writer インターフェースを適切に実装し、syslog への書き込みエラーを正確に報告するようにすることです。

以前の実装では、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 がエラーを返しても、常に nil エラーが返されていました。これは、io.Writer の契約に違反し、syslog への書き込みが失敗した場合でも、呼び出し元がその事実を知ることができないという問題を引き起こしていました。

今回の修正では、fmt.Fprintf の戻り値であるエラーを err 変数にキャプチャし、その errnil でない場合に、そのエラーを呼び出し元に返すように変更されています。

_, err := fmt.Fprintf(w.conn, "<%d>%s %s %s[%d]: %s%s",
    p, timestamp, w.hostname,
    w.tag, os.Getpid(), msg, nl)
if err != nil {
    return 0, err
}
// Note: return the length of the input, not the number of
// bytes printed by Fprintf, because this must behave like
// an io.Writer.
return len(msg), nil

ここで重要なのは、io.WriterWrite メソッドが返す n (書き込まれたバイト数) の扱いです。fmt.Fprintf が返す n は、フォーマットされた文字列のバイト数であり、元の入力メッセージ msg の長さとは異なります。しかし、io.Writer の契約では、Write メソッドは入力バイトスライスの長さ(または実際に書き込まれたバイト数)を返すことが期待されます。このコミットでは、コメントで明示されているように、fmt.Fprintf が書き込んだバイト数ではなく、元の入力メッセージ msg の長さ len(msg) を返すことで、io.Writer のセマンティクスに準拠しています。エラーが発生した場合は 0 とエラーを返します。

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

変更は src/pkg/log/syslog/syslog.go ファイルの Writer 型の write メソッドに集中しています。

--- a/src/pkg/log/syslog/syslog.go
+++ b/src/pkg/log/syslog/syslog.go
@@ -258,9 +258,15 @@ func (w *Writer) write(p Priority, msg string) (int, error) {
 	}\n 
 	timestamp := time.Now().Format(time.RFC3339)\n-\tfmt.Fprintf(w.conn, "<%d>%s %s %s[%d]: %s%s",\n+\t_, err := fmt.Fprintf(w.conn, "<%d>%s %s %s[%d]: %s%s",\n \t\tp, timestamp, w.hostname,\n \t\tw.tag, os.Getpid(), msg, nl)\n+\tif err != nil {\n+\t\treturn 0, err\n+\t}\n+\t// Note: return the length of the input, not the number of\n+\t// bytes printed by Fprintf, because this must behave like\n+\t// an io.Writer.\n \treturn len(msg), nil
 }\n 

コアとなるコードの解説

変更前は、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 // エラーは常にnil

変更後は、fmt.Fprintf の戻り値であるエラーを err 変数で受け取り、そのエラーをチェックしています。

_, err := fmt.Fprintf(w.conn, "<%d>%s %s %s[%d]: %s%s",
    p, timestamp, w.hostname,
    w.tag, os.Getpid(), msg, nl)
if err != nil {
    return 0, err // エラーがあれば、0バイトとエラーを返す
}
// Note: return the length of the input, not the number of
// bytes printed by Fprintf, because this must behave like
// an io.Writer.
return len(msg), nil // エラーがなければ、入力メッセージの長さを返す

この変更により、syslog への書き込み中にネットワークエラーやその他の問題が発生した場合、fmt.Fprintf が返すエラーが捕捉され、write メソッドの呼び出し元に適切に伝播されるようになります。これにより、log/syslog パッケージの利用者は、ログ出力の失敗を検知し、適切なエラーハンドリングを行うことができるようになります。

コメントで示されているように、io.Writer のセマンティクスに従うために、fmt.Fprintf が実際に書き込んだバイト数ではなく、元の入力メッセージ msg の長さ len(msg) を返す点が重要です。これは、io.WriterWrite メソッドが、引数として受け取ったバイトスライスのうち、何バイトを処理したか(または処理しようとしたか)を返すという一般的な期待に合致するためです。

関連リンク

  • Go Issue #5541: Web検索では直接的な情報を見つけることができませんでした。Goの公式Issueトラッカーでこの番号のIssueが存在した可能性がありますが、現在はアクセスできないか、別のリポジトリに移動している可能性があります。
  • Go Change List 9668043: Web検索では直接的な情報を見つけることができませんでした。Goのコードレビューシステム(Gerrit)の古いCL番号である可能性がありますが、現在はアクセスできないか、情報が公開されていない可能性があります。

参考にした情報源リンク

  • Go言語の公式ドキュメント: log/syslog パッケージ、io パッケージ、fmt パッケージに関する公式ドキュメントは、これらの機能の理解に不可欠です。
  • syslog プロトコルに関する一般的な情報源: syslog の動作原理を理解するために、RFC 5424などの標準ドキュメントや、関連する技術記事が参考になります。
  • Go言語のコミット履歴: GoのGitHubリポジトリのコミット履歴を辿ることで、関連する変更や議論の文脈を理解することができます。