slog
Rustにおける構造化・コンテキスト・拡張可能・組み合わせ可能なロギングライブラリ。標準logクレートのスーパーセットを意図して設計。組み合わせ(composition)がslogの強力な売りで、柔軟なロガー構築が可能。
ライブラリ
slog
概要
slogは「Go言語の構造化ロギング標準ライブラリ」として、Go 1.21で正式に標準ライブラリに追加された最新の高性能ロギングソリューションです。従来のlog標準パッケージに代わる次世代ロギングライブラリとして設計され、構造化ログ、レベル分けログ、コンテキスト対応ログを標準機能として提供。JSON、テキスト、カスタムフォーマットでの出力に対応し、ゼロアロケーション最適化による高いパフォーマンスを実現。サードパーティライブラリへの依存なしで、モダンなクラウドネイティブアプリケーションに必要な包括的なロギング機能を標準で利用可能です。
詳細
slog 2025年版は、Go言語エコシステムにおける統一的なロギング標準として確立され、サードパーティロガー(logrus、zap等)からの移行が急速に進んでいます。構造化ログのファーストクラスサポートにより、Prometheus、Grafana、Elasticsearch等の監視・分析ツールとの連携が大幅に強化。コンテキストベースのロギング設計により、分散トレーシング、リクエストID追跡、メトリクス収集との自然な統合を実現。ハンドラーベースのアーキテクチャにより、出力先やフォーマットの柔軟なカスタマイズが可能で、大規模なマイクロサービス環境での運用要件にも対応します。
主な特徴
- 標準ライブラリ統合: Go 1.21以降の標準パッケージ、外部依存なし
- 構造化ログファースト: JSON/Key-Value形式での自然な構造化ログ出力
- 高性能設計: ゼロアロケーション最適化による優秀なパフォーマンス
- ハンドラーアーキテクチャ: 柔軟な出力カスタマイズとフォーマット対応
- コンテキスト統合: context.Contextとの完全統合による分散トレーシング対応
- レベル制御: Debug、Info、Warn、Error の標準レベル + カスタムレベル対応
メリット・デメリット
メリット
- Go標準ライブラリのため依存関係なしで即座に利用開始可能
- 構造化ログのネイティブサポートによる近代的なログ分析基盤構築
- ゼロアロケーション設計による業界最高クラスの高性能
- コンテキストベース設計による分散システムでの優れた追跡性
- ハンドラーパターンによる高い拡張性とカスタマイズ性
- JSON出力対応による既存監視インフラとの簡単統合
デメリット
- Go 1.21以降が必要で、古いバージョンでは利用不可
- 従来のlog標準パッケージと比較してAPI学習コストあり
- サードパーティエコシステムは発展途上で機能拡張が限定的
- 複雑な条件分岐ログや高度なフィルタリング機能は自実装必要
- ログローテーションやファイル管理機能は標準では未提供
- レガシーコードからの移行時に既存ログ出力の大幅な変更必要
参考ページ
書き方の例
インストール・セットアップ
// Go 1.21以降で標準ライブラリとして利用可能
// 追加インストール不要
package main
import (
"log/slog"
"os"
)
func main() {
// 基本的な動作確認
slog.Info("slog is ready to use!")
slog.Debug("Debug message") // デフォルトでは表示されない(INFOレベル以上のため)
// デバッグレベルでの動作確認
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
logger.Debug("Debug message is now visible")
logger.Info("Info message")
logger.Warn("Warning message")
logger.Error("Error message")
}
// Go modulesでの最小設定例
// go.mod
module example.com/slog-example
go 1.21
// main.go
package main
import (
"context"
"log/slog"
"os"
)
func init() {
// アプリケーション全体でのデフォルトロガー設定
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
}
func main() {
ctx := context.Background()
// デフォルトロガーでの基本出力
slog.InfoContext(ctx, "Application started",
slog.String("version", "1.0.0"),
slog.Int("port", 8080))
}
基本的なログ出力
package main
import (
"context"
"errors"
"log/slog"
"os"
"time"
)
func demonstrateBasicLogging() {
// デフォルトロガーでの基本出力
slog.Debug("詳細なデバッグ情報") // デフォルトでは出力されない
slog.Info("一般的な情報メッセージ")
slog.Warn("警告: 注意が必要な状況")
slog.Error("エラー: 何かが失敗しました")
// 構造化ログの基本的な使い方
userID := 12345
userName := "田中太郎"
slog.Info("ユーザーログイン",
slog.Int("user_id", userID),
slog.String("user_name", userName),
slog.Time("login_time", time.Now()))
// グループ化されたフィールド
slog.Info("注文処理",
slog.Group("user",
slog.Int("id", 123),
slog.String("name", "山田花子")),
slog.Group("order",
slog.String("id", "ORD-001"),
slog.Float64("amount", 15800.50),
slog.Int("items", 3)))
// エラーログの出力
err := errors.New("データベース接続エラー")
slog.Error("処理失敗",
slog.Any("error", err),
slog.String("operation", "user_fetch"),
slog.Duration("retry_after", 5*time.Second))
// コンテキスト付きログ
ctx := context.Background()
slog.InfoContext(ctx, "コンテキスト付きメッセージ",
slog.String("request_id", "req-abc123"),
slog.String("client_ip", "192.168.1.100"))
// 異なるデータ型での出力
slog.Info("多様なデータ型の例",
slog.Bool("success", true),
slog.Int64("timestamp", time.Now().Unix()),
slog.Uint64("memory_bytes", 1073741824),
slog.Float32("cpu_usage", 23.5),
slog.Any("config", map[string]interface{}{
"timeout": "30s",
"retries": 3,
"enabled": true,
}))
// アトリビュート(Key-Value)の直接指定
attrs := []slog.Attr{
slog.String("service", "user-api"),
slog.String("version", "1.2.3"),
slog.Int("pid", os.Getpid()),
}
slog.LogAttrs(context.Background(), slog.LevelInfo, "サービス情報", attrs...)
}
ログレベル設定
package main
import (
"context"
"log/slog"
"os"
)
func demonstrateLogLevels() {
// デバッグレベルのハンドラー設定
debugHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
})
debugLogger := slog.New(debugHandler)
// 警告レベル以上のハンドラー設定
warnHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelWarn,
})
warnLogger := slog.New(warnHandler)
// レベル別のテスト
testMessage := "テストメッセージ"
debugLogger.Debug(testMessage + " - Debug") // 表示される
debugLogger.Info(testMessage + " - Info") // 表示される
debugLogger.Warn(testMessage + " - Warn") // 表示される
debugLogger.Error(testMessage + " - Error") // 表示される
warnLogger.Debug(testMessage + " - Debug") // 表示されない
warnLogger.Info(testMessage + " - Info") // 表示されない
warnLogger.Warn(testMessage + " - Warn") // 表示される
warnLogger.Error(testMessage + " - Error") // 表示される
// カスタムレベルの定義
const (
LevelTrace = slog.Level(-8) // DEBUGより詳細
LevelFatal = slog.Level(12) // ERRORより重要
)
// カスタムレベルでのログ出力
ctx := context.Background()
debugLogger.Log(ctx, LevelTrace, "トレースレベルメッセージ",
slog.String("function", "demonstrateLogLevels"))
debugLogger.Log(ctx, LevelFatal, "致命的なエラー",
slog.String("error", "システムが回復不能な状態"))
// 動的レベル制御
levelVar := &slog.LevelVar{}
levelVar.Set(slog.LevelInfo)
dynamicHandler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: levelVar,
})
dynamicLogger := slog.New(dynamicHandler)
dynamicLogger.Debug("動的レベル - Debug") // 表示されない(INFOレベル)
dynamicLogger.Info("動的レベル - Info") // 表示される
// レベルを変更
levelVar.Set(slog.LevelDebug)
dynamicLogger.Debug("動的レベル - Debug") // 表示される(DEBUGレベルに変更後)
}
// カスタムレベルの名前表示
func customLevelNames() *slog.Logger {
replace := func(groups []string, a slog.Attr) slog.Attr {
if a.Key == slog.LevelKey {
level := a.Value.Any().(slog.Level)
switch {
case level < slog.LevelDebug:
a.Value = slog.StringValue("TRACE")
case level < slog.LevelInfo:
a.Value = slog.StringValue("DEBUG")
case level < slog.LevelWarn:
a.Value = slog.StringValue("INFO")
case level < slog.LevelError:
a.Value = slog.StringValue("WARN")
case level >= slog.LevelError && level < slog.Level(12):
a.Value = slog.StringValue("ERROR")
default:
a.Value = slog.StringValue("FATAL")
}
}
return a
}
handler := slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
ReplaceAttr: replace,
})
return slog.New(handler)
}
Go固有機能
package main
import (
"context"
"log/slog"
"net/http"
"os"
"runtime"
"sync"
"time"
)
func demonstrateGoSpecificFeatures() {
// Goroutine IDとランタイム情報のログ
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
AddSource: true, // ソースコード位置情報を追加
}))
// Goroutineでの並行ログ出力
var wg sync.WaitGroup
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// Goroutineローカル情報の追加
logger.Info("Goroutine処理",
slog.Int("goroutine_id", id),
slog.Int("num_goroutine", runtime.NumGoroutine()),
slog.Int("num_cpu", runtime.NumCPU()))
time.Sleep(100 * time.Millisecond)
logger.Info("Goroutine完了",
slog.Int("goroutine_id", id))
}(i)
}
wg.Wait()
// HTTPリクエスト処理でのコンテキストログ
demonstrateHTTPContextLogging()
// チャネルとselect文でのログ
demonstrateChannelLogging()
// インターフェースとリフレクションログ
demonstrateInterfaceLogging()
// メモリ統計のログ
demonstrateMemoryStatsLogging()
}
func demonstrateHTTPContextLogging() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// リクエスト開始ログ
start := time.Now()
requestID := "req-" + time.Now().Format("20060102150405")
// コンテキストにリクエスト情報を追加
ctx := context.WithValue(r.Context(), "request_id", requestID)
logger.InfoContext(ctx, "HTTPリクエスト開始",
slog.String("method", r.Method),
slog.String("path", r.URL.Path),
slog.String("remote_addr", r.RemoteAddr),
slog.String("user_agent", r.UserAgent()),
slog.String("request_id", requestID))
// 処理のシミュレーション
time.Sleep(50 * time.Millisecond)
// レスポンス完了ログ
duration := time.Since(start)
logger.InfoContext(ctx, "HTTPリクエスト完了",
slog.String("request_id", requestID),
slog.Duration("duration", duration),
slog.Int("status", 200),
slog.Int64("response_size", 1024))
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
// テスト用のリクエスト実行例
req, _ := http.NewRequest("GET", "/test", nil)
req.RemoteAddr = "127.0.0.1:12345"
req.Header.Set("User-Agent", "Go-Test-Client/1.0")
rr := &mockResponseWriter{}
handler.ServeHTTP(rr, req)
}
func demonstrateChannelLogging() {
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
// チャネル間通信のログ
ch1 := make(chan string, 2)
ch2 := make(chan int, 2)
done := make(chan bool)
go func() {
ch1 <- "メッセージ1"
ch1 <- "メッセージ2"
close(ch1)
ch2 <- 100
ch2 <- 200
close(ch2)
done <- true
}()
// select文でのチャネル処理ログ
go func() {
for {
select {
case msg, ok := <-ch1:
if !ok {
logger.Info("チャネル1クローズ")
ch1 = nil
continue
}
logger.Info("チャネル1からメッセージ受信",
slog.String("message", msg))
case num, ok := <-ch2:
if !ok {
logger.Info("チャネル2クローズ")
ch2 = nil
continue
}
logger.Info("チャネル2から数値受信",
slog.Int("number", num))
case <-time.After(1 * time.Second):
logger.Warn("チャネル受信タイムアウト")
return
case <-done:
logger.Info("処理完了シグナル受信")
return
}
}
}()
time.Sleep(500 * time.Millisecond)
}
func demonstrateInterfaceLogging() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
// インターフェース型のログ
var data interface{}
data = "文字列データ"
logger.Info("インターフェース値",
slog.Any("data", data),
slog.String("type", "string"))
data = 42
logger.Info("インターフェース値",
slog.Any("data", data),
slog.String("type", "int"))
data = map[string]interface{}{
"name": "テストユーザー",
"age": 30,
"active": true,
"scores": []int{85, 92, 78},
}
logger.Info("インターフェース値",
slog.Any("data", data),
slog.String("type", "map"))
// カスタム型のログ
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
user := User{ID: 123, Name: "田中太郎", Email: "[email protected]"}
logger.Info("カスタム型",
slog.Any("user", user))
}
func demonstrateMemoryStatsLogging() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
var m runtime.MemStats
runtime.ReadMemStats(&m)
logger.Info("メモリ統計",
slog.Uint64("alloc_bytes", m.Alloc),
slog.Uint64("total_alloc_bytes", m.TotalAlloc),
slog.Uint64("sys_bytes", m.Sys),
slog.Uint64("num_gc", uint64(m.NumGC)),
slog.Float64("gc_cpu_fraction", m.GCCPUFraction),
slog.Uint64("heap_alloc_bytes", m.HeapAlloc),
slog.Uint64("heap_sys_bytes", m.HeapSys),
slog.Uint64("stack_sys_bytes", m.StackSys))
// GC実行とその後の統計
runtime.GC()
runtime.ReadMemStats(&m)
logger.Info("GC実行後メモリ統計",
slog.Uint64("alloc_bytes", m.Alloc),
slog.Uint64("num_gc", uint64(m.NumGC)),
slog.Duration("last_gc", time.Duration(m.LastGC)))
}
// モックレスポンスライター
type mockResponseWriter struct {
statusCode int
data []byte
}
func (m *mockResponseWriter) Header() http.Header {
return make(http.Header)
}
func (m *mockResponseWriter) Write(data []byte) (int, error) {
m.data = append(m.data, data...)
return len(data), nil
}
func (m *mockResponseWriter) WriteHeader(statusCode int) {
m.statusCode = statusCode
}
設定ファイル例
// config/logger.go - ロガー設定の管理
package config
import (
"log/slog"
"os"
"strings"
)
type LogConfig struct {
Level string `json:"level" env:"LOG_LEVEL" default:"info"`
Format string `json:"format" env:"LOG_FORMAT" default:"json"`
Output string `json:"output" env:"LOG_OUTPUT" default:"stdout"`
}
func SetupLogger(config LogConfig) *slog.Logger {
// ログレベルの解析
var level slog.Level
switch strings.ToLower(config.Level) {
case "debug":
level = slog.LevelDebug
case "info":
level = slog.LevelInfo
case "warn", "warning":
level = slog.LevelWarn
case "error":
level = slog.LevelError
default:
level = slog.LevelInfo
}
// ハンドラーオプション
opts := &slog.HandlerOptions{
Level: level,
AddSource: true,
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
// タイムスタンプのカスタマイズ
if a.Key == slog.TimeKey {
return slog.Attr{
Key: "timestamp",
Value: a.Value,
}
}
// レベル名のカスタマイズ
if a.Key == slog.LevelKey {
return slog.Attr{
Key: "severity",
Value: a.Value,
}
}
return a
},
}
// 出力先の決定
var output *os.File
switch config.Output {
case "stderr":
output = os.Stderr
case "stdout":
output = os.Stdout
default:
// ファイル出力(実際の実装では適切なファイル管理が必要)
var err error
output, err = os.OpenFile(config.Output, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
output = os.Stdout
}
}
// ハンドラーの作成
var handler slog.Handler
switch strings.ToLower(config.Format) {
case "text":
handler = slog.NewTextHandler(output, opts)
case "json":
handler = slog.NewJSONHandler(output, opts)
default:
handler = slog.NewJSONHandler(output, opts)
}
return slog.New(handler)
}
// アプリケーション共通のロガー設定
func InitializeApplicationLogger() {
config := LogConfig{
Level: getEnvOrDefault("LOG_LEVEL", "info"),
Format: getEnvOrDefault("LOG_FORMAT", "json"),
Output: getEnvOrDefault("LOG_OUTPUT", "stdout"),
}
logger := SetupLogger(config)
slog.SetDefault(logger)
slog.Info("ロガー初期化完了",
slog.String("level", config.Level),
slog.String("format", config.Format),
slog.String("output", config.Output))
}
func getEnvOrDefault(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
# docker-compose.yml でのログ設定例
version: '3.8'
services:
app:
build: .
environment:
- LOG_LEVEL=debug
- LOG_FORMAT=json
- LOG_OUTPUT=stdout
volumes:
- ./logs:/app/logs
# Dockerfile でのログ設定
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o main .
FROM alpine:latest
RUN apk --no-cache add ca-certificates tzdata
WORKDIR /root/
COPY --from=builder /app/main .
# 本番環境用ログ設定
ENV LOG_LEVEL=info
ENV LOG_FORMAT=json
ENV LOG_OUTPUT=stdout
CMD ["./main"]
パフォーマンス最適化
package main
import (
"context"
"log/slog"
"os"
"runtime"
"sync"
"time"
)
func demonstratePerformanceOptimization() {
// ベンチマーク用のロガー設定
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo, // デバッグレベルを無効化してパフォーマンス向上
})
logger := slog.New(handler)
// 1. アトリビュートの事前構築による最適化
demonstratePrebuiltAttributes(logger)
// 2. バッチ処理による最適化
demonstrateBatchLogging(logger)
// 3. 条件付きログによる最適化
demonstrateConditionalLogging(logger)
// 4. ゼロアロケーション技法
demonstrateZeroAllocation(logger)
// 5. 並行処理でのログ最適化
demonstrateConcurrentLogging(logger)
}
func demonstratePrebuiltAttributes(logger *slog.Logger) {
// 事前構築されたアトリビュート(再利用)
serviceAttrs := []slog.Attr{
slog.String("service", "user-api"),
slog.String("version", "1.2.3"),
slog.Int("pid", os.Getpid()),
}
start := time.Now()
// 非効率な例(毎回アトリビュート作成)
for i := 0; i < 1000; i++ {
logger.Info("処理中",
slog.String("service", "user-api"),
slog.String("version", "1.2.3"),
slog.Int("pid", os.Getpid()),
slog.Int("iteration", i))
}
inefficientDuration := time.Since(start)
start = time.Now()
// 効率的な例(事前構築アトリビュート使用)
for i := 0; i < 1000; i++ {
attrs := make([]slog.Attr, len(serviceAttrs)+1)
copy(attrs, serviceAttrs)
attrs[len(serviceAttrs)] = slog.Int("iteration", i)
logger.LogAttrs(context.Background(), slog.LevelInfo, "処理中", attrs...)
}
efficientDuration := time.Since(start)
logger.Info("パフォーマンス比較",
slog.Duration("inefficient", inefficientDuration),
slog.Duration("efficient", efficientDuration),
slog.Float64("improvement", float64(inefficientDuration)/float64(efficientDuration)))
}
func demonstrateBatchLogging(logger *slog.Logger) {
// バッチサイズの設定
const batchSize = 100
start := time.Now()
// 個別ログ出力(非効率)
for i := 0; i < 1000; i++ {
logger.Info("個別処理", slog.Int("item", i))
}
individualDuration := time.Since(start)
start = time.Now()
// バッチ処理(効率的)
for batch := 0; batch < 10; batch++ {
attrs := make([]slog.Attr, batchSize)
for i := 0; i < batchSize; i++ {
attrs[i] = slog.Int("item", batch*batchSize+i)
}
logger.LogAttrs(context.Background(), slog.LevelInfo, "バッチ処理",
append([]slog.Attr{slog.Int("batch", batch)}, attrs...)...)
}
batchDuration := time.Since(start)
logger.Info("バッチ処理パフォーマンス",
slog.Duration("individual", individualDuration),
slog.Duration("batch", batchDuration))
}
func demonstrateConditionalLogging(logger *slog.Logger) {
// レベルチェックによる最適化
start := time.Now()
// 非効率な例(常に文字列構築)
for i := 0; i < 10000; i++ {
expensiveData := generateExpensiveString(i)
logger.Debug("デバッグ情報", slog.String("data", expensiveData))
}
unconditionalDuration := time.Since(start)
start = time.Now()
// 効率的な例(条件付き実行)
for i := 0; i < 10000; i++ {
if logger.Enabled(context.Background(), slog.LevelDebug) {
expensiveData := generateExpensiveString(i)
logger.Debug("デバッグ情報", slog.String("data", expensiveData))
}
}
conditionalDuration := time.Since(start)
logger.Info("条件付きログ最適化",
slog.Duration("unconditional", unconditionalDuration),
slog.Duration("conditional", conditionalDuration))
}
func demonstrateZeroAllocation(logger *slog.Logger) {
// ゼロアロケーション技法
var m runtime.MemStats
runtime.ReadMemStats(&m)
startAllocs := m.Allocs
// アロケーションを最小化したログ出力
ctx := context.Background()
for i := 0; i < 1000; i++ {
// 定数値の直接使用(アロケーション回避)
logger.LogAttrs(ctx, slog.LevelInfo, "ゼロアロケーション",
slog.Int("counter", i),
slog.Bool("enabled", true),
slog.Float64("ratio", 0.95))
}
runtime.ReadMemStats(&m)
endAllocs := m.Allocs
logger.Info("アロケーション統計",
slog.Uint64("start_allocs", startAllocs),
slog.Uint64("end_allocs", endAllocs),
slog.Uint64("new_allocs", endAllocs-startAllocs))
}
func demonstrateConcurrentLogging(logger *slog.Logger) {
const numGoroutines = 10
const logsPerGoroutine = 100
start := time.Now()
var wg sync.WaitGroup
// 並行ログ出力のパフォーマンステスト
for g := 0; g < numGoroutines; g++ {
wg.Add(1)
go func(goroutineID int) {
defer wg.Done()
for i := 0; i < logsPerGoroutine; i++ {
logger.Info("並行ログ",
slog.Int("goroutine", goroutineID),
slog.Int("iteration", i),
slog.Time("timestamp", time.Now()))
}
}(g)
}
wg.Wait()
duration := time.Since(start)
totalLogs := numGoroutines * logsPerGoroutine
logger.Info("並行ログパフォーマンス",
slog.Int("total_logs", totalLogs),
slog.Int("goroutines", numGoroutines),
slog.Duration("duration", duration),
slog.Float64("logs_per_second", float64(totalLogs)/duration.Seconds()))
}
// メモリプールを使った最適化例
type AttributePool struct {
pool sync.Pool
}
func NewAttributePool() *AttributePool {
return &AttributePool{
pool: sync.Pool{
New: func() interface{} {
return make([]slog.Attr, 0, 10)
},
},
}
}
func (p *AttributePool) Get() []slog.Attr {
return p.pool.Get().([]slog.Attr)
}
func (p *AttributePool) Put(attrs []slog.Attr) {
attrs = attrs[:0] // スライスをクリア
p.pool.Put(attrs)
}
func demonstrateAttributePooling(logger *slog.Logger) {
pool := NewAttributePool()
for i := 0; i < 1000; i++ {
attrs := pool.Get()
attrs = append(attrs,
slog.Int("id", i),
slog.String("status", "processing"),
slog.Time("created", time.Now()))
logger.LogAttrs(context.Background(), slog.LevelInfo, "プール使用", attrs...)
pool.Put(attrs)
}
}
func generateExpensiveString(n int) string {
// 高コストな文字列生成のシミュレーション
result := make([]byte, 0, 1000)
for i := 0; i < 100; i++ {
result = append(result, byte('A'+i%26))
}
return string(result)
}