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

[インデックス 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.Contextdatastore の使用から、このダッシュボードアプリケーションが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) を呼び出すよりも、文字列を直接書き込む意図が明確になります。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. ログデータ型の変更:

    • misc/dashboard/app/build/build.go 内の Result 構造体において、ログデータを保持する Log フィールドの型が []byte から string に変更されました。datastore:"-" タグは、このフィールドがDatastoreに直接保存されるのではなく、JSONのアンマーシャリングのためだけに使用されることを示しています。実際のログデータは、LogHash をキーとして別の Log エンティティに保存されます。
    • ログをDatastoreに保存する PutLog 関数のシグネチャも、引数 text の型が []byte から string に変更されました。
  2. ハッシュ計算と圧縮処理の変更:

    • PutLog 関数内で、SHA-1ハッシュの計算 (h.Write(text)) およびGzip圧縮 (z.Write(text)) の際に、[]byte を直接渡す代わりに io.WriteString(h, text) および io.WriteString(z, text) が使用されるようになりました。これは、入力が文字列になったことに対応し、よりGoらしい書き方です。io.WriteString は内部的に文字列をバイトスライスに変換して Write メソッドを呼び出すため、機能的には同等ですが、コードの意図がより明確になります。
  3. ログ表示時の Content-Type ヘッダ設定:

    • misc/dashboard/app/build/build.go 内の logHandler 関数に、w.Header().Set("Content-type", "text/plain") という行が追加されました。これにより、/log/ パスでログデータが提供される際に、HTTPレスポンスの Content-Type ヘッダが text/plain に設定されます。これは、クライアント(Webブラウザなど)がレスポンスボディをプレーンテキストとして正しく解釈し、表示するために不可欠です。
  4. テストコードの更新:

    • 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("...") から "..." に変更されました。これにより、テストコードが新しい型定義に適合し、コンパイルエラーを防ぎます。

関連リンク

参考にした情報源リンク