Files
end 626e1bb5ac fix: lifecycle-safe logger teardown without global state
Store file handle per module, swap lifecycle logger before closing file,
return teardown errors, remove duplicate Module() func.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 18:01:08 -07:00

92 lines
2.0 KiB
Go

package applog
import (
"errors"
"fmt"
"log/slog"
"os"
"time"
"gitea.auvem.com/go-toolkit/app"
"github.com/lmittmann/tint"
slogmulti "github.com/samber/slog-multi"
)
type moduleState struct {
opts AppLogOpts
logfile *os.File
consoleHandler slog.Handler
}
func (s *moduleState) setup(m *app.Module) error {
if s.opts.FileOutput == "" && s.opts.ConsoleOutput == nil {
return fmt.Errorf("no logging output configured")
}
handlers := make([]slog.Handler, 0, 2)
if s.opts.FileOutput != "" {
f, err := os.OpenFile(s.opts.FileOutput, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return err
}
s.logfile = f
fileLevel := s.opts.FileLevel
if fileLevel == 0 && s.opts.ConsoleOutput == nil {
fileLevel = slog.LevelInfo
}
handlers = append(handlers, slog.NewJSONHandler(f, &slog.HandlerOptions{Level: fileLevel}))
}
if s.opts.ConsoleOutput != nil {
consoleLevel := s.opts.ConsoleLevel
if consoleLevel == 0 {
consoleLevel = slog.LevelInfo
}
s.consoleHandler = tint.NewHandler(s.opts.ConsoleOutput, &tint.Options{
Level: consoleLevel,
TimeFormat: time.Kitchen,
})
handlers = append(handlers, s.consoleHandler)
}
logger := slog.New(slogmulti.Fanout(handlers...))
m.Lifecycle().WithLogger(logger)
if s.opts.SetDefault {
slog.SetDefault(logger)
}
if !s.opts.DisableAnnouncement {
logger.Info("Logger initialized", "module", ModuleName)
}
return nil
}
func (s *moduleState) teardown(lifecycle *app.Lifecycle) error {
if lifecycle != nil {
switch {
case s.consoleHandler != nil:
lifecycle.WithLogger(slog.New(s.consoleHandler))
default:
lifecycle.WithLogger(slog.New(slog.DiscardHandler))
}
}
if s.logfile == nil {
return nil
}
var err error
if syncErr := s.logfile.Sync(); syncErr != nil {
err = fmt.Errorf("flush log file: %w", syncErr)
}
if closeErr := s.logfile.Close(); closeErr != nil {
err = errors.Join(err, fmt.Errorf("close log file: %w", closeErr))
}
s.logfile = nil
return err
}