Skip to content

Commit 6443958

Browse files
committed
slog
1 parent 1ffcd58 commit 6443958

File tree

4 files changed

+171
-1
lines changed

4 files changed

+171
-1
lines changed

source/_posts/20250730a_Go_1.25リリース連載始まります_&_trace.FlightRecorder.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Go 1.25 のリリースが近づいてきました。今回は比較的シンプ
2020
| Date | Title | Author |
2121
| :--: | :--------------- | :--------- |
2222
| 7/30 | この記事 | 渋川 |
23-
| 7/31 | log/slog | 武田さん |
23+
| 7/31 | [log/slog](/articles/20250731a/) | 武田さん |
2424
| 8/1 | sync | 辻さん |
2525
| 8/4 | net/http | 島ノ江さん |
2626
| 8/5 | testing/synctest | 市川燿さん |
@@ -188,3 +188,5 @@ func main() {
188188
いままでも便利な Trace はありましたが、FlightRecorder の「いつでもしておく」というのはオブザーバービリティ的な考えで良いですね。常に回しておける、というのは今までになかった使い方も出てくると思います。
189189

190190
特定のエンドポイントやシグナルでダンプをどこかに出力するような仕組みを作ってみたり、ダウンロードできるエンドポイントを作っておく、というのも良いかもしれません。「なんか goroutine がブロックしているな」とか「なんかメモリ消費量が跳ね上がったな」みたいなタイミングで、過去 1 分間分のトレースをダウンロードして分析してみる、といったことができると、問題の分析の役に立つ気がします。
191+
192+
次は武田さんの [log/slog](/articles/20250731a/) です。
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
---
2+
title: "Go 1.25 リリース連載 log/slog"
3+
date: 2025/07/31 00:00:00
4+
postid: a
5+
tag:
6+
- Go
7+
- Go1.25
8+
- 構造化ログ
9+
- ログ
10+
category:
11+
- Programming
12+
thumbnail: /images/20250731a/thumbnail.jpg
13+
author: 武田大輝
14+
lede: "log/slog パッケージのアップデートについて紹介します。Go の log/slog パッケージは、Go 1.21 で導入された構造化ロギングをサポートする標準ライブラリです。"
15+
---
16+
17+
<img src="/images/20250731a/top.jpg" alt="" width="512" height="512">
18+
19+
# はじめに
20+
21+
[Go 1.25 リリース連載](/articles/20250730a/) の 2 本目です。
22+
23+
本記事では [log/slog パッケージ](https://pkg.go.dev/log/slog@go1.25rc2) のアップデートについて紹介します。
24+
Go の log/slog パッケージは、Go 1.21 で導入された構造化ロギングをサポートする標準ライブラリです。
25+
26+
本記事では slog についての基本的は割愛しますが、slog の概要やこれまでのアップデート経緯をつかみたい方は、過去のリリース連載記事を参照してください。
27+
28+
- [Go 1.21 連載始まります&slog をどう使うべきか](/articles/20230731a/)
29+
- [Go 1.22 リリース連載 vet, log/slog, testing/slogtest](/articles/20240205a/)
30+
31+
# アップデートの概要
32+
33+
[リリースノート](https://go.dev/doc/go1.25#logslogpkglogslog) を参照してみましょう。
34+
35+
> GroupAttrs creates a group Attr from a slice of Attr values.
36+
>
37+
> Record now has a Source method, returning its source location or nil if unavailable.
38+
39+
- **`slog.GroupAttrs` の追加**
40+
複数の属性 (`[]slog.Attr`) を簡潔にグルーピングできるようになりました。
41+
- **`Record.Source()` の公開**
42+
ログエントリの発生元(ファイル・行番号・関数名)を取得できるようになりました。
43+
44+
それぞれ詳細な内容を見ていきましょう。
45+
46+
# アップデートの詳細
47+
48+
## slog.GroupAttrs による属性のグルーピング([#66365](https://github.com/golang/go/issues/66365)
49+
50+
`slog.GroupAttrs` を使用して構造化ログの属性(要素)をグルーピングできるようになりました。
51+
52+
```go
53+
g := slog.GroupAttrs("user",
54+
slog.String("id", "00001"),
55+
slog.String("name", "Bob"),
56+
)
57+
// {"level":"INFO","msg":"GroupAttrs","user":{"id":"00001","name":"Bob"}}
58+
logger.Info("GroupAttrs", g)
59+
```
60+
61+
属性のグルーピング自体はもともと `slog.Group` を使用して実現できましたが、属性を動的に生成する場合にいくつか使用上の問題がありました。
62+
63+
### 属性が静的な場合
64+
65+
```go
66+
g := slog.Group("user",
67+
slog.String("id", "00001"),
68+
slog.String("name", "Bob"),
69+
)
70+
// {"level":"INFO","msg":"Group","user":{"id":"00001","name":"Bob"}}
71+
logger.Info("Group", g)
72+
```
73+
74+
### 属性が動的な場合
75+
76+
条件に応じて属性を追加する場合などは `slog.Group` がうまく機能しません。
77+
78+
```go
79+
attrs := []slog.Attr{
80+
slog.String("id", "00001"),
81+
slog.String("name", "Bob"),
82+
}
83+
84+
// 条件に応じて属性を追加
85+
if showAge {
86+
attrs = append(attrs, slog.Int("age", 36))
87+
}
88+
89+
// []slog.Attr doesn't match []any となり動かない
90+
g := slog.Group("user", attrs...)
91+
```
92+
93+
そのためこれまでは `slog.Any` を利用したり、`[]slog.Attr``[]any` に変換したりして対応してきた背景があります。
94+
95+
```go
96+
// slog.Any を使用する
97+
g := slog.Any("user", slog.GroupValue(attrs...))
98+
99+
// ヘルパーファンクションを用意して []any に変換する
100+
g := slog.Group("key", attr2any(attrs)...)
101+
102+
// []any を使用する
103+
var attrs []any
104+
...
105+
g := slog.Group("key", attrs...)
106+
```
107+
108+
このアプローチは any の使用が避けられず、適切なコードサジェストがなされないなど直感的ではないということで `[]slog.Attr` を引数として渡せる `slog.GroupAttrs` が生まれました。
109+
110+
## record.Source によるソース情報の取得([#70280](https://github.com/golang/go/issues/70280)
111+
112+
Go の log/slog では、1 件のログエントリを内部では `slog.Record` という構造体で表現しています。
113+
Go1.25 で追加された `record.Source` はレコードからログの発生元(ファイル名・行番号・関数名)を返します。
114+
ソースの [Diff](https://github.com/golang/go/commit/044ca4e5c878c785e2c69e5ebcb3d44bf97abc9f) を見るとはもともとパッケージ内限定(unexported)だったものが公開された(export)された形になります。
115+
116+
これにより、自作のカスタムハンドラなどからもログのソース情報にアクセスできます。
117+
サンプルソースは次の通りです。
118+
119+
```go
120+
func main() {
121+
logger := slog.New(NewSourceHandler(slog.NewJSONHandler(os.Stdout, nil)))
122+
g := slog.Group("user",
123+
slog.String("id", "00001"),
124+
slog.String("name", "Bob"),
125+
)
126+
// LOG from /xxx/main.go:61 (main)
127+
logger.Info("Group", g)
128+
}
129+
130+
131+
type SourceHandler struct {
132+
h slog.Handler
133+
}
134+
135+
func NewSourceHandler(h slog.Handler) *SourceHandler {
136+
return &SourceHandler{
137+
h: h,
138+
}
139+
}
140+
141+
func (sh SourceHandler) Enabled(ctx context.Context, level slog.Level) bool {
142+
return sh.h.Enabled(ctx, level)
143+
}
144+
145+
func (sh SourceHandler) Handle(ctx context.Context, r slog.Record) error {
146+
// ソース情報を出力
147+
if src := r.Source(); src != nil {
148+
fmt.Printf("LOG from %s:%d (%s)\n", src.File, src.Line, src.Function)
149+
}
150+
return sh.h.Handle(ctx, r)
151+
}
152+
153+
func (sh SourceHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
154+
return SourceHandler{h: sh.h.WithAttrs(attrs)}
155+
}
156+
157+
func (sh SourceHandler) WithGroup(name string) slog.Handler {
158+
return SourceHandler{h: sh.h.WithGroup(name)}
159+
}
160+
```
161+
162+
# おわりに
163+
164+
Go 1.25 の `log/slog` では、構造化ログの柔軟性と拡張性がさらに向上しました。
165+
166+
サンプルのソースコードは [こちらのリポジトリ](https://github.com/rhumie/tech-blog-go-1.25-feature) で公開しています。
167+
168+
次回は `sync` のアップデートについてです。
35.1 KB
Loading

source/images/20250731a/top.jpg

86.6 KB
Loading

0 commit comments

Comments
 (0)