いろいろ備忘録日記

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

Goメモ-488 (slogメモ-11)(カスタムログレベル)

関連記事

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)(機密情報などのマスキング) - いろいろ備忘録日記

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

概要

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

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

少しづつメモしていきます。今回はカスタムログレベルの作成について。

サンプル

main.go

package main

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

const (
    MainTimeout = time.Second
    ProcTimeout = 10 * 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
}

const (
    LevelTrace   = slog.Level(-8)
    LevelVerbose = slog.Level(-2)
    LevelFatal   = slog.Level(12)
)

func proc(ctx context.Context) error {
    //
    // カスタムログレベル
    //
    // slogパッケージでは、デフォルトのログレベル(Debug、Info、Warn、Error)に加えて
    // カスタムログレベルを定義することが出来る。
    //
    // カスタムログレベルの作成は、slog.Level型を使用して行う。
    // 既定のログレベルの値は https://pkg.go.dev/log/slog@go1.23.2#Level を参照。
    //
    // - Debug: -4
    // - Info : 0
    // - Warn : 4
    // - Error: 8
    //
    // カスタムログレベルを作成するには、これらの既存のレベルの間の値を選択する。
    //
    // カスタムログレベルをそのままログ出力すると "DEBUG-4" のような表示となるため
    // 文字列表現を出力したい場合は、適時変換する関数などを用意して処理する。
    //

    var (
        level = slog.LevelDebug
        opt   = &slog.HandlerOptions{
            Level:       level,
            ReplaceAttr: replaceAttr,
        }
        writer     = os.Stdout
        handler    = slog.NewJSONHandler(writer, opt)
        rootLogger = slog.New(handler)
        logger     = rootLogger.With()
    )

    //
    // カスタムログレベルを利用する場合は slog.Log() メソッドで出力する
    //
    logger.Log(ctx, LevelTrace, "TRACE MESSAGE")
    logger.Log(ctx, LevelVerbose, "VERBOSE MESSAGE")
    logger.Log(ctx, slog.LevelDebug, "Debug MESSAGE")
    logger.Log(ctx, slog.LevelInfo, "INFO MESSAGE")
    logger.Log(ctx, LevelFatal, "FATAL MESSAGE")

    return nil
}

func replaceAttr(group []string, a slog.Attr) slog.Attr {
    switch a.Key {
    case slog.TimeKey:
        return slog.Attr{}
    case slog.LevelKey:
        a.Value = slog.StringValue(l2s(a.Value.Any().(slog.Level)))
    }

    return a
}

func l2s(l slog.Level) string {
    switch l {
    case LevelTrace:
        return "TRACE"
    case LevelVerbose:
        return "VERBOSE"
    case LevelFatal:
        return "FATAL"
    default:
        return l.String()
    }
}

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

Taskfile.yml

# https://taskfile.dev

version: '3'

tasks:
  default:
    cmds:
      - task: run
  run:
    cmds:
      - go run main.go

shell

$ task
task: [run] go run main.go
{"level":"VERBOSE","msg":"VERBOSE MESSAGE"}
{"level":"DEBUG","msg":"Debug MESSAGE"}
{"level":"INFO","msg":"INFO MESSAGE"}
{"level":"FATAL","msg":"FATAL MESSAGE"}

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

参考情報

Goのおすすめ書籍


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

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