[インデックス 10921] ファイルの概要
このコミットは、Go言語のダッシュボードアプリケーションにおけるログの記録と出力に関する修正を目的としています。具体的には、ログデータの内部表現をバイトスライス ([]byte
) から文字列 (string
) に変更し、それに伴う関連コードの調整と、ログ表示時のHTTPレスポンスヘッダの追加を行っています。
コミット
commit 15782bbfaf13b92e9d06e30278a05e7ab61914fa
Author: Andrew Gerrand <adg@golang.org>
Date: Wed Dec 21 12:13:27 2011 +1100
dashboard: fix log recording and output
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5503054
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/15782bbfaf13b92e9d06e30278a05e7ab61914fa
元コミット内容
dashboard: fix log recording and output
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5503054
変更の背景
このコミットは、Go言語のビルドおよびテスト結果を表示するダッシュボードアプリケーションにおいて、ログデータの取り扱いに関する問題を修正するために行われました。以前はログデータが汎用的なバイトスライス ([]byte
) として扱われていましたが、ログの内容が本質的にテキストデータであるため、これを明示的に文字列 (string
) として扱うことで、コードの可読性、保守性、および効率性を向上させる狙いがあります。
また、Webブラウザなどのクライアントがログデータを正しく解釈できるように、HTTPレスポンスの Content-Type
ヘッダを適切に設定する必要がありました。これにより、ログがプレーンテキストとして表示され、誤った解釈(例えばHTMLとしてレンダリングされるなど)を防ぐことができます。
この変更は、Go言語の標準ライブラリや慣用的なGoプログラミングスタイルへの適合も意図していると考えられます。特に、io.WriteString
の使用は、文字列をライターに書き込む際の推奨される方法であり、以前の Write([]byte)
よりも意図が明確になります。
前提知識の解説
- Go言語の型システム: Go言語では、
[]byte
は任意のバイト列を表すのに対し、string
はUTF-8でエンコードされた不変のバイト列(テキスト)を表します。このコミットでは、ログデータがテキストであることを明確にするために、[]byte
からstring
への型変更が行われています。 - Google App Engine (GAE): コミット内の
appengine.Context
やdatastore
の使用から、このダッシュボードアプリケーションがGoogle App Engine上で動作していることがわかります。GAEはGoogleが提供するPaaS (Platform as a Service) であり、Webアプリケーションのスケーラブルなホスティングを可能にします。 - Datastore: Google App EngineのDatastoreは、NoSQLドキュメントデータベースサービスです。
datastore:"-"
タグは、Goの構造体フィールドがDatastoreに永続化されないことを示します。datastore:",noindex"
は、フィールドがDatastoreに保存されるものの、インデックス付けされないことを意味します。この場合、Log
フィールド自体はDatastoreに直接保存されず、LogHash
をキーとして別のLog
エンティティに保存される設計になっています。これは、大きなデータを直接エンデックス化しないための一般的なパターンです。 - SHA-1ハッシュ:
sha1.New()
の使用から、ログの内容のハッシュ値を計算していることがわかります。SHA-1は、データの完全性を検証するためによく使用される暗号学的ハッシュ関数です。 - Gzip圧縮:
gzip.NewWriterLevel
の使用から、ログデータが保存される際にGzip形式で圧縮されていることがわかります。これは、ストレージスペースを節約し、データ転送量を削減するための一般的な手法です。 - HTTP
Content-Type
ヘッダ: HTTPレスポンスヘッダの一つで、レスポンスボディのメディアタイプ(MIMEタイプ)を示します。text/plain
は、ボディがプレーンテキストであることを示し、ブラウザがその内容を適切に表示するために重要です。 io.WriteString
: Go言語のio
パッケージに含まれる関数で、io.Writer
インターフェースを実装するオブジェクトに文字列を書き込むためのユーティリティ関数です。Writer.Write([]byte)
を呼び出すよりも、文字列を直接書き込む意図が明確になります。
技術的詳細
このコミットの主要な技術的変更点は以下の通りです。
-
ログデータ型の変更:
misc/dashboard/app/build/build.go
内のResult
構造体において、ログデータを保持するLog
フィールドの型が[]byte
からstring
に変更されました。datastore:"-"
タグは、このフィールドがDatastoreに直接保存されるのではなく、JSONのアンマーシャリングのためだけに使用されることを示しています。実際のログデータは、LogHash
をキーとして別のLog
エンティティに保存されます。- ログをDatastoreに保存する
PutLog
関数のシグネチャも、引数text
の型が[]byte
からstring
に変更されました。
-
ハッシュ計算と圧縮処理の変更:
PutLog
関数内で、SHA-1ハッシュの計算 (h.Write(text)
) およびGzip圧縮 (z.Write(text)
) の際に、[]byte
を直接渡す代わりにio.WriteString(h, text)
およびio.WriteString(z, text)
が使用されるようになりました。これは、入力が文字列になったことに対応し、よりGoらしい書き方です。io.WriteString
は内部的に文字列をバイトスライスに変換してWrite
メソッドを呼び出すため、機能的には同等ですが、コードの意図がより明確になります。
-
ログ表示時の
Content-Type
ヘッダ設定:misc/dashboard/app/build/build.go
内のlogHandler
関数に、w.Header().Set("Content-type", "text/plain")
という行が追加されました。これにより、/log/
パスでログデータが提供される際に、HTTPレスポンスのContent-Type
ヘッダがtext/plain
に設定されます。これは、クライアント(Webブラウザなど)がレスポンスボディをプレーンテキストとして正しく解釈し、表示するために不可欠です。
-
テストコードの更新:
misc/dashboard/app/build/test.go
内のテストリクエストデータが、Result
構造体のLog
フィールドの型変更に合わせて更新されました。具体的には、Log: []byte("test")
やLog: []byte("boo")
のような記述がLog: "test"
やLog: "boo"
に変更されています。これにより、テストデータが新しい型定義に適合するようになりました。
これらの変更は、ログデータが常にテキスト形式であることを前提とし、その取り扱いをより効率的かつ安全にするためのものです。特に、Content-Type
ヘッダの追加は、Webアプリケーションとしての正しい振る舞いを保証する上で重要です。
コアとなるコードの変更箇所
misc/dashboard/app/build/build.go
--- a/misc/dashboard/app/build/build.go
+++ b/misc/dashboard/app/build/build.go
@@ -176,7 +176,7 @@ type Result struct {
GoHash string
OK bool
- Log []byte `datastore:"-"` // for JSON unmarshaling
+ Log string `datastore:"-"` // for JSON unmarshaling only
LogHash string `datastore:",noindex"` // Key to the Log record.
}
@@ -208,12 +208,12 @@ type Log struct {
CompressedLog []byte
}
-func PutLog(c appengine.Context, text []byte) (hash string, err os.Error) {
+func PutLog(c appengine.Context, text string) (hash string, err os.Error) {
h := sha1.New()
-\th.Write(text)\n+\tio.WriteString(h, text)\n \tb := new(bytes.Buffer)\n \tz, _ := gzip.NewWriterLevel(b, gzip.BestCompression)\n-\tz.Write(text)\n+\tio.WriteString(z, text)\n \tz.Close()\n \thash = fmt.Sprintf("%x", h.Sum())\n \tkey := datastore.NewKey(c, "Log", hash, 0, nil)\n@@ -511,6 +511,7 @@ func resultHandler(r *http.Request) (interface{}, os.Error) {
// logHandler displays log text for a given hash.
// It handles paths like "/log/hash".
func logHandler(w http.ResponseWriter, r *http.Request) {
+\tw.Header().Set("Content-type", "text/plain")
c := appengine.NewContext(r)
h := r.URL.Path[len("/log/"):]
k := datastore.NewKey(c, "Log", h, 0, nil)
misc/dashboard/app/build/test.go
--- a/misc/dashboard/app/build/test.go
+++ b/misc/dashboard/app/build/test.go
@@ -90,7 +90,7 @@ var testRequests = []struct {
{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, &Todo{Kind: "build-go-commit", Data: &Commit{Hash: "0003"}}},
// logs
- {"/result", nil, &Result{Builder: "linux-386", Hash: "0003", OK: false, Log: []byte("test")}, nil},
+ {"/result", nil, &Result{Builder: "linux-386", Hash: "0003", OK: false, Log: "test"}, nil},
{"/log/a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", nil, nil, "test"},
{"/todo", url.Values{"kind": {"build-go-commit"}, "builder": {"linux-386"}}, nil, nil},
@@ -106,7 +106,7 @@ var testRequests = []struct {
{"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0001", OK: true}, nil},
{"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0001"}}, nil, nil},
{"/todo", url.Values{"kind": {"build-package"}, "builder": {"linux-386"}, "packagePath": {testPkg}, "goHash": {"0002"}}, nil, &Todo{Kind: "build-package", Data: &Commit{Hash: "1003"}}},
- {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0005", OK: false, Log: []byte("boo")}, nil},
+ {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0005", OK: false, Log: "boo"}, nil},
}
func testHandler(w http.ResponseWriter, r *http.Request) {
コアとなるコードの解説
misc/dashboard/app/build/build.go
-
Result
構造体:type Result struct { // ... OK bool Log string `datastore:"-"` // for JSON unmarshaling only LogHash string `datastore:",noindex"` // Key to the Log record. }
Log
フィールドの型が[]byte
からstring
に変更されました。これにより、ログデータがテキストとして扱われることが明確になります。datastore:"-"
タグは、このフィールドがDatastoreに直接保存されないことを示し、主にJSONのデシリアライズ時に使用されることを意味します。実際のログデータはLogHash
をキーとして別途保存されます。 -
PutLog
関数:func PutLog(c appengine.Context, text string) (hash string, err os.Error) { h := sha1.New() io.WriteString(h, text) // 変更点: h.Write(text) から io.WriteString(h, text) b := new(bytes.Buffer) z, _ := gzip.NewWriterLevel(b, gzip.BestCompression) io.WriteString(z, text) // 変更点: z.Write(text) から io.WriteString(z, text) z.Close() // ... }
関数の引数
text
の型が[]byte
からstring
に変更されました。それに伴い、SHA-1ハッシュ計算とGzip圧縮の際に、h.Write(text)
とz.Write(text)
がそれぞれio.WriteString(h, text)
とio.WriteString(z, text)
に変更されました。これは、文字列をライターに書き込むためのより慣用的なGoの書き方です。 -
logHandler
関数:func logHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-type", "text/plain") // 追加された行 c := appengine.NewContext(r) // ... }
この関数は、特定のハッシュ値に対応するログテキストを表示するHTTPハンドラです。
w.Header().Set("Content-type", "text/plain")
が追加されたことで、このハンドラが返すレスポンスのContent-Type
ヘッダがtext/plain
に設定されます。これにより、クライアントはレスポンスボディをプレーンテキストとして正しく解釈し、表示することができます。
misc/dashboard/app/build/test.go
testRequests
変数:var testRequests = []struct { // ... }{ // ... {"/result", nil, &Result{Builder: "linux-386", Hash: "0003", OK: false, Log: "test"}, nil}, // 変更点: Log: []byte("test") から Log: "test" // ... {"/result", nil, &Result{PackagePath: testPkg, Builder: "linux-386", Hash: "1001", GoHash: "0005", OK: false, Log: "boo"}, nil}, // 変更点: Log: []byte("boo") から Log: "boo" }
Result
構造体のLog
フィールドの型がstring
に変更されたことに伴い、テストデータ内のLog
フィールドの初期化も[]byte("...")
から"..."
に変更されました。これにより、テストコードが新しい型定義に適合し、コンパイルエラーを防ぎます。
関連リンク
- Go言語の公式ドキュメント: https://go.dev/doc/
- Google App Engine: https://cloud.google.com/appengine
- Go言語の
io
パッケージ: https://pkg.go.dev/io - Go言語の
crypto/sha1
パッケージ: https://pkg.go.dev/crypto/sha1 - Go言語の
compress/gzip
パッケージ: https://pkg.go.dev/compress/gzip
参考にした情報源リンク
- Go言語の
string
と[]byte
の違い: https://go.dev/blog/strings - HTTP
Content-Type
ヘッダ: https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Content-Type - Go言語の
io.WriteString
の使用例: https://pkg.go.dev/io#WriteString - Google App Engine Datastoreの概要: https://cloud.google.com/datastore/docs/concepts/overview
- Go言語の
datastore
タグに関する情報 (App Engine Go SDKのドキュメント): (当時のApp Engine Go SDKのドキュメントを参照する必要があるが、現在はGo 1.11以降の標準ライブラリに統合されているため、一般的なGoの構造体タグのドキュメントが参考になる)- Go言語の構造体タグ: https://go.dev/blog/json (JSONの例だが、タグの概念は共通)
- Go言語の
reflect
パッケージ (タグの処理): https://pkg.go.dev/reflect
- Go言語の
os.Error
(Go 1.0以前のエラーハンドリング): https://go.dev/blog/error-handling-and-go (現在はerror
インターフェースが使われる)