diff --git a/internal/aghos/syslog.go b/internal/aghos/syslog.go index a67a5635..5e58bdcb 100644 --- a/internal/aghos/syslog.go +++ b/internal/aghos/syslog.go @@ -1,6 +1,8 @@ package aghos -// ConfigureSyslog reroutes standard logger output to syslog. -func ConfigureSyslog(serviceName string) error { +import "io" + +// ConfigureSyslog returns an output rerouted to syslog. +func ConfigureSyslog(serviceName string) (w io.Writer, err error) { return configureSyslog(serviceName) } diff --git a/internal/aghos/syslog_others.go b/internal/aghos/syslog_others.go index 1659ae49..4242dd84 100644 --- a/internal/aghos/syslog_others.go +++ b/internal/aghos/syslog_others.go @@ -3,16 +3,15 @@ package aghos import ( + "io" "log/syslog" - - "github.com/AdguardTeam/golibs/log" ) -func configureSyslog(serviceName string) error { - w, err := syslog.New(syslog.LOG_NOTICE|syslog.LOG_USER, serviceName) +func configureSyslog(serviceName string) (w io.Writer, err error) { + w, err = syslog.New(syslog.LOG_NOTICE|syslog.LOG_USER, serviceName) if err != nil { - return err + return nil, err } - log.SetOutput(w) - return nil + + return w, nil } diff --git a/internal/aghos/syslog_windows.go b/internal/aghos/syslog_windows.go index c8e86e78..21de19c1 100644 --- a/internal/aghos/syslog_windows.go +++ b/internal/aghos/syslog_windows.go @@ -3,9 +3,9 @@ package aghos import ( + "io" "strings" - "github.com/AdguardTeam/golibs/log" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc/eventlog" ) @@ -19,23 +19,26 @@ func (w *eventLogWriter) Write(b []byte) (int, error) { return len(b), w.el.Info(1, string(b)) } -func configureSyslog(serviceName string) error { - // Note that the eventlog src is the same as the service name - // Otherwise, we will get "the description for event id cannot be found" warning in every log record +func configureSyslog(serviceName string) (w io.Writer, err error) { + // Note that the eventlog src is the same as the service name. Otherwise, + // we will get "the description for event id cannot be found" warning in + // every log record. // Continue if we receive "registry key already exists" or if we get // ERROR_ACCESS_DENIED so that we can log without administrative permissions // for pre-existing eventlog sources. - if err := eventlog.InstallAsEventCreate(serviceName, eventlog.Info|eventlog.Warning|eventlog.Error); err != nil { - if !strings.Contains(err.Error(), "registry key already exists") && err != windows.ERROR_ACCESS_DENIED { - return err + err = eventlog.InstallAsEventCreate(serviceName, eventlog.Info|eventlog.Warning|eventlog.Error) + if err != nil { + if !strings.Contains(err.Error(), "registry key already exists") && + err != windows.ERROR_ACCESS_DENIED { + return nil, err } } + el, err := eventlog.Open(serviceName) if err != nil { - return err + return nil, err } - log.SetOutput(&eventLogWriter{el: el}) - return nil + return &eventLogWriter{el: el}, nil } diff --git a/internal/home/home.go b/internal/home/home.go index fc64f376..0897d474 100644 --- a/internal/home/home.go +++ b/internal/home/home.go @@ -38,6 +38,7 @@ import ( "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/hostsfile" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/osutil" ) @@ -547,15 +548,21 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) { // Configure config filename. initConfigFilename(opts) + ls := getLogSettings(opts) + // Configure log level and output. - err = configureLogger(opts) + err = configureLogger(ls) + fatalOnError(err) + + // Configure slog logger. + l, err := initLogger(ls) fatalOnError(err) // Print the first message after logger is configured. - log.Info(version.Full()) - log.Debug("current working directory is %s", Context.workDir) + l.Info(version.Full()) + l.Debug("current working directory is set", "work_dir", Context.workDir) if opts.runningAsService { - log.Info("AdGuard Home is running as a service") + l.Info("AdGuard Home is running as a service") } err = setupContext(opts) @@ -586,7 +593,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) { } confPath := configFilePath() - log.Debug("using config path %q for updater", confPath) + l.Debug("using config path for updater", "path", confPath) upd := updater.NewUpdater(&updater.Config{ Client: config.Filtering.HTTPClient, @@ -628,7 +635,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) { Context.tls, err = newTLSManager(config.TLS, config.DNS.ServePlainDNS) if err != nil { - log.Error("initializing tls: %s", err) + l.Error("initializing tls", slogutil.KeyError, err) onConfigModified() } @@ -652,7 +659,7 @@ func run(opts options, clientBuildFS fs.FS, done chan struct{}) { if Context.dhcpServer != nil { err = Context.dhcpServer.Start() if err != nil { - log.Error("starting dhcp server: %s", err) + l.Error("starting dhcp server", slogutil.KeyError, err) } } } diff --git a/internal/home/log.go b/internal/home/log.go index fd18d1ec..a83b9782 100644 --- a/internal/home/log.go +++ b/internal/home/log.go @@ -3,11 +3,14 @@ package home import ( "cmp" "fmt" + "io" + "log/slog" "path/filepath" "runtime" "github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/golibs/log" + "github.com/AdguardTeam/golibs/logutil/slogutil" "gopkg.in/natefinch/lumberjack.v2" "gopkg.in/yaml.v3" ) @@ -16,10 +19,58 @@ import ( // for logger output. const configSyslog = "syslog" -// configureLogger configures logger level and output. -func configureLogger(opts options) (err error) { - ls := getLogSettings(opts) +// initLogger returns new [*slog.Logger] configured with the given settings. +func initLogger(ls *logSettings) (l *slog.Logger, err error) { + if !ls.Enabled { + return slogutil.NewDiscardLogger(), nil + } + w, err := logOutput(ls) + if err != nil { + return nil, fmt.Errorf("cannot initialize log output: %w", err) + } + + return slogutil.New(&slogutil.Config{ + Output: w, + Format: slogutil.FormatDefault, + AddTimestamp: true, + Verbose: ls.Verbose, + }), nil +} + +// logOutput returns a log output [io.Writer] configured with the settings. +func logOutput(ls *logSettings) (w io.Writer, err error) { + if ls.File == "" { + return nil, nil + } + + if ls.File == configSyslog { + // Use syslog where it is possible and eventlog on Windows. + w, err = aghos.ConfigureSyslog(serviceName) + if err != nil { + return nil, fmt.Errorf("cannot initialize syslog: %w", err) + } + + return w, nil + } + + logFilePath := ls.File + if !filepath.IsAbs(logFilePath) { + logFilePath = filepath.Join(Context.workDir, logFilePath) + } + + return &lumberjack.Logger{ + Filename: logFilePath, + Compress: ls.Compress, + LocalTime: ls.LocalTime, + MaxBackups: ls.MaxBackups, + MaxSize: ls.MaxSize, + MaxAge: ls.MaxAge, + }, nil +} + +// configureLogger configures logger level and output. +func configureLogger(ls *logSettings) (err error) { // Configure logger level. if !ls.Enabled { log.SetLevel(log.OFF) @@ -33,16 +84,19 @@ func configureLogger(opts options) (err error) { // Write logs to stdout by default. if ls.File == "" { - return nil + return err } if ls.File == configSyslog { // Use syslog where it is possible and eventlog on Windows. - err = aghos.ConfigureSyslog(serviceName) + var w io.Writer + w, err = aghos.ConfigureSyslog(serviceName) if err != nil { return fmt.Errorf("cannot initialize syslog: %w", err) } + log.SetOutput(w) + return nil } @@ -60,7 +114,7 @@ func configureLogger(opts options) (err error) { MaxAge: ls.MaxAge, }) - return nil + return err } // getLogSettings returns a log settings object properly initialized from opts. diff --git a/internal/next/cmd/log.go b/internal/next/cmd/log.go index 3aa2a0e5..6c834082 100644 --- a/internal/next/cmd/log.go +++ b/internal/next/cmd/log.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "io" "os" "github.com/AdguardTeam/AdGuardHome/internal/aghos" @@ -22,10 +23,13 @@ func setLog(opts *options) (err error) { case "stderr": log.SetOutput(os.Stderr) case "syslog": - err = aghos.ConfigureSyslog(syslogServiceName) + var w io.Writer + w, err = aghos.ConfigureSyslog(syslogServiceName) if err != nil { return fmt.Errorf("initializing syslog: %w", err) } + + log.SetOutput(w) default: // TODO(a.garipov): Use the path. }