いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

Goメモ-491 (slogメモ-13)(io.Writerとの連携)

関連記事

Goメモ-477 (slogメモ-01)(基本的な使い方) - いろいろ備忘録日記

Goメモ-478 (slogメモ-02)(構造化ログの出力) - いろいろ備忘録日記

Goメモ-479 (slogメモ-03)(デフォルトロガー) - いろいろ備忘録日記

Goメモ-480 (slogメモ-04)(従来のlogパッケージとの連携) - いろいろ備忘録日記

Goメモ-482 (slogメモ-05)(テキスト形式のログ) - いろいろ備忘録日記

Goメモ-483 (slogメモ-06)(JSON形式のログ) - いろいろ備忘録日記

Goメモ-484 (slogメモ-07)(動的にログレベルを変更) - いろいろ備忘録日記

Goメモ-485 (slogメモ-08)(グループ (1)) - いろいろ備忘録日記

Goメモ-486 (slogメモ-09)(グループ (2)) - いろいろ備忘録日記

Goメモ-487 (slogメモ-10)(機密情報などのマスキング) - いろいろ備忘録日記

Goメモ-488 (slogメモ-11)(カスタムログレベル) - いろいろ備忘録日記

Goメモ-490 (slogメモ-12)(context.Contextとの連携) - いろいろ備忘録日記

GitHub - devlights/blog-summary: ブログ「いろいろ備忘録日記」のまとめ

概要

以下、自分用のメモです。

今更ながら、Go 1.21で導入された log/slog を使ってみたりしています。

少しづつメモしていきます。今回はio.Writerとの連携について。

slogでは、ハンドラに io.Writer を渡して出力してもらうことになっています。

io.Writerが渡せるので、好きに調整できますね。

以下のサンプルでは、os.Stdoutlumberjack.Loggerio.MultiWriter で包んでハンドラに指定しています。

サンプル

main.go

package main

import (
    "context"
    "errors"
    "io"
    "log"
    "log/slog"
    "os"
    "time"

    "gopkg.in/natefinch/lumberjack.v2"
)

const (
    MainTimeout = time.Second
    ProcTimeout = 500 * time.Millisecond
)

var (
    ErrMainTooSlow = errors.New("(MAIN) TOO SLOW")
    ErrProcTooSlow = errors.New("(PROC) TOO SLOW")
)

func init() {
    log.SetFlags(0)
}

func main() {
    var (
        rootCtx          = context.Background()
        mainCtx, mainCxl = context.WithTimeoutCause(rootCtx, MainTimeout, ErrMainTooSlow)
        procCtx          = run(mainCtx)
        err              error
    )
    defer mainCxl()

    select {
    case <-mainCtx.Done():
        err = context.Cause(mainCtx)
    case <-procCtx.Done():
        if err = context.Cause(procCtx); errors.Is(err, context.Canceled) {
            err = nil
        }
    }

    if err != nil {
        log.Fatal(err)
    }
}

func run(pCtx context.Context) context.Context {
    var (
        ctx, cxl = context.WithCancelCause(pCtx)
    )

    go func() {
        cxl(proc(ctx))
    }()
    go func() {
        <-time.After(ProcTimeout)
        cxl(ErrProcTooSlow)
    }()

    return ctx
}

func proc(_ context.Context) error {
    //
    // slogでは、出力先をハンドラにて指定できる。
    // 出力先には、 io.Writer を指定出来るので好きに調整出来る。
    //
    // ここに gopkg.in/natefinch/lumberjack.v2 などを
    // 設定することにより、ローリングも可能になる。
    //
    // 本サンプルでは、 os.Stdout と lumberjack.Logger を
    // io.MultiWriter で包んで slog.Handler に指定している。
    //

    var (
        level = &slog.LevelVar{}
        opt   = &slog.HandlerOptions{
            Level:       level,
            ReplaceAttr: replaceAttr,
        }
        writer, closeFn = newWriter()
        handler         = slog.NewJSONHandler(writer, opt)
        rootLogger      = slog.New(handler)
        logger          *slog.Logger
    )
    defer closeFn()

    logger = rootLogger.With("loop-count", 10)
    for i := range 10 {
        // 奇数番目のときだけログに出るように小細工
        level.Set(slog.LevelDebug)
        if i%2 == 0 {
            level.Set(slog.LevelInfo)
        }

        logger.Debug("helloworld", "i", i)
    }

    return nil
}

func newWriter() (io.Writer, func()) {
    var (
        writer1 = os.Stdout
        writer2 = &lumberjack.Logger{
            Filename:   "/tmp/try-golang/slog-example/app.log",
            MaxSize:    1,
            MaxBackups: 3,
            MaxAge:     7,
            Compress:   false,
        }

        writer = io.MultiWriter(writer1, writer2)
    )

    return writer, func() { writer2.Close() }
}

func replaceAttr(g []string, a slog.Attr) slog.Attr {
    if a.Key == slog.TimeKey {
        return slog.Attr{}
    }

    return a
}

実行すると以下のように出力されます。

Taskfile.yml

# https://taskfile.dev

version: '3'

tasks:
  default:
    cmds:
      - task: run
  run:
    cmds:
      - go run main.go
      - defer: rm -rf /tmp/try-golang
      - ls -1 /tmp/try-golang/slog-example
      - cat /tmp/try-golang/slog-example/*.log

shell

$ task
task: [run] go run main.go
{"level":"DEBUG","msg":"helloworld","loop-count":10,"i":1}
{"level":"DEBUG","msg":"helloworld","loop-count":10,"i":3}
{"level":"DEBUG","msg":"helloworld","loop-count":10,"i":5}
{"level":"DEBUG","msg":"helloworld","loop-count":10,"i":7}
{"level":"DEBUG","msg":"helloworld","loop-count":10,"i":9}
task: [run] ls -1 /tmp/try-golang/slog-example
app.log
task: [run] cat /tmp/try-golang/slog-example/*.log
{"level":"DEBUG","msg":"helloworld","loop-count":10,"i":1}
{"level":"DEBUG","msg":"helloworld","loop-count":10,"i":3}
{"level":"DEBUG","msg":"helloworld","loop-count":10,"i":5}
{"level":"DEBUG","msg":"helloworld","loop-count":10,"i":7}
{"level":"DEBUG","msg":"helloworld","loop-count":10,"i":9}
task: [run] rm -rf /tmp/try-golang

ちゃんと標準出力にも出力されて、ログファイルにも出力されてますね。

try-golang/examples/slog at main · devlights/try-golang · GitHub

参考情報

Goのおすすめ書籍


過去の記事については、以下のページからご参照下さい。

サンプルコードは、以下の場所で公開しています。