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
460
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
Go1.25新機能 testing/synctest で高速&確実な並行テストを実現する方法
k3forx
0
80
Other Decks in Programming
See All in Programming
AIのバカさ加減に怒る前にやっておくこと
blueeventhorizon
0
130
テーブル定義書の構造化抽出して、生成AIでDWH分析を試してみた / devio2025tokyo
kasacchiful
0
340
オープンソースソフトウェアへの解像度🔬
utam0k
18
3.2k
Swift Concurrency 年表クイズ
omochi
3
210
CSC509 Lecture 08
javiergs
PRO
0
270
ネストしたdata classの面倒な更新にさようなら!Lensを作って理解するArrowのOpticsの世界
shiita0903
1
180
Blazing Fast UI Development with Compose Hot Reload (Bangladesh KUG, October 2025)
zsmb
2
430
なんでRustの環境構築してないのにRust製のツールが動くの? / Why Do Rust-Based Tools Run Without a Rust Environment?
ssssota
14
47k
AIと人間の共創開発!OSSで試行錯誤した開発スタイル
mae616
2
840
EMこそClaude Codeでコード調査しよう
shibayu36
0
510
SwiftDataを使って10万件のデータを読み書きする
akidon0000
0
250
Pythonに漸進的に型をつける
nealle
1
140
Featured
See All Featured
Fireside Chat
paigeccino
41
3.7k
GitHub's CSS Performance
jonrohan
1032
470k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
3.7k
jQuery: Nuts, Bolts and Bling
dougneiner
65
7.9k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Building Applications with DynamoDB
mza
96
6.7k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
194
16k
How STYLIGHT went responsive
nonsquared
100
5.9k
Gamification - CAS2011
davidbonilla
81
5.5k
Agile that works and the tools we love
rasmusluckow
331
21k
How To Stay Up To Date on Web Technology
chriscoyier
791
250k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
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インターフェイスのパフォーマンスに関する調 査は後日ブログで公開予定
ありがとうございました