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>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user