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

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

このコミットは、Go言語のドキュメンテーションツールであるgodocにおけるログの冗長性を削減するための変更です。具体的には、godocがファイルシステムを読み込む際に発生する可能性のあるエラーログの出力を抑制し、より静かな動作を実現しています。

コミット

commit 8e5b34e5801e1ace1ba6c012a5d07ce9e568eb53
Author: Russ Cox <rsc@golang.org>
Date:   Mon Mar 5 13:29:13 2012 -0500

    godoc: quiet log spam
    
    Fixes #3191.
    Sorry.
    
    R=golang-dev, bradfitz
    CC=golang-dev
    https://golang.org/cl/5726059

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

https://github.com/golang/go/commit/8e5b34e5801e1ace1ba6c012a5d07ce9e568eb53

元コミット内容

このコミットの元の内容は、godocツールが生成するログの量を減らすことです。特に、ファイルシステムの読み込みエラーに関するログが過剰に出力される「ログスパム」状態を解消することを目的としています。コミットメッセージには「Fixes #3191」とあり、これはGoプロジェクトのIssueトラッカーにおける3191番の問題を解決することを示唆しています。

変更の背景

godocはGo言語のソースコードからドキュメンテーションを生成し、Webサーバーとして提供するツールです。このツールは、Goのパッケージやモジュールに関する情報を表示するために、ファイルシステムを頻繁に走査します。

変更の背景には、以下のような問題があったと推測されます。

  1. 過剰なログ出力(ログスパム): godocがファイルシステムを読み込む際に、存在しないディレクトリやアクセス権のないディレクトリなど、様々な理由でReadDir関数がエラーを返すことがあります。これらのエラーがすべてログに出力されると、ログファイルが肥大化したり、コンソールが大量のエラーメッセージで埋め尽くされたりする「ログスパム」の状態が発生します。これは、実際の重要なエラーを見落とす原因となったり、デバッグ作業を困難にしたりします。
  2. 非本質的なエラーログ: godocの動作において、一部のReadDirエラーは予期される、あるいは許容されるものであり、必ずしもユーザーに通知する必要がない場合があります。例えば、特定のパスにGoパッケージが存在しない場合でも、それはgodocの正常な動作の一部であり、エラーとしてログに記録する必要はないかもしれません。
  3. ユーザー体験の向上: ログスパムは、godocを使用する開発者にとって煩わしいものであり、ツールの使い勝手を損ないます。ログ出力を抑制することで、よりクリーンで集中しやすい環境を提供できます。

このコミットは、これらの問題を解決し、godocのログ出力をより意味のあるものに絞り込むことを目的としています。

前提知識の解説

このコミットを理解するためには、以下のGo言語および関連技術の知識が役立ちます。

  1. godocツール: Go言語の公式ドキュメンテーションツール。Goのソースコードからコメントや宣言を解析し、HTML形式でドキュメントを生成したり、コマンドラインからドキュメントを検索したりする機能を提供します。通常、godoc -http=:6060のように実行し、Webブラウザでアクセスして利用します。
  2. logパッケージ: Go言語の標準ライブラリに含まれるロギング機能を提供するパッケージです。log.Printf関数は、指定されたフォーマット文字列と引数を使用してログメッセージを出力します。デフォルトでは、標準エラー出力にタイムスタンプ付きで出力されます。
  3. osパッケージ: オペレーティングシステムと対話するための機能を提供するGoの標準ライブラリパッケージです。ファイルシステム操作(ファイルの読み書き、ディレクトリの作成・削除など)やプロセス管理など、OSレベルの機能にアクセスできます。
  4. os.FileInfoインターフェース: osパッケージで定義されているインターフェースで、ファイルやディレクトリのメタデータ(名前、サイズ、パーミッション、更新時刻など)を表します。os.ReadDir関数は、このインターフェースを実装するオブジェクトのスライスを返します。
  5. fs.ReadDir関数: io/fsパッケージ(またはosパッケージのReadDir)で提供される関数で、指定されたディレクトリ内のエントリ(ファイルやサブディレクトリ)の情報を読み込みます。通常、[]os.FileInfoerrorの2つの値を返します。エラーが発生した場合、2番目の戻り値が非nilとなります。
  6. エラーハンドリング: Go言語では、関数がエラーを返す場合、通常は戻り値の最後にerror型の値を返します。呼び出し元は、このエラー値をチェックして、エラーが発生したかどうかを判断し、適切に処理する必要があります。このコミットでは、エラーを無視する(_に代入する)ことで、ログ出力を抑制しています。
  7. diffコマンド: Gitなどのバージョン管理システムで、2つのファイルやディレクトリツリー間の差分を表示するために使用されるコマンドです。diff --git a/path/to/file b/path/to/fileのような形式で、変更されたファイルの元のバージョン(a/プレフィックス)と新しいバージョン(b/プレフィックス)を示し、追加行(+)、削除行(-)、変更行( )をマークします。

技術的詳細

このコミットの技術的な変更は、主に以下の3つのファイルにわたっています。

  1. src/cmd/godoc/dirtrees.go:

    • newDirTree関数内でfs.ReadDir(path)の呼び出しがあり、以前はエラーをチェックし、エラーが発生した場合はlog.Printfでログに出力していました。
    • 変更後、list, err := fs.ReadDir(path)list, _ := fs.ReadDir(path)に変更されています。これは、ReadDirが返すエラー値を明示的に破棄(無視)することを意味します。これにより、この場所でのReadDirエラーがログに出力されなくなります。
    • コメントも削除されており、このエラーが「起こるべきではない」という前提が緩和されたか、あるいはログに出力するほど重要ではないと判断されたことを示唆しています。
  2. src/cmd/godoc/filesystem.go:

    • nameSpace構造体のReadDirメソッドが変更されています。このメソッドは、複数のファイルシステム(例えば、GoのソースツリーとGOPATH)を統合して単一の仮想ファイルシステムとして扱うためのロジックを含んでいます。
    • first []os.FileInfoという新しいフィールドがnameSpace構造体に追加されています。これは、ReadDirが成功した最初のディレクトリの内容を保持するために使用されます。
    • ループ内でdir == nilまたはfirst == nilの場合にfirst = dirとして、最初に成功したReadDirの結果をfirstに保存するロジックが追加されています。
    • 最も重要な変更は、if len(all) == 0 && first != nilのブロックです。これは、ReadDirがGoファイルを含むディレクトリを全く見つけられなかった場合でも、もし何らかのディレクトリの読み込みが一度でも成功していれば(first != nil)、その最初の成功したディレクトリの内容を結果として返すようにしています。これにより、エラーが発生しても、部分的にでも結果を返すことで、呼び出し元がエラーを処理せずに済むケースが増える可能性があります。これは、dirtrees.goでのエラー無視と連携して、ログスパムを減らす効果があります。
  3. src/cmd/godoc/godoc.go:

    • serveDirectory関数内でfs.ReadDir(abspath)の呼び出しがあり、以前はエラーをチェックし、エラーが発生した場合はlog.Printfでログに出力していました。
    • 変更後、log.Printf("ReadDir: %s", err)の行が削除されています。これにより、Webサーバーとしてディレクトリをサーブする際に発生するReadDirエラーがログに出力されなくなります。エラーは引き続きserveError関数に渡され、HTTPレスポンスとしてクライアントに返される可能性がありますが、サーバー側のログには出力されません。

これらの変更は、godocがファイルシステムを読み込む際の特定のエラーパスにおいて、ログ出力を意図的に抑制することで、全体的なログの量を減らすことを目的としています。特に、dirtrees.gogodoc.goではエラーのログ出力を直接削除し、filesystem.goではReadDirの振る舞いを調整して、エラーが発生しても部分的な結果を返すことで、エラーが呼び出し元に伝播する頻度を減らしています。

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

src/cmd/godoc/dirtrees.go

--- a/src/cmd/godoc/dirtrees.go
+++ b/src/cmd/godoc/dirtrees.go
@@ -69,13 +69,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i
 		}
 	}
 
-	list, err := fs.ReadDir(path)
-	if err != nil {
-		// newDirTree is called with a path that should be a package
-		// directory; errors here should not happen, but if they do,
-		// we want to know about them
-		log.Printf("ReadDir(%s): %s", path, err)
-	}
+	list, _ := fs.ReadDir(path)
 
 	// determine number of subdirectories and if there are package files
 	ndirs := 0

src/cmd/godoc/filesystem.go

--- a/src/cmd/godoc/filesystem.go
+++ b/src/cmd/godoc/filesystem.go
@@ -400,6 +400,7 @@ func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) {
 		haveName = map[string]bool{}
 		all      []os.FileInfo
 		err      error
+		first    []os.FileInfo
 	)
 
 	for _, m := range ns.resolve(path) {
@@ -411,6 +412,14 @@ func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) {
 			continue
 		}
 
+		if dir == nil {
+			dir = []os.FileInfo{}
+		}
+
+		if first == nil {
+			first = dir
+		}
+
 		// If we don't yet have Go files in 'all' and this directory
 		// has some, add all the files from this directory.
 		// Otherwise, only add subdirectories.
@@ -434,6 +443,15 @@ func (ns nameSpace) ReadDir(path string) ([]os.FileInfo, error) {
 		}
 	}
 
+	// We didn't find any directories containing Go files.
+	// If some directory returned successfully, use that.
+	if len(all) == 0 && first != nil {
+		for _, d := range first {
+			haveName[d.Name()] = true
+			all = append(all, d)
+		}
+	}
+
 	// Built union.  Add any missing directories needed to reach mount points.
 	for old := range ns {
 		if hasPathPrefix(old, path) && old != path {

src/cmd/godoc/godoc.go

--- a/src/cmd/godoc/godoc.go
+++ b/src/cmd/godoc/godoc.go
@@ -658,7 +658,6 @@ func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str
 
 	list, err := fs.ReadDir(abspath)
 	if err != nil {
-		log.Printf("ReadDir: %s", err)
 		serveError(w, r, relpath, err)
 		return
 	}

コアとなるコードの解説

src/cmd/godoc/dirtrees.go の変更

newDirTree関数は、Goのパッケージツリーを構築する際にディレクトリを読み込む役割を担っています。 変更前:

	list, err := fs.ReadDir(path)
	if err != nil {
		// newDirTree is called with a path that should be a package
		// directory; errors here should not happen, but if they do,
		// we want to know about them
		log.Printf("ReadDir(%s): %s", path, err)
	}

変更後:

	list, _ := fs.ReadDir(path)

この変更は、fs.ReadDir(path)が返すエラーを_(ブランク識別子)に代入することで、エラー値を明示的に無視しています。これにより、ReadDirがエラーを返しても、そのエラーがログに出力されることはなくなります。元のコメントが示唆するように、このパスはパッケージディレクトリであるべきであり、エラーは「起こるべきではない」とされていましたが、実際には発生し、ログスパムの原因となっていたため、ログ出力を抑制する判断がなされたと考えられます。

src/cmd/godoc/filesystem.go の変更

nameSpace.ReadDirメソッドは、godocが複数のソースツリー(GOPATHなど)を統合して単一のファイルシステムビューを提供する際に使用されます。このメソッドは、複数の場所からディレクトリの内容を読み込み、それらを結合するロジックを含んでいます。

追加されたfirst []os.FileInfoフィールドと関連ロジック:

		first    []os.FileInfo
	)

	for _, m := range ns.resolve(path) {
		// ... (既存のコード) ...

		if dir == nil {
			dir = []os.FileInfo{}
		}

		if first == nil {
			first = dir
		}

		// ... (既存のコード) ...
	}

	// We didn't find any directories containing Go files.
	// If some directory returned successfully, use that.
	if len(all) == 0 && first != nil {
		for _, d := range first {
			haveName[d.Name()] = true
			all = append(all, d)
		}
	}

この変更の目的は、ReadDirがGoファイルを含むディレクトリを全く見つけられなかった場合でも、少なくとも一度でもディレクトリの読み込みが成功していれば、その最初の成功した結果を返すようにすることです。これにより、たとえGoファイルが見つからなくても、エラーを返さずに空でないディレクトリリストを返すことが可能になり、呼び出し元でのエラー処理(およびログ出力)の必要性を減らします。これは、godocがGoパッケージを探す際に、Goファイルがないディレクトリでもエラーとして扱わないようにするための改善と考えられます。

src/cmd/godoc/godoc.go の変更

serveDirectory関数は、godocのWebサーバーがディレクトリの内容をHTTPレスポンスとして提供する際に使用されます。

変更前:

	list, err := fs.ReadDir(abspath)
	if err != nil {
		log.Printf("ReadDir: %s", err)
		serveError(w, r, relpath, err)
		return
	}

変更後:

	list, err := fs.ReadDir(abspath)
	if err != nil {
		serveError(w, r, relpath, err)
		return
	}

この変更では、fs.ReadDir(abspath)がエラーを返した場合のlog.Printf呼び出しが削除されています。エラーは引き続きserveError関数に渡され、HTTPレスポンスとしてクライアントにエラー情報が返されますが、godocサーバーの標準エラー出力にはログとして記録されなくなります。これにより、Webアクセス時に発生する可能性のあるファイルシステムエラーが、サーバーログを汚染するのを防ぎます。

これらの変更は全体として、godocがファイルシステムを走査する際の、本質的ではないエラーログの出力を抑制し、よりクリーンなログと安定した動作を目指しています。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード(GitHubリポジトリ): https://github.com/golang/go
  • Go言語のIssueトラッカー(Fixes #3191に関連する可能性のある情報源): https://github.com/golang/go/issues (ただし、今回の検索では直接的な情報は見つかりませんでした)
  • Go言語のコードレビューシステム(https://golang.org/cl/5726059): https://go.dev/cl/ (このリンクは古い形式であり、現在はhttps://go.dev/cl/5726059のようにアクセスできる可能性がありますが、コミットが古いため直接アクセスできない場合もあります。)
  • 一般的なGo言語のエラーハンドリングに関する情報源。
  • Go言語のlogパッケージに関する情報源。
  • Go言語のファイルシステム操作に関する情報源。