Skip to content

Commit 9dfa112

Browse files
authoredDec 13, 2024··
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
1 parent ec1933f commit 9dfa112

15 files changed

+85
-59
lines changed
 

‎commands/commandeer.go

+12-9
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ type configKey struct {
103103
type rootCommand struct {
104104
Printf func(format string, v ...interface{})
105105
Println func(a ...interface{})
106-
Out io.Writer
106+
StdOut io.Writer
107+
StdErr io.Writer
107108

108109
logger loggers.Logger
109110

@@ -356,7 +357,7 @@ func (r *rootCommand) getOrCreateHugo(cfg config.Provider, ignoreModuleDoesNotEx
356357
}
357358

358359
func (r *rootCommand) newDepsConfig(conf *commonConfig) deps.DepsCfg {
359-
return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
360+
return deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, StdOut: r.logger.StdOut(), StdErr: r.logger.StdErr(), LogLevel: r.logger.Level(), ChangesFromBuild: r.changesFromBuild}
360361
}
361362

362363
func (r *rootCommand) Name() string {
@@ -421,21 +422,23 @@ func (r *rootCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args
421422
}
422423

423424
func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
424-
r.Out = os.Stdout
425+
r.StdOut = os.Stdout
426+
r.StdErr = os.Stderr
425427
if r.quiet {
426-
r.Out = io.Discard
428+
r.StdOut = io.Discard
429+
r.StdErr = io.Discard
427430
}
428431
// Used by mkcert (server).
429-
log.SetOutput(r.Out)
432+
log.SetOutput(r.StdOut)
430433

431434
r.Printf = func(format string, v ...interface{}) {
432435
if !r.quiet {
433-
fmt.Fprintf(r.Out, format, v...)
436+
fmt.Fprintf(r.StdOut, format, v...)
434437
}
435438
}
436439
r.Println = func(a ...interface{}) {
437440
if !r.quiet {
438-
fmt.Fprintln(r.Out, a...)
441+
fmt.Fprintln(r.StdOut, a...)
439442
}
440443
}
441444
_, running := runner.Command.(*serverCommand)
@@ -485,8 +488,8 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
485488
optsLogger := loggers.Options{
486489
DistinctLevel: logg.LevelWarn,
487490
Level: level,
488-
Stdout: r.Out,
489-
Stderr: r.Out,
491+
StdOut: r.StdOut,
492+
StdErr: r.StdErr,
490493
StoreErrors: running,
491494
}
492495

‎commands/list.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func newListCommand() *listCommand {
5757
return err
5858
}
5959

60-
writer := csv.NewWriter(r.Out)
60+
writer := csv.NewWriter(r.StdOut)
6161
defer writer.Flush()
6262

6363
writer.Write([]string{

‎common/loggers/handlerterminal.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ func newNoAnsiEscapeHandler(outWriter, errWriter io.Writer, noLevelPrefix bool,
4040

4141
type noAnsiEscapeHandler struct {
4242
mu sync.Mutex
43-
outWriter io.Writer // Defaults to os.Stdout.
44-
errWriter io.Writer // Defaults to os.Stderr.
43+
outWriter io.Writer
44+
errWriter io.Writer
4545
predicate func(*logg.Entry) bool
4646
noLevelPrefix bool
4747
}

‎common/loggers/logger.go

+24-20
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ var (
3838
// Options defines options for the logger.
3939
type Options struct {
4040
Level logg.Level
41-
Stdout io.Writer
42-
Stderr io.Writer
41+
StdOut io.Writer
42+
StdErr io.Writer
4343
DistinctLevel logg.Level
4444
StoreErrors bool
4545
HandlerPost func(e *logg.Entry) error
@@ -48,21 +48,22 @@ type Options struct {
4848

4949
// New creates a new logger with the given options.
5050
func New(opts Options) Logger {
51-
if opts.Stdout == nil {
52-
opts.Stdout = os.Stdout
51+
if opts.StdOut == nil {
52+
opts.StdOut = os.Stdout
5353
}
54-
if opts.Stderr == nil {
55-
opts.Stderr = os.Stdout
54+
if opts.StdErr == nil {
55+
opts.StdErr = os.Stderr
5656
}
57+
5758
if opts.Level == 0 {
5859
opts.Level = logg.LevelWarn
5960
}
6061

6162
var logHandler logg.Handler
62-
if terminal.PrintANSIColors(os.Stdout) {
63-
logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
63+
if terminal.PrintANSIColors(os.Stderr) {
64+
logHandler = newDefaultHandler(opts.StdErr, opts.StdErr)
6465
} else {
65-
logHandler = newNoAnsiEscapeHandler(opts.Stdout, opts.Stderr, false, nil)
66+
logHandler = newNoAnsiEscapeHandler(opts.StdErr, opts.StdErr, false, nil)
6667
}
6768

6869
errorsw := &strings.Builder{}
@@ -137,7 +138,8 @@ func New(opts Options) Logger {
137138
logCounters: logCounters,
138139
errors: errorsw,
139140
reset: reset,
140-
out: opts.Stdout,
141+
stdOut: opts.StdOut,
142+
stdErr: opts.StdErr,
141143
level: opts.Level,
142144
logger: logger,
143145
tracel: l.WithLevel(logg.LevelTrace),
@@ -153,8 +155,6 @@ func NewDefault() Logger {
153155
opts := Options{
154156
DistinctLevel: logg.LevelWarn,
155157
Level: logg.LevelWarn,
156-
Stdout: os.Stdout,
157-
Stderr: os.Stdout,
158158
}
159159
return New(opts)
160160
}
@@ -163,8 +163,6 @@ func NewTrace() Logger {
163163
opts := Options{
164164
DistinctLevel: logg.LevelWarn,
165165
Level: logg.LevelTrace,
166-
Stdout: os.Stdout,
167-
Stderr: os.Stdout,
168166
}
169167
return New(opts)
170168
}
@@ -189,7 +187,8 @@ type Logger interface {
189187
Level() logg.Level
190188
LoggCount(logg.Level) int
191189
Logger() logg.Logger
192-
Out() io.Writer
190+
StdOut() io.Writer
191+
StdErr() io.Writer
193192
Printf(format string, v ...any)
194193
Println(v ...any)
195194
PrintTimerIfDelayed(start time.Time, name string)
@@ -207,7 +206,8 @@ type logAdapter struct {
207206
logCounters *logLevelCounter
208207
errors *strings.Builder
209208
reset func()
210-
out io.Writer
209+
stdOut io.Writer
210+
stdErr io.Writer
211211
level logg.Level
212212
logger logg.Logger
213213
tracel logg.LevelLogger
@@ -259,8 +259,12 @@ func (l *logAdapter) Logger() logg.Logger {
259259
return l.logger
260260
}
261261

262-
func (l *logAdapter) Out() io.Writer {
263-
return l.out
262+
func (l *logAdapter) StdOut() io.Writer {
263+
return l.stdOut
264+
}
265+
266+
func (l *logAdapter) StdErr() io.Writer {
267+
return l.stdErr
264268
}
265269

266270
// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
@@ -279,11 +283,11 @@ func (l *logAdapter) Printf(format string, v ...any) {
279283
if !strings.HasSuffix(format, "\n") {
280284
format += "\n"
281285
}
282-
fmt.Fprintf(l.out, format, v...)
286+
fmt.Fprintf(l.stdOut, format, v...)
283287
}
284288

285289
func (l *logAdapter) Println(v ...any) {
286-
fmt.Fprintln(l.out, v...)
290+
fmt.Fprintln(l.stdOut, v...)
287291
}
288292

289293
func (l *logAdapter) Reset() {

‎common/loggers/logger_test.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ func TestLogDistinct(t *testing.T) {
3131
opts := loggers.Options{
3232
DistinctLevel: logg.LevelWarn,
3333
StoreErrors: true,
34-
Stdout: io.Discard,
35-
Stderr: io.Discard,
34+
StdOut: io.Discard,
35+
StdErr: io.Discard,
3636
}
3737

3838
l := loggers.New(opts)
@@ -54,8 +54,8 @@ func TestHookLast(t *testing.T) {
5454
HandlerPost: func(e *logg.Entry) error {
5555
panic(e.Message)
5656
},
57-
Stdout: io.Discard,
58-
Stderr: io.Discard,
57+
StdOut: io.Discard,
58+
StdErr: io.Discard,
5959
}
6060

6161
l := loggers.New(opts)
@@ -70,8 +70,8 @@ func TestOptionStoreErrors(t *testing.T) {
7070

7171
opts := loggers.Options{
7272
StoreErrors: true,
73-
Stderr: &sb,
74-
Stdout: &sb,
73+
StdErr: &sb,
74+
StdOut: &sb,
7575
}
7676

7777
l := loggers.New(opts)
@@ -131,8 +131,8 @@ func TestReset(t *testing.T) {
131131
opts := loggers.Options{
132132
StoreErrors: true,
133133
DistinctLevel: logg.LevelWarn,
134-
Stdout: io.Discard,
135-
Stderr: io.Discard,
134+
StdOut: io.Discard,
135+
StdErr: io.Discard,
136136
}
137137

138138
l := loggers.New(opts)

‎deps/deps.go

+5-3
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,11 @@ type DepsCfg struct {
405405
// The logging level to use.
406406
LogLevel logg.Level
407407

408-
// Where to write the logs.
409-
// Currently we typically write everything to stdout.
410-
LogOut io.Writer
408+
// Logging output.
409+
StdErr io.Writer
410+
411+
// The console output.
412+
StdOut io.Writer
411413

412414
// The file systems to use
413415
Fs *hugofs.Fs

‎hugolib/integrationtest_builder.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -660,8 +660,8 @@ func (s *IntegrationTestBuilder) initBuilder() error {
660660

661661
logger := loggers.New(
662662
loggers.Options{
663-
Stdout: w,
664-
Stderr: w,
663+
StdOut: w,
664+
StdErr: w,
665665
Level: s.Cfg.LogLevel,
666666
DistinctLevel: logg.LevelWarn,
667667
},
@@ -685,7 +685,7 @@ func (s *IntegrationTestBuilder) initBuilder() error {
685685

686686
s.Assert(err, qt.IsNil)
687687

688-
depsCfg := deps.DepsCfg{Configs: res, Fs: fs, LogLevel: logger.Level(), LogOut: logger.Out()}
688+
depsCfg := deps.DepsCfg{Configs: res, Fs: fs, LogLevel: logger.Level(), StdErr: logger.StdErr()}
689689
sites, err := NewHugoSites(depsCfg)
690690
if err != nil {
691691
initErr = err

‎hugolib/site.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,11 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
145145
if cfg.Configs.Base.PanicOnWarning {
146146
logHookLast = loggers.PanicOnWarningHook
147147
}
148-
if cfg.LogOut == nil {
149-
cfg.LogOut = os.Stdout
148+
if cfg.StdOut == nil {
149+
cfg.StdOut = os.Stdout
150+
}
151+
if cfg.StdErr == nil {
152+
cfg.StdErr = os.Stderr
150153
}
151154
if cfg.LogLevel == 0 {
152155
cfg.LogLevel = logg.LevelWarn
@@ -156,8 +159,8 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
156159
Level: cfg.LogLevel,
157160
DistinctLevel: logg.LevelWarn, // This will drop duplicate log warning and errors.
158161
HandlerPost: logHookLast,
159-
Stdout: cfg.LogOut,
160-
Stderr: cfg.LogOut,
162+
StdOut: cfg.StdOut,
163+
StdErr: cfg.StdErr,
161164
StoreErrors: conf.Watching(),
162165
SuppressStatements: conf.IgnoredLogs(),
163166
}

‎modules/client.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ func (c *Client) Get(args ...string) error {
365365
}
366366

367367
func (c *Client) get(args ...string) error {
368-
if err := c.runGo(context.Background(), c.logger.Out(), append([]string{"get"}, args...)...); err != nil {
368+
if err := c.runGo(context.Background(), c.logger.StdOut(), append([]string{"get"}, args...)...); err != nil {
369369
return fmt.Errorf("failed to get %q: %w", args, err)
370370
}
371371
return nil
@@ -375,7 +375,7 @@ func (c *Client) get(args ...string) error {
375375
// If path is empty, Go will try to guess.
376376
// If this succeeds, this project will be marked as Go Module.
377377
func (c *Client) Init(path string) error {
378-
err := c.runGo(context.Background(), c.logger.Out(), "mod", "init", path)
378+
err := c.runGo(context.Background(), c.logger.StdOut(), "mod", "init", path)
379379
if err != nil {
380380
return fmt.Errorf("failed to init modules: %w", err)
381381
}

‎testscripts/commands/deprecate.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11

22
# Test deprecation logging.
33
hugo -e info --logLevel info
4-
stdout 'INFO deprecated: item was deprecated in Hugo'
4+
stderr 'INFO deprecated: item was deprecated in Hugo'
55

66
hugo -e warn --logLevel warn
7-
stdout 'WARN deprecated: item was deprecated in Hugo'
7+
stderr 'WARN deprecated: item was deprecated in Hugo'
88

99
! hugo -e error --logLevel warn
10-
stdout 'ERROR deprecated: item was deprecated in Hugo'
10+
stderr 'ERROR deprecated: item was deprecated in Hugo'
1111

1212
-- hugo.toml --
1313
baseURL = "https://example.com/"

‎testscripts/commands/hugo__configdir.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ hugo
33
! stderr .
44

55
-- config/_default/hugo.toml --
6-
baseURL = "https://example.com/"
6+
baseURL = "https://example.com/"
7+
disableKinds = ["RSS", "page", "sitemap", "robotsTXT", "404", "taxonomy", "term", "home"]

‎testscripts/commands/hugo__path-warnings.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
hugo --printPathWarnings
22

3-
stdout 'Duplicate'
3+
stderr 'Duplicate'
44

55
-- hugo.toml --
66
-- assets/css/styles.css --

‎testscripts/commands/hugo_printpathwarnings.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
hugo --printPathWarnings
22

3-
stdout 'Duplicate target paths: .index.html \(2\)'
3+
stderr 'Duplicate target paths: .index.html \(2\)'
44

55
-- hugo.toml --
66
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section"]

‎testscripts/commands/hugo_printunusedtemplates.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
hugo --printUnusedTemplates
22

3-
stdout 'Template _default/list.html is unused'
3+
stderr 'Template _default/list.html is unused'
44

55
-- hugo.toml --
66
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section", "page"]

‎testscripts/commands/warnf_stderr.txt

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Issue #13074
2+
3+
hugo
4+
stderr 'warning'
5+
! stdout 'warning'
6+
7+
-- hugo.toml --
8+
baseURL = "http://example.org/"
9+
disableKinds = ["RSS", "page", "sitemap", "robotsTXT", "404", "taxonomy", "term"]
10+
-- layouts/index.html --
11+
Home
12+
{{ warnf "This is a warning" }}
13+

0 commit comments

Comments
 (0)
Please sign in to comment.