[インデックス 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のパッケージやモジュールに関する情報を表示するために、ファイルシステムを頻繁に走査します。
変更の背景には、以下のような問題があったと推測されます。
- 過剰なログ出力(ログスパム):
godoc
がファイルシステムを読み込む際に、存在しないディレクトリやアクセス権のないディレクトリなど、様々な理由でReadDir
関数がエラーを返すことがあります。これらのエラーがすべてログに出力されると、ログファイルが肥大化したり、コンソールが大量のエラーメッセージで埋め尽くされたりする「ログスパム」の状態が発生します。これは、実際の重要なエラーを見落とす原因となったり、デバッグ作業を困難にしたりします。 - 非本質的なエラーログ:
godoc
の動作において、一部のReadDir
エラーは予期される、あるいは許容されるものであり、必ずしもユーザーに通知する必要がない場合があります。例えば、特定のパスにGoパッケージが存在しない場合でも、それはgodoc
の正常な動作の一部であり、エラーとしてログに記録する必要はないかもしれません。 - ユーザー体験の向上: ログスパムは、
godoc
を使用する開発者にとって煩わしいものであり、ツールの使い勝手を損ないます。ログ出力を抑制することで、よりクリーンで集中しやすい環境を提供できます。
このコミットは、これらの問題を解決し、godoc
のログ出力をより意味のあるものに絞り込むことを目的としています。
前提知識の解説
このコミットを理解するためには、以下のGo言語および関連技術の知識が役立ちます。
godoc
ツール: Go言語の公式ドキュメンテーションツール。Goのソースコードからコメントや宣言を解析し、HTML形式でドキュメントを生成したり、コマンドラインからドキュメントを検索したりする機能を提供します。通常、godoc -http=:6060
のように実行し、Webブラウザでアクセスして利用します。log
パッケージ: Go言語の標準ライブラリに含まれるロギング機能を提供するパッケージです。log.Printf
関数は、指定されたフォーマット文字列と引数を使用してログメッセージを出力します。デフォルトでは、標準エラー出力にタイムスタンプ付きで出力されます。os
パッケージ: オペレーティングシステムと対話するための機能を提供するGoの標準ライブラリパッケージです。ファイルシステム操作(ファイルの読み書き、ディレクトリの作成・削除など)やプロセス管理など、OSレベルの機能にアクセスできます。os.FileInfo
インターフェース:os
パッケージで定義されているインターフェースで、ファイルやディレクトリのメタデータ(名前、サイズ、パーミッション、更新時刻など)を表します。os.ReadDir
関数は、このインターフェースを実装するオブジェクトのスライスを返します。fs.ReadDir
関数:io/fs
パッケージ(またはos
パッケージのReadDir
)で提供される関数で、指定されたディレクトリ内のエントリ(ファイルやサブディレクトリ)の情報を読み込みます。通常、[]os.FileInfo
とerror
の2つの値を返します。エラーが発生した場合、2番目の戻り値が非nil
となります。- エラーハンドリング: Go言語では、関数がエラーを返す場合、通常は戻り値の最後に
error
型の値を返します。呼び出し元は、このエラー値をチェックして、エラーが発生したかどうかを判断し、適切に処理する必要があります。このコミットでは、エラーを無視する(_
に代入する)ことで、ログ出力を抑制しています。 diff
コマンド: Gitなどのバージョン管理システムで、2つのファイルやディレクトリツリー間の差分を表示するために使用されるコマンドです。diff --git a/path/to/file b/path/to/file
のような形式で、変更されたファイルの元のバージョン(a/
プレフィックス)と新しいバージョン(b/
プレフィックス)を示し、追加行(+
)、削除行(-
)、変更行(
技術的詳細
このコミットの技術的な変更は、主に以下の3つのファイルにわたっています。
-
src/cmd/godoc/dirtrees.go
:newDirTree
関数内でfs.ReadDir(path)
の呼び出しがあり、以前はエラーをチェックし、エラーが発生した場合はlog.Printf
でログに出力していました。- 変更後、
list, err := fs.ReadDir(path)
がlist, _ := fs.ReadDir(path)
に変更されています。これは、ReadDir
が返すエラー値を明示的に破棄(無視)することを意味します。これにより、この場所でのReadDir
エラーがログに出力されなくなります。 - コメントも削除されており、このエラーが「起こるべきではない」という前提が緩和されたか、あるいはログに出力するほど重要ではないと判断されたことを示唆しています。
-
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
でのエラー無視と連携して、ログスパムを減らす効果があります。
-
src/cmd/godoc/godoc.go
:serveDirectory
関数内でfs.ReadDir(abspath)
の呼び出しがあり、以前はエラーをチェックし、エラーが発生した場合はlog.Printf
でログに出力していました。- 変更後、
log.Printf("ReadDir: %s", err)
の行が削除されています。これにより、Webサーバーとしてディレクトリをサーブする際に発生するReadDir
エラーがログに出力されなくなります。エラーは引き続きserveError
関数に渡され、HTTPレスポンスとしてクライアントに返される可能性がありますが、サーバー側のログには出力されません。
これらの変更は、godoc
がファイルシステムを読み込む際の特定のエラーパスにおいて、ログ出力を意図的に抑制することで、全体的なログの量を減らすことを目的としています。特に、dirtrees.go
とgodoc.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言語の公式ドキュメント: https://go.dev/doc/
godoc
コマンドのドキュメント: https://pkg.go.dev/cmd/godoclog
パッケージのドキュメント: https://pkg.go.dev/logos
パッケージのドキュメント: https://pkg.go.dev/osio/fs
パッケージのドキュメント: https://pkg.go.dev/io/fs
参考にした情報源リンク
- 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言語のファイルシステム操作に関する情報源。