Write all logging (INFO, WARN, ERROR) to stderr - hugo - [fork] hugo port for 9front
 (HTM) git clone git@git.drkhsh.at/hugo.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 9dfa1126177952433f8339a87fb566c2959fc3d6
 (DIR) parent ec1933f79d0da792d5c853cf7bf99fc9f0961162
 (HTM) Author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
       Date:   Fri, 13 Dec 2024 09:23:09 +0100
       
       Write all logging (INFO, WARN, ERROR) to stderr
       
       The old setup tried to log >= warning to stderr, the rest to stdout.
       
       However, that logic was flawed, so warnings ended up in stdout, which makes `hugo list all` etc. hard to reason about from scripts.
       
       This commit fixes this by making all logging (info, warn, error) log to stderr and let stdout be reserved for program output.
       
       Fixes #13074
       Diffstat:
         M commands/commandeer.go              |      21 ++++++++++++---------
         M commands/list.go                    |       2 +-
         M common/loggers/handlerterminal.go   |       4 ++--
         M common/loggers/logger.go            |      44 +++++++++++++++++--------------
         M common/loggers/logger_test.go       |      16 ++++++++--------
         M deps/deps.go                        |       8 +++++---
         M hugolib/integrationtest_builder.go  |       6 +++---
         M hugolib/site.go                     |      11 +++++++----
         M modules/client.go                   |       4 ++--
         M testscripts/commands/deprecate.txt  |       6 +++---
         M testscripts/commands/hugo__configd… |       5 +++--
         M testscripts/commands/hugo__path-wa… |       2 +-
         M testscripts/commands/hugo_printpat… |       2 +-
         M testscripts/commands/hugo_printunu… |       2 +-
         A testscripts/commands/warnf_stderr.… |      13 +++++++++++++
       
       15 files changed, 86 insertions(+), 60 deletions(-)
       ---
 (DIR) diff --git a/commands/commandeer.go b/commands/commandeer.go
       @@ -103,7 +103,8 @@ type configKey struct {
        type rootCommand struct {
                Printf  func(format string, v ...interface{})
                Println func(a ...interface{})
       -        Out     io.Writer
       +        StdOut  io.Writer
       +        StdErr  io.Writer
        
                logger loggers.Logger
        
       @@ -356,7 +357,7 @@ func (r *rootCommand) getOrCreateHugo(cfg config.Provider, ignoreModuleDoesNotEx
        }
        
        func (r *rootCommand) newDepsConfig(conf *commonConfig) deps.DepsCfg {
       -        return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
       +        return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, StdOut: r.logger.StdOut(), StdErr: r.logger.StdErr(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
        }
        
        func (r *rootCommand) Name() string {
       @@ -421,21 +422,23 @@ func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args 
        }
        
        func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
       -        r.Out = os.Stdout
       +        r.StdOut = os.Stdout
       +        r.StdErr = os.Stderr
                if r.quiet {
       -                r.Out = io.Discard
       +                r.StdOut = io.Discard
       +                r.StdErr = io.Discard
                }
                // Used by mkcert (server).
       -        log.SetOutput(r.Out)
       +        log.SetOutput(r.StdOut)
        
                r.Printf = func(format string, v ...interface{}) {
                        if !r.quiet {
       -                        fmt.Fprintf(r.Out, format, v...)
       +                        fmt.Fprintf(r.StdOut, format, v...)
                        }
                }
                r.Println = func(a ...interface{}) {
                        if !r.quiet {
       -                        fmt.Fprintln(r.Out, a...)
       +                        fmt.Fprintln(r.StdOut, a...)
                        }
                }
                _, running := runner.Command.(*serverCommand)
       @@ -485,8 +488,8 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
                optsLogger := loggers.Options{
                        DistinctLevel: logg.LevelWarn,
                        Level:         level,
       -                Stdout:        r.Out,
       -                Stderr:        r.Out,
       +                StdOut:        r.StdOut,
       +                StdErr:        r.StdErr,
                        StoreErrors:   running,
                }
        
 (DIR) diff --git a/commands/list.go b/commands/list.go
       @@ -57,7 +57,7 @@ func newListCommand() *listCommand {
                                return err
                        }
        
       -                writer := csv.NewWriter(r.Out)
       +                writer := csv.NewWriter(r.StdOut)
                        defer writer.Flush()
        
                        writer.Write([]string{
 (DIR) diff --git a/common/loggers/handlerterminal.go b/common/loggers/handlerterminal.go
       @@ -40,8 +40,8 @@ func newNoAnsiEscapeHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, 
        
        type noAnsiEscapeHandler struct {
                mu            sync.Mutex
       -        outWriter     io.Writer // Defaults to os.Stdout.
       -        errWriter     io.Writer // Defaults to os.Stderr.
       +        outWriter     io.Writer
       +        errWriter     io.Writer
                predicate     func(*logg.Entry) bool
                noLevelPrefix bool
        }
 (DIR) diff --git a/common/loggers/logger.go b/common/loggers/logger.go
       @@ -38,8 +38,8 @@ var (
        // Options defines options for the logger.
        type Options struct {
                Level              logg.Level
       -        Stdout             io.Writer
       -        Stderr             io.Writer
       +        StdOut             io.Writer
       +        StdErr             io.Writer
                DistinctLevel      logg.Level
                StoreErrors        bool
                HandlerPost        func(e *logg.Entry) error
       @@ -48,21 +48,22 @@ type Options struct {
        
        // New creates a new logger with the given options.
        func New(opts Options) Logger {
       -        if opts.Stdout == nil {
       -                opts.Stdout = os.Stdout
       +        if opts.StdOut == nil {
       +                opts.StdOut = os.Stdout
                }
       -        if opts.Stderr == nil {
       -                opts.Stderr = os.Stdout
       +        if opts.StdErr == nil {
       +                opts.StdErr = os.Stderr
                }
       +
                if opts.Level == 0 {
                        opts.Level = logg.LevelWarn
                }
        
                var logHandler logg.Handler
       -        if terminal.PrintANSIColors(os.Stdout) {
       -                logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
       +        if terminal.PrintANSIColors(os.Stderr) {
       +                logHandler = newDefaultHandler(opts.StdErr, opts.StdErr)
                } else {
       -                logHandler = newNoAnsiEscapeHandler(opts.Stdout, opts.Stderr, false, nil)
       +                logHandler = newNoAnsiEscapeHandler(opts.StdErr, opts.StdErr, false, nil)
                }
        
                errorsw := &strings.Builder{}
       @@ -137,7 +138,8 @@ func New(opts Options) Logger {
                        logCounters: logCounters,
                        errors:      errorsw,
                        reset:       reset,
       -                out:         opts.Stdout,
       +                stdOut:      opts.StdOut,
       +                stdErr:      opts.StdErr,
                        level:       opts.Level,
                        logger:      logger,
                        tracel:      l.WithLevel(logg.LevelTrace),
       @@ -153,8 +155,6 @@ func NewDefault() Logger {
                opts := Options{
                        DistinctLevel: logg.LevelWarn,
                        Level:         logg.LevelWarn,
       -                Stdout:        os.Stdout,
       -                Stderr:        os.Stdout,
                }
                return New(opts)
        }
       @@ -163,8 +163,6 @@ func NewTrace() Logger {
                opts := Options{
                        DistinctLevel: logg.LevelWarn,
                        Level:         logg.LevelTrace,
       -                Stdout:        os.Stdout,
       -                Stderr:        os.Stdout,
                }
                return New(opts)
        }
       @@ -189,7 +187,8 @@ type Logger interface {
                Level() logg.Level
                LoggCount(logg.Level) int
                Logger() logg.Logger
       -        Out() io.Writer
       +        StdOut() io.Writer
       +        StdErr() io.Writer
                Printf(format string, v ...any)
                Println(v ...any)
                PrintTimerIfDelayed(start time.Time, name string)
       @@ -207,7 +206,8 @@ type logAdapter struct {
                logCounters *logLevelCounter
                errors      *strings.Builder
                reset       func()
       -        out         io.Writer
       +        stdOut      io.Writer
       +        stdErr      io.Writer
                level       logg.Level
                logger      logg.Logger
                tracel      logg.LevelLogger
       @@ -259,8 +259,12 @@ func (l *logAdapter) Logger() logg.Logger {
                return l.logger
        }
        
       -func (l *logAdapter) Out() io.Writer {
       -        return l.out
       +func (l *logAdapter) StdOut() io.Writer {
       +        return l.stdOut
       +}
       +
       +func (l *logAdapter) StdErr() io.Writer {
       +        return l.stdErr
        }
        
        // PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
       @@ -279,11 +283,11 @@ func (l *logAdapter) Printf(format string, v ...any) {
                if !strings.HasSuffix(format, "\n") {
                        format += "\n"
                }
       -        fmt.Fprintf(l.out, format, v...)
       +        fmt.Fprintf(l.stdOut, format, v...)
        }
        
        func (l *logAdapter) Println(v ...any) {
       -        fmt.Fprintln(l.out, v...)
       +        fmt.Fprintln(l.stdOut, v...)
        }
        
        func (l *logAdapter) Reset() {
 (DIR) diff --git a/common/loggers/logger_test.go b/common/loggers/logger_test.go
       @@ -31,8 +31,8 @@ func TestLogDistinct(t *testing.T) {
                opts := loggers.Options{
                        DistinctLevel: logg.LevelWarn,
                        StoreErrors:   true,
       -                Stdout:        io.Discard,
       -                Stderr:        io.Discard,
       +                StdOut:        io.Discard,
       +                StdErr:        io.Discard,
                }
        
                l := loggers.New(opts)
       @@ -54,8 +54,8 @@ func TestHookLast(t *testing.T) {
                        HandlerPost: func(e *logg.Entry) error {
                                panic(e.Message)
                        },
       -                Stdout: io.Discard,
       -                Stderr: io.Discard,
       +                StdOut: io.Discard,
       +                StdErr: io.Discard,
                }
        
                l := loggers.New(opts)
       @@ -70,8 +70,8 @@ func TestOptionStoreErrors(t *testing.T) {
        
                opts := loggers.Options{
                        StoreErrors: true,
       -                Stderr:      &sb,
       -                Stdout:      &sb,
       +                StdErr:      &sb,
       +                StdOut:      &sb,
                }
        
                l := loggers.New(opts)
       @@ -131,8 +131,8 @@ func TestReset(t *testing.T) {
                opts := loggers.Options{
                        StoreErrors:   true,
                        DistinctLevel: logg.LevelWarn,
       -                Stdout:        io.Discard,
       -                Stderr:        io.Discard,
       +                StdOut:        io.Discard,
       +                StdErr:        io.Discard,
                }
        
                l := loggers.New(opts)
 (DIR) diff --git a/deps/deps.go b/deps/deps.go
       @@ -405,9 +405,11 @@ type DepsCfg struct {
                // The logging level to use.
                LogLevel logg.Level
        
       -        // Where to write the logs.
       -        // Currently we typically write everything to stdout.
       -        LogOut io.Writer
       +        // Logging output.
       +        StdErr io.Writer
       +
       +        // The console output.
       +        StdOut io.Writer
        
                // The file systems to use
                Fs *hugofs.Fs
 (DIR) diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
       @@ -660,8 +660,8 @@ func (s *IntegrationTestBuilder) initBuilder() error {
        
                        logger := loggers.New(
                                loggers.Options{
       -                                Stdout:        w,
       -                                Stderr:        w,
       +                                StdOut:        w,
       +                                StdErr:        w,
                                        Level:         s.Cfg.LogLevel,
                                        DistinctLevel: logg.LevelWarn,
                                },
       @@ -685,7 +685,7 @@ func (s *IntegrationTestBuilder) initBuilder() error {
        
                        s.Assert(err, qt.IsNil)
        
       -                depsCfg := deps.DepsCfg{Configs: res, Fs: fs, LogLevel: logger.Level(), LogOut: logger.Out()}
       +                depsCfg := deps.DepsCfg{Configs: res, Fs: fs, LogLevel: logger.Level(), StdErr: logger.StdErr()}
                        sites, err := NewHugoSites(depsCfg)
                        if err != nil {
                                initErr = err
 (DIR) diff --git a/hugolib/site.go b/hugolib/site.go
       @@ -145,8 +145,11 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
                        if cfg.Configs.Base.PanicOnWarning {
                                logHookLast = loggers.PanicOnWarningHook
                        }
       -                if cfg.LogOut == nil {
       -                        cfg.LogOut = os.Stdout
       +                if cfg.StdOut == nil {
       +                        cfg.StdOut = os.Stdout
       +                }
       +                if cfg.StdErr == nil {
       +                        cfg.StdErr = os.Stderr
                        }
                        if cfg.LogLevel == 0 {
                                cfg.LogLevel = logg.LevelWarn
       @@ -156,8 +159,8 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
                                Level:              cfg.LogLevel,
                                DistinctLevel:      logg.LevelWarn, // This will drop duplicate log warning and errors.
                                HandlerPost:        logHookLast,
       -                        Stdout:             cfg.LogOut,
       -                        Stderr:             cfg.LogOut,
       +                        StdOut:             cfg.StdOut,
       +                        StdErr:             cfg.StdErr,
                                StoreErrors:        conf.Watching(),
                                SuppressStatements: conf.IgnoredLogs(),
                        }
 (DIR) diff --git a/modules/client.go b/modules/client.go
       @@ -365,7 +365,7 @@ func (c *Client) Get(args ...string) error {
        }
        
        func (c *Client) get(args ...string) error {
       -        if err := c.runGo(context.Background(), c.logger.Out(), append([]string{"get"}, args...)...); err != nil {
       +        if err := c.runGo(context.Background(), c.logger.StdOut(), append([]string{"get"}, args...)...); err != nil {
                        return fmt.Errorf("failed to get %q: %w", args, err)
                }
                return nil
       @@ -375,7 +375,7 @@ func (c *Client) get(args ...string) error {
        // If path is empty, Go will try to guess.
        // If this succeeds, this project will be marked as Go Module.
        func (c *Client) Init(path string) error {
       -        err := c.runGo(context.Background(), c.logger.Out(), "mod", "init", path)
       +        err := c.runGo(context.Background(), c.logger.StdOut(), "mod", "init", path)
                if err != nil {
                        return fmt.Errorf("failed to init modules: %w", err)
                }
 (DIR) diff --git a/testscripts/commands/deprecate.txt b/testscripts/commands/deprecate.txt
       @@ -1,13 +1,13 @@
        
        # Test deprecation logging.
        hugo -e info --logLevel info
       -stdout 'INFO  deprecated: item was deprecated in Hugo'
       +stderr 'INFO  deprecated: item was deprecated in Hugo'
        
        hugo -e warn --logLevel warn
       -stdout 'WARN  deprecated: item was deprecated in Hugo'
       +stderr 'WARN  deprecated: item was deprecated in Hugo'
        
        ! hugo -e error --logLevel warn
       -stdout 'ERROR deprecated: item was deprecated in Hugo'
       +stderr 'ERROR deprecated: item was deprecated in Hugo'
        
        -- hugo.toml --
        baseURL = "https://example.com/"
 (DIR) diff --git a/testscripts/commands/hugo__configdir.txt b/testscripts/commands/hugo__configdir.txt
       @@ -3,4 +3,5 @@ hugo
        ! stderr .
        
        -- config/_default/hugo.toml --
       -baseURL = "https://example.com/"
       -\ No newline at end of file
       +baseURL = "https://example.com/"
       +disableKinds = ["RSS", "page", "sitemap", "robotsTXT", "404", "taxonomy", "term", "home"]
       +\ No newline at end of file
 (DIR) diff --git a/testscripts/commands/hugo__path-warnings.txt b/testscripts/commands/hugo__path-warnings.txt
       @@ -1,6 +1,6 @@
        hugo --printPathWarnings
        
       -stdout 'Duplicate'
       +stderr 'Duplicate'
        
        -- hugo.toml --
        -- assets/css/styles.css --
 (DIR) diff --git a/testscripts/commands/hugo_printpathwarnings.txt b/testscripts/commands/hugo_printpathwarnings.txt
       @@ -1,6 +1,6 @@
        hugo  --printPathWarnings
        
       -stdout 'Duplicate target paths: .index.html \(2\)'
       +stderr 'Duplicate target paths: .index.html \(2\)'
        
        -- hugo.toml --
        disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section"]
 (DIR) diff --git a/testscripts/commands/hugo_printunusedtemplates.txt b/testscripts/commands/hugo_printunusedtemplates.txt
       @@ -1,6 +1,6 @@
        hugo  --printUnusedTemplates
        
       -stdout 'Template _default/list.html is unused'
       +stderr 'Template _default/list.html is unused'
        
        -- hugo.toml --
        disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section", "page"]
 (DIR) diff --git a/testscripts/commands/warnf_stderr.txt b/testscripts/commands/warnf_stderr.txt
       @@ -0,0 +1,13 @@
       +# Issue #13074
       +
       +hugo
       +stderr 'warning'
       +! stdout 'warning'
       +
       +-- hugo.toml --
       +baseURL = "http://example.org/"
       +disableKinds = ["RSS", "page", "sitemap", "robotsTXT", "404", "taxonomy", "term"]
       +-- layouts/index.html --
       +Home
       +{{ warnf "This is a warning" }}
       +