関連記事
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送り込んだ際にローテートされてますね。
参考情報
個人的Goのおすすめ書籍
個人的に読んでとても勉強になった書籍さんたちです。
過去の記事については、以下のページからご参照下さい。
サンプルコードは、以下の場所で公開しています。






