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 }