Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Deep dive into log/slog package
Search
Pana
December 11, 2024
Programming
1
330
Deep dive into log/slog package
Go Conference mini 2023 Winter IN KYOTOでの登壇資料になります
Pana
December 11, 2024
Tweet
Share
More Decks by Pana
See All by Pana
testing/synctest使ってみた
k3forx
0
46
Other Decks in Programming
See All in Programming
すべてのコンテキストを、 ユーザー価値に変える
applism118
4
1.4k
Modern Angular with Signals and Signal Store:New Rules for Your Architecture @enterJS Advanced Angular Day 2025
manfredsteyer
PRO
0
240
新メンバーも今日から大活躍!SREが支えるスケールし続ける組織のオンボーディング
honmarkhunt
5
8.1k
Webの外へ飛び出せ NativePHPが切り拓くPHPの未来
takuyakatsusa
2
570
PipeCDのプラグイン化で目指すところ
warashi
1
290
AI コーディングエージェントの時代へ:JetBrains が描く開発の未来
masaruhr
1
200
Android 16KBページサイズ対応をはじめからていねいに
mine2424
0
220
ご注文の差分はこちらですか? 〜 AWS CDK のいろいろな差分検出と安全なデプロイ
konokenj
3
410
AIと”コードの評価関数”を共有する / Share the "code evaluation function" with AI
euglena1215
1
170
PHPで始める振る舞い駆動開発(Behaviour-Driven Development)
ohmori_yusuke
2
420
Rails Frontend Evolution: It Was a Setup All Along
skryukov
0
230
レベル1の開発生産性向上に取り組む − 日々の作業の効率化・自動化を通じた改善活動
kesoji
0
270
Featured
See All Featured
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
20
1.3k
Code Reviewing Like a Champion
maltzj
524
40k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
26k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
35
2.4k
Statistics for Hackers
jakevdp
799
220k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Gamification - CAS2011
davidbonilla
81
5.4k
Speed Design
sergeychernyshev
32
1k
Testing 201, or: Great Expectations
jmmastey
43
7.6k
Unsuck your backbone
ammeep
671
58k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
34
5.9k
Transcript
Deep dive into log/slog package Pana@Go Conference mini 2023 Winter
IN KYOTO
本日のトークの内容 • 自己紹介 • log/slogパッケージの概要 • 基本的なAPI • パフォーマンスに関して
自己紹介 • 宮鼻 (Pana) • 株式会社SODAでWebエンジニア ◦ 2年くらいGoを書いている ◦ Zennに記事を書いている
• 好き ◦ コーヒー ☕ ◦ つけ麺 • GitHub ◦ k3forx
本日話すこと/話さないこと • 話すこと ◦ log/slogが実装/導入された背景や目的 ◦ 公開されているAPIの概要/内部のアーキテクチャ ◦ パフォーマンス観点での詳細な実装/設計 •
話さないこと ◦ log/slogのAPIの詳細な使い方 ◦ Webアプリケーションとの連携
log/slogパッケージの概要
log/slogパッケージとは • Go1.21のリリースで開発された構造化ログのための標準ライブラリ ◦ 現時点 (2023/11時点) ではテキスト形式とJSON形式での出力をサポートしてい る • 他のロギングパッケージは?
◦ logrus, zap, zerologなど色々ある
なぜlog/slogパッケージが開発された? • 色々なパッケージが開発された結果 ◦ 巨大なアプリケーションでは依存関係を通して様々なロギングパッケージに依存し ている • 何を解決したかった? ◦ ログを統一的に扱えるようできる
◦ ユーザーがカスタマイズ可能な ”バックエンド” を定義した ◦ 将来的に色々なロギングパッケージがこの “バックエンド” を受け付ける (実装す る) ようになることを期待 ◦ より高品質なバックエンドを作成するためにGoのコミュニティが協力し合える ◦ 詳細は Proposal: Structured Logging#what-does-success-look-like に記載があ る
基本的なアーキテクチャ • Logger構造体 ◦ ユーザー (開発者) から呼ばれる関数を定義 ◦ Record構造体を生成しHandlerにわたす •
Record構造体 ◦ ログの内容を構造体として保持する • Handlerインターフェイス ◦ Record構造体を受け取り処理する
基本的なAPI (使い方)
Infoメソッドをみてみる Output: • time, levelが出力 (デフォルト) • 第一引数はmsgのvalueとして出力 (デフォルト) •
その後の引数はkey-valueとして出力 (なくても良い) ソースの位置の出力有 無などが設定できる
内部の構造を見てみる 1. Record構造体を生成 + argsを追加 2. Hanlderインターフェイスに 渡す ログレベル
同じ情報を固定で出力させる Output: • Withメソッドを使えば同じメッセージを出力できる
同じような情報をまとめて出力 Output: • WithGroupメソッドを使えば、オプションのkey-valueをまとめて出力できる
パフォーマンスに関して
随所に現れるパフォーマンスを意識した設計 • log/slogでいうパフォーマンスとは? ◦ 主にメモリアロケーションのこと ◦ つまり、メモリのアロケーションをできるだけ少なくしている
どこに現れるか? • 色々な構造体/インターフェイス/型に現れている!! ◦ Logger構造体 ◦ Handlerインターフェイス ◦ Record構造体 ▪
Add関数 ◦ Attr (Value) 構造体 ◦ etc…
どこに現れるか? • 色々な構造体/インターフェイスに現れている!! ◦ Logger構造体 ◦ Handlerインターフェイス ◦ Record構造体 ⭐
▪ Add関数 ⭐ ◦ Attr (Value) 構造体 ⭐ ◦ etc…
関数の呼び出し (インライン展開) • インライン展開とは? ◦ 関数を呼び出す側に呼び出される側の関数のコードを展開し、関 数への制御転送をしないようにする方法。これにより関数呼び出し に伴うオーバーヘッドを削減する (Wikipediaより抜粋) •
コンパイラがいい感じにインライン展開してくれる • インライン展開できるかどうかは細かい条件がある ◦ https://github.com/golang/go/wiki/CompilerOptimizations#fun ction-inlining
関数の呼び出し (直接 vs 間接) • 試してみる • ビルドの最適化オプションを有効化
関数の呼び出し (直接 vs 間接) • 試してみる • 実行ファイルを逆アセンブルする
関数の呼び出し (直接 vs 間接) • インターフェイス経由でのメソッド呼び出しはインライン展開されない場 合がある (されることもある) ◦ 関数呼び出しにオーバーヘッドが生じるのでパフォーマンスに影響
する可能性がある • log/slogではどこで意識されているのか? ◦ Loggerはインターフェイスではなく構造体 になっている ▪ 呼び出し側でインターフェイスによる間接的なメソッド呼び出し にならないようにしている ◦ Infoメソッドなどはインターフェイス経由ではなく直接呼び出せる よ うになっている
Logger構造体のパフォーマンスメモ ✍ インライン展開されるようにインターフェースで はなく構造体として定義されている
Handlerインターフェイス Handleメソッド以外はパフォーマンス (最適 化) のためのメソッド
Handlerインターフェイス (Enabledメソッド) メソッドの最初に呼ばれる →早期リターン
Enabledメソッドのパフォーマンスメモ ✍ 無駄にログを出力しないように関数の一番最初 に呼ばれるようになっている
Handlerインターフェイス (WithAttrsメソッド) • WithAttrメソッドは引数のattrsを[]byteに変換した後、新しいHandler を生成する ◦ Handlerは内部にhandlerStateを持つ ◦ bufの型の*buffer.Bufferは実際には[]byte型 •
さらに[]byteはsync.Poolを使用
sync.Poolとは? • GoDocをみてみる ◦ 「... Pool’s purpose is to cache
allocated but unused items for later reuse, relieving pressure on the garbage collector. …」
sync.Poolとは? • GoDocをみてみる ◦ 「プールの目的は、割り当てられたが未使用のアイテムをキャッシュ し、後で再利用することで、ガベージ・コレクターの負担を軽減する ことである。」 • → 一時的に何かしらの値を保持して後で使いたい時
/確保のコス トが高いものをあらかじめに用意しておきたい時に便利 • log/slogパッケージ以外では、fmtパッケージやencoding/jsonパッケー ジでも使用されている • パフォーマンスに関するベンチマーク等の話はたくさん記事が出ている のでここでは割愛
sync.Poolの基本的な使い方 • GoDocのExampleをみてみる 1. 再利用したいアイテムを定義 2. アイテムを利用したい場合は Get メソッドを呼ぶ 3.
アイテムを更新した後は Putメ ソッドを呼ぶ
log/slogパッケージではどう使われているか? • 実装 Freeメソッドで空にした後に Putメ ソッドを呼ぶ ある程度大きい初期化を行うことで appendによるメモリの再確保を避け る
log/slogパッケージではどう使われているか? Handleメソッドでは書き込む内容を bufにつめ て、出力したのちにFreeメソッドを呼ぶ → bufは常に空に初期化される WithAttrsメソッドでは引数のattrsをbufにつ めて、新しいhandlerを返すような処理 → 元々のHandlerに影響を出さずに、引数
のattrsを高速に出力できるような仕組み
WithAttrsメソッドのパフォーマンスメモ ✍ • sync.Poolを使うことによって Handlerのメソッド を呼ぶたびに都度メモリを確保しないようになっ ている • 引数のattrsはbufに保存され、新しい Handler
ではその保存された bufを内部的に保持してい る
Record構造体 (Addメソッド) anyの可変長引数をAttrに 変換してる
Attr構造体 (Value構造体) uint64とanyのみでGoの型を表現 している
こう思いました... Valueフィールドの型はanyだとダメなのか?
結論は... Valueの方がanyより少しだけメモリ効率が良い →文字列 (string型) を例に考えてみる
(おさらい) Goにおけるany型とstring型
stringをanyで表現しようとすると... stringへのポインタ (anyが持つ) + byteへのポインタ (stringが持つ)
Value構造体でのstringの表現を見てみる 文字列の長さ byteへのポインタ anyの場合
Value構造体でのstringの表現を見てみる 文字列の長さ byteへのポインタ Valueの場合
Attr (Value) 構造体のパフォーマンスメモ ✍ Value構造体を定義することで anyをそのまま扱う より少しだけメモリ効率が良い設計になっている
Record構造体 (Addメソッド) なんかちょっと複雑なことをやってい るように見える...なぜ?
Record構造体 最初に長さ5の配列が初期化される
Record構造体 (Addメソッド) 最初 (nFront=0, len(r.Front)=5) は初期化済みの5つの配列にAttrを 詰める 配列が埋まったあとは、残りの argsを (Attr単位で)
カウントして、最初にそ の容量を確保している (appendによる 再確保はない)
なぜ nAttrsInline = 5となっているのか? A. zapを使っているコードを調べたところ9割くらいのロガーが5個の key-valueを出力していた (https://youtu.be/8rnI2xLrdeM?si=oCuKdvPQcrW08dXF から)
Record構造体のAdd関数のパフォーマンスメモ ✍ • zapの使い方から使用される key-valueペアの個 数を調査していた • 最初にその個数分のメモリだけ初期化し、追加 でメモリの再確保が可能な限り起きないように なっていた
(起きたとしても 1回)
まとめ
log/slogパッケージまとめ • 構造化ログを生成する初の標準パッケージ • パフォーマンスについて色々な工夫が見られた ◦ メモリのアロケーションを何回も行わない工夫が見られた • 突き詰めて実装コードを読んでみると、意外と難しいことはやっていない ように思えた
◦ Goの言語仕様をきちんと理解した上の設計/実装になっていた • Logger構造体やHandlerインターフェイスのパフォーマンスに関する調 査は後日ブログで公開予定
ありがとうございました