いろいろ備忘録日記

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

Goメモ-657 (Go1.26でslogにMultiHandlerが追加)

関連記事

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

概要

以下、自分用のメモです。忘れないうちにメモメモ。。。

Go 1.26で slog.MultiHandlerが追加されていたのですね。

io.MultiWriterみたいな感じで、複数のハンドラを指定することが出来るようになっています。

これが追加されたので、ファイルに書きつつ、標準出力にもログ出力するみたいなロガーが作りやすくなりました。

以下、自分用の使い方メモ。

サンプル

ファイルと標準出力に出力するロガーをslog.MultiHandlerで構築して

SIGHUPを受けたらファイルをローテート、SIGINTを受けたら処理終了ってなるようにしています。

package main

import (
        "log/slog"
        "os"
        "os/signal"
        "path/filepath"
        "sync"
        "syscall"
        "time"

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

func rmTime(groups []string, attr slog.Attr) slog.Attr {
        if attr.Key == slog.TimeKey && len(groups) == 0 {
                return slog.Attr{}
        }
        return attr
}

func main() {
        //
        // Go 1.26 より slog に MultiHandler が追加される
        // slog.NewMultiHandler から生成できる
        //
        //      func NewMultiHandler(handlers ...Handler) *MultiHandler
        //

        //
        // 標準出力とファイルに出力するMultiHandlerを生成
        //
        var (
                dir, _  = os.Getwd()
                fpath   = filepath.Join(dir, "applog.log")
                file    = lumberjack.Logger{Filename: fpath, LocalTime: true}
                options = slog.HandlerOptions{ReplaceAttr: rmTime}

                stdoutHandler = slog.NewTextHandler(os.Stdout, &options)
                fileHandler   = slog.NewJSONHandler(&file, &options)
                handler       = slog.NewMultiHandler(stdoutHandler, fileHandler)
                logger        = slog.New(handler)
        )
        defer file.Close()

        //
        // SIGHUPでローテート, SIGINTで終了するように設定
        //
        var (
                sigCh  = make(chan os.Signal, 1)
                doneCh = make(chan bool)

                wg sync.WaitGroup

                toTime = func(t time.Time) string {
                        return t.Format("15:04:05.000")
                }
        )
        defer close(sigCh)

        signal.Notify(sigCh, syscall.SIGHUP, syscall.SIGINT)
        wg.Go(func() {
                logger.Info("SIGLOOP", slog.String("begin", toTime(time.Now())))

                for {
                        select {
                        case sig := <-sigCh:
                                switch sig {
                                case syscall.SIGHUP:
                                        logger.Warn("RECV SIGHUP", slog.String("action", "ROTATE"))
                                        file.Rotate()
                                case syscall.SIGINT:
                                        logger.Warn("RECV SIGINT", slog.String("action", "EXIT"))
                                        close(doneCh)
                                }
                        case <-doneCh:
                                logger.Info("SIGLOOP", slog.String("end", toTime(time.Now())))
                                return
                        }
                }
        })

        //
        // 1秒に一回ずつ、なんかログ出しておく
        //
        wg.Go(func() {
                var (
                        tickCh = time.Tick(1 * time.Second)
                )

                logger.Info("TICK", slog.String("begin", toTime(time.Now())))

                for {
                        select {
                        case t := <-tickCh:
                                logger.Info("TICK", slog.String("exec", toTime(t)))
                        case <-doneCh:
                                logger.Info("TICK", slog.String("end", toTime(time.Now())))
                                return
                        }
                }
        })

        wg.Wait()

        logger.Info("DONE")
}
# yaml-language-server: $schema=https://taskfile.dev/schema.json

version: '3'

tasks:
  default:
    cmds:
      - rm -f ./*.log
      - go1.26rc2 build -o app main.go
      - ./app &
      - sleep 3s
      - pkill -HUP app
      - sleep 3s
      - pkill -INT app
    silent: true

実行すると以下のようになります。

$ go1.26rc2 version
go version go1.26rc2 linux/amd64

$ task
 task
level=INFO msg=TICK begin=15:24:09.761
level=INFO msg=SIGLOOP begin=15:24:09.761
level=INFO msg=TICK exec=15:24:10.761
level=INFO msg=TICK exec=15:24:11.761
level=INFO msg=TICK exec=15:24:12.761
level=WARN msg="RECV SIGHUP" action=ROTATE
level=INFO msg=TICK exec=15:24:13.761
level=INFO msg=TICK exec=15:24:14.761
level=INFO msg=TICK exec=15:24:15.761
level=WARN msg="RECV SIGINT" action=EXIT
level=INFO msg=SIGLOOP end=15:24:15.784
level=INFO msg=TICK end=15:24:15.784
level=INFO msg=DONE

$ ls *log
applog-2026-01-28T15-24-12.772.log  applog.log

$ cat applog-2026-01-28T15-24-12.772.log
{"level":"INFO","msg":"TICK","begin":"15:24:09.761"}
{"level":"INFO","msg":"SIGLOOP","begin":"15:24:09.761"}
{"level":"INFO","msg":"TICK","exec":"15:24:10.761"}
{"level":"INFO","msg":"TICK","exec":"15:24:11.761"}
{"level":"INFO","msg":"TICK","exec":"15:24:12.761"}
{"level":"WARN","msg":"RECV SIGHUP","action":"ROTATE"}


$ cat applog.log
{"level":"INFO","msg":"TICK","exec":"15:24:13.761"}
{"level":"INFO","msg":"TICK","exec":"15:24:14.761"}
{"level":"INFO","msg":"TICK","exec":"15:24:15.761"}
{"level":"WARN","msg":"RECV SIGINT","action":"EXIT"}
{"level":"INFO","msg":"SIGLOOP","end":"15:24:15.784"}
{"level":"INFO","msg":"TICK","end":"15:24:15.784"}
{"level":"INFO","msg":"DONE"}

ちゃんとSIGHUP送り込んだ際にローテートされてますね。

参考情報

devlights.hatenablog.com

個人的Goのおすすめ書籍

個人的に読んでとても勉強になった書籍さんたちです。


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

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