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

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

このコミットは、log/syslogパッケージ内のWriter.writeメソッドに対する以前の変更(CL 9658043 / コミットハッシュ ac7877558dce)を元に戻すものです。元のコードが正しく、writeメソッドが返すバイト数は、フォーマットされたメッセージの長さではなく、入力されたメッセージスライスの長さであるべきだという判断に基づいています。

コミット

  • コミットハッシュ: f6c7b339a6b7592aa83abe253a40825fcb7d3e39
  • 作者: Rob Pike r@golang.org
  • 日付: Wed May 22 11:42:04 2013 -0700

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

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

元コミット内容

    undo CL 9658043 / ac7877558dce
    
    The original code was correct. The count returned must be the length
    of the input slice, not the length of the formatted message.
    
    ««« original CL description
    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
    »»»
    
    R=golang-dev, iant
    CC=golang-dev
    https://golang.org/cl/9644044

変更の背景

このコミットは、以前に適用された変更(CL 9658043、コミットハッシュ ac7877558dce)を元に戻すために作成されました。元の変更は、log/syslogパッケージのWriter.writeメソッドにおいて、fmt.Fprintfからのエラーを報告するように修正することを目的としていました。これは、GoのIssue #5541に関連しており、Fprintfが書き込みに失敗した場合にそのエラーを適切に伝播させるべきだという考えに基づいていたようです。

しかし、この元々の変更は、syslogwriteメソッドの期待される振る舞いを誤解していたことが判明しました。syslogwriteメソッドは、io.Writerインターフェースの規約に従い、書き込まれたバイト数を返す必要があります。この「書き込まれたバイト数」は、syslogプロトコルにおいて、実際に送信されたメッセージのバイト数ではなく、入力として受け取ったメッセージスライスの長さを指すべきでした。

以前の変更では、fmt.Fprintfが実際にw.conn(ネットワーク接続)に書き込んだバイト数を返そうとしていました。しかし、syslogの文脈では、これは誤った情報となります。syslogwriteメソッドは、呼び出し元に対して、与えられたメッセージ全体が処理された(または処理されようとした)ことを示すために、入力メッセージの長さを返すのが正しい挙動です。したがって、このコミットは、その誤りを修正し、元の正しいロジックに戻すことを目的としています。

前提知識の解説

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

Go言語の標準ライブラリには、システムログデーモン(syslogd)にメッセージを送信するためのlog/syslogパッケージが含まれています。このパッケージは、RFC 5424(またはそれ以前のRFC 3164)で定義されているsyslogプロトコルに準拠してログメッセージをフォーマットし、送信する機能を提供します。

  • Writer構造体: syslogサーバーへの接続を管理し、ログメッセージを書き込むための主要な型です。
  • Writer.writeメソッド: syslogメッセージを実際にフォーマットし、ネットワーク接続(通常はUDPまたはTCP)を介してsyslogサーバーに送信する内部メソッドです。このメソッドは、io.WriterインターフェースのWriteメソッドと同様に、書き込まれたバイト数とエラーを返します。

fmt.Fprintfの戻り値

fmt.Fprintf関数は、指定されたio.Writerにフォーマットされた文字列を書き込みます。そのシグネチャは以下の通りです。 func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) ここで、n実際に書き込まれたバイト数を表し、errは書き込み中に発生したエラーを表します。

syslogプロトコルにおけるメッセージの長さの扱い

syslogプロトコルでは、メッセージのペイロード(実際のログ内容)の長さは、プロトコルヘッダの一部として含まれることがあります。しかし、log/syslogパッケージのWriter.writeメソッドがio.Writerインターフェースの規約に従う場合、その戻り値nは、syslogサーバーに送信されたバイト数ではなく、writeメソッドに渡された元のメッセージスライスmsgの長さを意味するのが一般的です。これは、io.WriterWriteメソッドが「pから書き込まれたバイト数」を返すという規約に合致するためです。もしwriteメソッドがFprintfによって実際にネットワークに書き込まれたバイト数を返してしまうと、それはsyslogプロトコルの内部的な詳細であり、io.Writerの期待されるセマンティクスとは異なる可能性があります。

GoのChange List (CL)

Goプロジェクトでは、コード変更は「Change List (CL)」として提出されます。これは、Gitのコミットに似ていますが、よりレビュープロセスに焦点を当てた概念です。各CLには一意のIDが割り当てられ、レビューと承認を経て最終的にGitリポジトリにコミットされます。このコミットメッセージに記載されているCL 9658043CL 9644044は、これらの変更管理システムにおける識別子です。

技術的詳細

このコミットの核心は、src/pkg/log/syslog/syslog.goファイル内のWriter.writeメソッドの戻り値のセマンティクスに関するものです。

func (w *Writer) write(p Priority, msg string) (int, error)

このメソッドは、syslogメッセージを構築し、w.conn(通常はネットワーク接続を表すnet.Connインターフェースを実装する型)に書き込みます。

以前のCL (ac7877558dce) では、このメソッドの最後の部分が以下のように変更されていました。

	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が返すn(実際に書き込まれたバイト数)もそのまま返していました。

問題は、syslog.Writerwriteメソッドがio.Writerインターフェースの規約に従う場合、そのnの戻り値は、入力として受け取ったmsgスライスの長さであるべきだという点です。fmt.Fprintfが返すnは、syslogヘッダやタイムスタンプ、ホスト名、PIDなどを含む、フォーマットされた完全なsyslogメッセージのバイト数です。これは、writeメソッドの呼び出し元が期待する「書き込まれたメッセージの長さ」とは異なります。呼び出し元は、通常、自分が渡したメッセージがどれだけ処理されたかを知りたいのであり、syslogプロトコル内部で生成された追加のメタデータを含むメッセージの全長を知りたいわけではありません。

したがって、このコミットは、fmt.Fprintfの戻り値のうち、エラーは無視し(または別途処理し)、常に入力メッセージmsgの長さを返すように修正しています。これにより、Writer.writeメソッドはio.Writerのセマンティクスに正しく準拠し、呼び出し元に期待される情報を提供します。エラー処理については、syslogの文脈では、メッセージの送信が失敗した場合でも、writeメソッド自体は成功したかのように振る舞い、エラーは内部的にログに記録されるか、別のメカニズムで処理されることが一般的です。このコミットでは、エラーを完全に無視し、nilを返すことで、この慣習に従っています。

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

変更はsrc/pkg/log/syslog/syslog.goファイルにのみ行われました。

--- a/src/pkg/log/syslog/syslog.go
+++ b/src/pkg/log/syslog/syslog.go
@@ -258,9 +258,10 @@ 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(w.conn, "<%d>%s %s %s[%d]: %s%s",
 		p, timestamp, w.hostname,
 		w.tag, os.Getpid(), msg, nl)
+	return len(msg), nil
 }
 
 // NewLogger creates a log.Logger whose output is written to

コアとなるコードの解説

変更はWriter.writeメソッドの最後の部分に集中しています。

変更前(元に戻されたコード):

	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が返すバイト数は、syslogヘッダを含むフォーマットされたメッセージ全体の長さであり、writeメソッドに渡された元のmsgの長さではありませんでした。

変更後(このコミットによる修正):

	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

この修正では、以下の2つの変更が行われています。

  1. fmt.Fprintfの呼び出しが独立したステートメントになり、その戻り値(書き込まれたバイト数とエラー)は直接返されなくなりました。これにより、Fprintfが返すエラーは、このメソッドのスコープ内で処理されるか、無視されることになります。このコミットでは、エラーは無視されています。
  2. その代わりに、return len(msg), nilという行が追加されました。これは、writeメソッドが常に入力として受け取ったmsgスライスの長さを返し、エラーは発生しなかった(nil)と報告することを意味します。

この変更により、Writer.writeメソッドはio.Writerインターフェースの規約に正しく従うようになり、呼び出し元は、自分が渡したメッセージがsyslogシステムに「書き込まれた」と期待できるようになります。syslogの文脈では、メッセージの送信がネットワークレベルで失敗したとしても、writeメソッド自体は成功したと報告し、エラーは内部的に処理されるか、別のメカニズムで通知されるのが一般的です。

関連リンク

参考にした情報源リンク

  • コミットメッセージと差分情報 (./commit_data/16374.txtから取得)
  • Go言語のlog/syslogパッケージのドキュメント (Go標準ライブラリ)
  • fmt.Fprintf関数のドキュメント (Go標準ライブラリ)
  • io.Writerインターフェースのドキュメント (Go標準ライブラリ)
  • Web検索: "golang CL 9658043" (CLの背景情報を確認するため)
  • RFC 5424 (The Syslog Protocol) - syslogプロトコルの詳細理解のため (一般的な知識として)
  • RFC 3164 (The BSD Syslog Protocol) - syslogプロトコルの詳細理解のため (一般的な知識として)