github.com/shogo82148/std@v1.22.1-0.20240327122250-4e474527810c/log/slog/doc.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 /* 6 Package slogは、メッセージ、重大度レベル、およびキー-値ペアとして表されるさまざまなその他の属性を含むログレコードを提供する構造化されたログを提供します。 7 8 [Logger] という型を定義し、 9 [Logger.Info] や [Logger.Error] などのいくつかのメソッドを提供して、 10 興味深いイベントを報告するための構造化されたログを提供します。 11 12 各Loggerは [Handler] に関連付けられています。 13 Loggerの出力メソッドは、メソッド引数から [Record] を作成し、 14 それを処理する方法を決定するHandlerに渡します。 15 対応するLoggerメソッドを呼び出す [Info] や [Error] などのトップレベル関数を介してアクセス可能なデフォルトのLoggerがあります。 16 17 ログレコードは、時刻、レベル、メッセージ、およびキー-値ペアのセットで構成されます。 18 キーは文字列で、値は任意の型である場合があります。 19 例として、 20 21 slog.Info("hello", "count", 3) 22 23 呼び出しの時間、Infoレベル、メッセージ"hello"、および単一のキー"count"と値3を持つレコードを作成します。 24 25 [Info] トップレベル関数は、デフォルトのLogger上の [Logger.Info] メソッドを呼び出します。 26 [Logger.Info] に加えて、Debug、Warn、Errorレベルのメソッドがあります。 27 これらの一般的なレベルのための便利なメソッドに加えて、 28 [Logger.Log] メソッドがあり、レベルを引数として受け取ります。 29 これらのメソッドのそれぞれに対応するトップレベル関数があり、 30 デフォルトのロガーを使用します。 31 32 デフォルトのハンドラは、ログレコードのメッセージ、時刻、レベル、および属性を 33 文字列としてフォーマットし、 [log] パッケージに渡します。 34 35 2022/11/08 15:28:26 INFO hello count=3 36 37 出力フォーマットをより細かく制御するには、別のハンドラを持つロガーを作成します。 38 このステートメントでは、 [New] を使用して、 [TextHandler] で構造化されたレコードをテキスト形式で標準エラーに書き込む新しいロガーを作成しています。 39 40 logger := slog.New(slog.NewTextHandler(os.Stderr, nil)) 41 42 [TextHandler] の出力は、キー=値のペアのシーケンスであり、機械によって簡単かつ曖昧に解析できます。 43 この文: 44 45 logger.Info("hello", "count", 3) 46 47 は、次の出力を生成します。 48 49 time=2022-11-08T15:28:26.000-05:00 level=INFO msg=hello count=3 50 51 パッケージはまた、行区切りJSONで出力される [JSONHandler] を提供します。 52 53 logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) 54 logger.Info("hello", "count", 3) 55 56 次の出力を生成します: 57 58 {"time":"2022-11-08T15:28:26.000000000-05:00","level":"INFO","msg":"hello","count":3} 59 60 [TextHandler] と [JSONHandler] の両方は、 [HandlerOptions] で構成できます。 61 最小レベルの設定(以下の [Levels] を参照)、 62 ログ呼び出しのソースファイルと行の表示、 63 およびログに記録される前に属性を変更するためのオプションがあります。 64 65 デフォルトのロガーは以下のようにして変更できます。 66 67 slog.SetDefault(logger) 68 69 [Info] のようなトップレベルの関数がloggerを使用するようになります。 70 [SetDefault] は、 [log] パッケージが使用するデフォルトのロガーも更新します。 71 これにより [log.Printf] などを使用する既存のアプリケーションが、 72 書き換える必要なくログレコードをロガーのハンドラに送信できます。 73 74 多くのログ呼び出しで共通の属性があります。 75 たとえば、サーバーリクエストから生じるすべてのログイベントにURLやトレース識別子を含めたい場合があります。 76 ログ呼び出しごとに属性を繰り返す代わりに、 [Logger.With] を使用して属性を含む新しいLoggerを構築できます。 77 78 logger2 := logger.With("url", r.URL) 79 80 Withの引数は、 [Logger.Info] で使用されるキー-値ペアと同じです。 81 結果は、元のハンドラと同じハンドラを持つ新しいLoggerですが、 82 すべての呼び出しの出力に表示される追加の属性が含まれています。 83 84 # Levels 85 86 [Level] は、ログイベントの重要度または深刻度を表す整数です。 87 レベルが高いほど、イベントはより深刻です。 88 このパッケージは、最も一般的なレベルの定数を定義していますが、 89 任意のintをレベルとして使用できます。 90 91 アプリケーションでは、特定のレベル以上のメッセージのみをログに記録することが望ましい場合があります。 92 一般的な構成の1つは、Infoレベル以上のメッセージをログに記録し、 93 デバッグログを必要になるまで抑制することです。 94 組み込みのハンドラは、 [HandlerOptions.Level] を設定することで、 95 出力する最小レベルを構成できます。 96 通常、プログラムの`main`関数がこれを行います。 97 デフォルト値はLevelInfoです。 98 99 [HandlerOptions.Level] フィールドを [Level] 値に設定すると、 100 ハンドラの最小レベルがその寿命全体で固定されます。 101 [LevelVar] に設定すると、レベルを動的に変化させることができます。 102 LevelVarはLevelを保持し、複数のゴルーチンから読み書きすることができます。 103 プログラム全体でレベルを動的に変化させるには、まずグローバルなLevelVarを初期化します。 104 105 var programLevel = new(slog.LevelVar) // Info by default 106 107 次に、LevelVarを使用してハンドラを構築し、デフォルトにします。 108 109 h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: programLevel}) 110 slog.SetDefault(slog.New(h)) 111 112 プログラムは、単一のステートメントでログレベルを変更できるようになりました。 113 114 programLevel.Set(slog.LevelDebug) 115 116 # Groups 117 118 属性はグループに集めることができます。 119 グループには、属性の名前に修飾子として使用される名前があります。 120 この修飾子がどのように表示されるかは、ハンドラによって異なります。 121 [TextHandler] は、グループと属性名をドットで区切ります。 122 [JSONHandler] は、各グループを別々のJSONオブジェクトとして扱い、グループ名をキーとして扱います。 123 124 [Group] を使用して、名前とキー値のリストからグループ属性を作成します。 125 126 slog.Group("request", 127 "method", r.Method, 128 "url", r.URL) 129 130 [TextHandler] は、このグループを次のように表示します。 131 132 request.method=GET request.url=http://example.com 133 134 [JSONHandler] は、次のように表示します。 135 136 "request":{"method":"GET","url":"http://example.com"} 137 138 [Logger.WithGroup] を使用して、Loggerのすべての出力にグループ名を付けます。 139 LoggerでWithGroupを呼び出すと、元のハンドラと同じハンドラを持つ新しいLoggerが生成されますが、すべての属性がグループ名で修飾されます。 140 141 これにより、大規模なシステムで重複した属性キーを防止できます。 142 サブシステムが同じキーを使用する可能性がある場合、異なるグループ名を持つ独自のLoggerを各サブシステムに渡して、潜在的な重複を修飾します。 143 144 logger := slog.Default().With("id", systemID) 145 parserLogger := logger.WithGroup("parser") 146 parseInput(input, parserLogger) 147 148 parseInputがparserLoggerでログを記録する場合、そのキーは "parser"で修飾されるため、共通のキー "id"を使用していても、ログ行には異なるキーがあります。 149 150 # Contexts 151 152 一部のハンドラは、呼び出し元で利用可能な [context.Context] から情報を取得することを望む場合があります。 153 トレースが有効になっている場合、現在のスパンの識別子などの情報が含まれます。 154 155 [Logger.Log] と [Logger.LogAttrs] メソッドは、対応するトップレベル関数と同様に、最初の引数としてコンテキストを取ります。 156 157 Loggerの便利なメソッド(Infoなど)と対応するトップレベル関数は、コンテキストを取りませんが、"Context"で終わる代替メソッドはコンテキストを取ります。例えば、 158 159 slog.InfoContext(ctx, "message") 160 161 出力メソッドにコンテキストが利用可能な場合は、コンテキストを渡すことをお勧めします。 162 163 # Attrs and Values 164 165 [Attr] は、キーと値のペアです。Loggerの出力メソッドは、Attrsと交互にキーと値を受け入れます。以下の文を参照してください。 166 167 slog.Info("hello", slog.Int("count", 3)) 168 169 以下のように動作します。 170 171 slog.Info("hello", "count", 3) 172 173 [Attr] には [Int] 、 [String] 、 [Bool] などの便利なコンストラクタがあり、一般的な型に対して、 [Any] 関数を使用して任意の型の [Attr] を構築することもできます。 174 175 [Attr] の値部分は [Value] と呼ばれる型です。 176 [any] のように、 [Value] は任意のGo値を保持できますが、 177 すべての数値と文字列を含む一般的な値を、割り当てなしで表現できます。 178 179 最も効率的なログ出力には、 [Logger.LogAttrs] を使用してください。 180 これは [Logger.Log] に似ていますが、交互にキーと値を受け入れるのではなく、Attrsのみを受け入れるため、これも割り当てを回避できます。 181 182 logger.LogAttrs(ctx, slog.LevelInfo, "hello", slog.Int("count", 3)) 183 184 は、以下と同じ出力を生成する最も効率的な方法です。 185 186 slog.Info("hello", "count", 3) 187 188 slog.InfoContext(ctx, "hello", "count", 3) 189 190 # Customizing a type's logging behavior 191 192 タイプが [LogValuer] インターフェースを実装している場合、その [LogValue] メソッドから返される [Value] がログ出力に使用されます。 193 これを使用して、タイプの値がログにどのように表示されるかを制御できます。 194 例えば、パスワードのような秘密情報を伏せたり、構造体のフィールドをグループ化したりすることができます。 195 詳細については、 [LogValuer] の例を参照してください。 196 197 LogValueメソッドは、 [LogValuer] を実装している [Value] を返すことができます。 198 [Value.Resolve] メソッドは、これらの場合に無限ループや無制限の再帰を回避するように注意して処理します。 199 ハンドラの作者やその他の人々は、LogValueを直接呼び出す代わりに、[Value.Resolve] を使用したい場合があります。 200 201 # Wrapping output methods 202 203 ロガー関数は、呼び出し元のコールスタック上でリフレクションを使用して、アプリケーション内のログ呼び出しのファイル名と行番号を検索します。 204 これは、slogをラップする関数に対して誤ったソース情報を生成する可能性があります。 205 たとえば、mylog.goファイルでこの関数を定義する場合、以下のようになります。 206 207 func Infof(logger *slog.Logger, format string, args ...any) { 208 logger.Info(fmt.Sprintf(format, args...)) 209 210 211 } 212 213 そして、main.goで次のように呼び出す場合、 214 215 Infof(slog.Default(), "hello, %s", "world") 216 217 slogは、ソースファイルをmylog.goではなくmain.goとして報告しません。 218 219 Infofの正しい実装は、ソースの場所(pc)を取得し、NewRecordに渡す必要があります。 220 パッケージレベルの例である "wrapping" で示されているように、Infof関数の実装方法を示します。 221 222 # Working with Records 223 224 ハンドラが別のハンドラやバックエンドに渡す前に、レコードを変更する必要がある場合があります。 225 レコードには、単純な公開フィールド(例: Time、Level、Message)と、状態(属性など)を間接的に参照する非公開フィールドが混在しています。 226 これは、レコードの単純なコピーを変更する(例えば、属性を追加するために [Record.Add] または [Record.AddAttrs] を呼び出す)と、元のレコードに予期しない影響を与える可能性があることを意味します。 227 レコードを変更する前に、 [Record.Clone] を使用して、元のレコードと状態を共有しないコピーを作成するか、 [NewRecord] で新しいレコードを作成し、 [Record.Attrs] を使用して古いレコードをトラバースしてそのAttrsを構築してください。 228 229 # Performance considerations 230 231 アプリケーションのプロファイリングによって、ログの記録にかかる時間がかなりあることが示された場合、以下の提案が役立つ場合があります。 232 233 多くのログ行に共通の属性がある場合は、 [Logger.With] を使用して、その属性を持つLoggerを作成します。 234 組み込みのハンドラは、 [Logger.With] の呼び出し時にその属性を1回だけフォーマットします。 235 [Handler] インターフェースは、その最適化を許容するように設計されており、適切に書かれたHandlerはそれを活用するはずです。 236 237 ログ呼び出しの引数は常に評価されます。たとえログイベントが破棄された場合でもです。 238 可能であれば、値が実際にログに記録される場合にのみ計算が行われるように遅延させてください。 239 たとえば、次の呼び出しを考えてみてください。 240 241 slog.Info("starting request", "url", r.URL.String()) // may compute String unnecessarily 242 243 URL.Stringメソッドは、ロガーがInfoレベルのイベントを破棄する場合でも呼び出されます。 244 代わりに、URLを直接渡してください。 245 246 slog.Info("starting request", "url", &r.URL) // calls URL.String only if needed 247 248 組み込みの [TextHandler] は、そのStringメソッドを呼び出しますが、 249 ログイベントが有効になっている場合にのみ呼び出します。 250 Stringの呼び出しを回避することは、基礎となる値の構造を保持することもできます。 251 例えば、 [JSONHandler] は解析されたURLのコンポーネントをJSONオブジェクトとして出力します。 252 String呼び出しのコストを支払うことを避けたい場合、 253 値の構造を検査する可能性のあるハンドラを引き起こすことなく、 254 その値を隠すfmt.Stringer実装でラップしてください。 255 256 [LogValuer] インターフェースを使用すると、無効なログ呼び出しで不必要な作業を回避できます。 257 例えば、高価な値をログに記録する必要がある場合を考えてみましょう。 258 259 slog.Debug("frobbing", "value", computeExpensiveValue(arg)) 260 261 この行が無効になっていても、computeExpensiveValueが呼び出されます。 262 これを回避するには、LogValuerを実装する型を定義します。 263 264 type expensive struct { arg int } 265 266 func (e expensive) LogValue() slog.Value { 267 return slog.AnyValue(computeExpensiveValue(e.arg)) 268 } 269 270 そして、ログ呼び出しでその型の値を使用します。 271 272 slog.Debug("frobbing", "value", expensive{arg}) 273 274 これで、行が有効になっている場合にのみcomputeExpensiveValueが呼び出されます。 275 276 組み込みのハンドラは、[io.Writer.Write] を呼び出す前にロックを取得して、 277 一度に正確に一つの [Record] が完全に書き込まれることを保証します。 278 各ログレコードにはタイムスタンプがありますが、 279 組み込みのハンドラはその時間を使用して書き込まれたレコードをソートしません。 280 ユーザー定義のハンドラは、自身のロックとソートを担当します。 281 282 # ハンドラの作成 283 284 カスタムハンドラの作成方法についてのガイドについては、https://golang.org/s/slog-handler-guide を参照してください。 285 */ 286 package slog