mirror of
https://github.com/gohugoio/hugo.git
synced 2025-04-27 14:10:31 +03:00
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
This commit is contained in:
parent
ec1933f79d
commit
9dfa112617
15 changed files with 85 additions and 59 deletions
|
@ -103,7 +103,8 @@ type configKey struct {
|
||||||
type rootCommand struct {
|
type rootCommand struct {
|
||||||
Printf func(format string, v ...interface{})
|
Printf func(format string, v ...interface{})
|
||||||
Println func(a ...interface{})
|
Println func(a ...interface{})
|
||||||
Out io.Writer
|
StdOut io.Writer
|
||||||
|
StdErr io.Writer
|
||||||
|
|
||||||
logger loggers.Logger
|
logger loggers.Logger
|
||||||
|
|
||||||
|
@ -356,7 +357,7 @@ func (r *rootCommand) getOrCreateHugo(cfg config.Provider, ignoreModuleDoesNotEx
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rootCommand) newDepsConfig(conf *commonConfig) deps.DepsCfg {
|
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 {
|
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 {
|
func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
|
||||||
r.Out = os.Stdout
|
r.StdOut = os.Stdout
|
||||||
|
r.StdErr = os.Stderr
|
||||||
if r.quiet {
|
if r.quiet {
|
||||||
r.Out = io.Discard
|
r.StdOut = io.Discard
|
||||||
|
r.StdErr = io.Discard
|
||||||
}
|
}
|
||||||
// Used by mkcert (server).
|
// Used by mkcert (server).
|
||||||
log.SetOutput(r.Out)
|
log.SetOutput(r.StdOut)
|
||||||
|
|
||||||
r.Printf = func(format string, v ...interface{}) {
|
r.Printf = func(format string, v ...interface{}) {
|
||||||
if !r.quiet {
|
if !r.quiet {
|
||||||
fmt.Fprintf(r.Out, format, v...)
|
fmt.Fprintf(r.StdOut, format, v...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r.Println = func(a ...interface{}) {
|
r.Println = func(a ...interface{}) {
|
||||||
if !r.quiet {
|
if !r.quiet {
|
||||||
fmt.Fprintln(r.Out, a...)
|
fmt.Fprintln(r.StdOut, a...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, running := runner.Command.(*serverCommand)
|
_, running := runner.Command.(*serverCommand)
|
||||||
|
@ -485,8 +488,8 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
|
||||||
optsLogger := loggers.Options{
|
optsLogger := loggers.Options{
|
||||||
DistinctLevel: logg.LevelWarn,
|
DistinctLevel: logg.LevelWarn,
|
||||||
Level: level,
|
Level: level,
|
||||||
Stdout: r.Out,
|
StdOut: r.StdOut,
|
||||||
Stderr: r.Out,
|
StdErr: r.StdErr,
|
||||||
StoreErrors: running,
|
StoreErrors: running,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ func newListCommand() *listCommand {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
writer := csv.NewWriter(r.Out)
|
writer := csv.NewWriter(r.StdOut)
|
||||||
defer writer.Flush()
|
defer writer.Flush()
|
||||||
|
|
||||||
writer.Write([]string{
|
writer.Write([]string{
|
||||||
|
|
|
@ -40,8 +40,8 @@ func newNoAnsiEscapeHandler(outWriter, errWriter io.Writer, noLevelPrefix bool,
|
||||||
|
|
||||||
type noAnsiEscapeHandler struct {
|
type noAnsiEscapeHandler struct {
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
outWriter io.Writer // Defaults to os.Stdout.
|
outWriter io.Writer
|
||||||
errWriter io.Writer // Defaults to os.Stderr.
|
errWriter io.Writer
|
||||||
predicate func(*logg.Entry) bool
|
predicate func(*logg.Entry) bool
|
||||||
noLevelPrefix bool
|
noLevelPrefix bool
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,8 @@ var (
|
||||||
// Options defines options for the logger.
|
// Options defines options for the logger.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
Level logg.Level
|
Level logg.Level
|
||||||
Stdout io.Writer
|
StdOut io.Writer
|
||||||
Stderr io.Writer
|
StdErr io.Writer
|
||||||
DistinctLevel logg.Level
|
DistinctLevel logg.Level
|
||||||
StoreErrors bool
|
StoreErrors bool
|
||||||
HandlerPost func(e *logg.Entry) error
|
HandlerPost func(e *logg.Entry) error
|
||||||
|
@ -48,21 +48,22 @@ type Options struct {
|
||||||
|
|
||||||
// New creates a new logger with the given options.
|
// New creates a new logger with the given options.
|
||||||
func New(opts Options) Logger {
|
func New(opts Options) Logger {
|
||||||
if opts.Stdout == nil {
|
if opts.StdOut == nil {
|
||||||
opts.Stdout = os.Stdout
|
opts.StdOut = os.Stdout
|
||||||
}
|
}
|
||||||
if opts.Stderr == nil {
|
if opts.StdErr == nil {
|
||||||
opts.Stderr = os.Stdout
|
opts.StdErr = os.Stderr
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Level == 0 {
|
if opts.Level == 0 {
|
||||||
opts.Level = logg.LevelWarn
|
opts.Level = logg.LevelWarn
|
||||||
}
|
}
|
||||||
|
|
||||||
var logHandler logg.Handler
|
var logHandler logg.Handler
|
||||||
if terminal.PrintANSIColors(os.Stdout) {
|
if terminal.PrintANSIColors(os.Stderr) {
|
||||||
logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
|
logHandler = newDefaultHandler(opts.StdErr, opts.StdErr)
|
||||||
} else {
|
} else {
|
||||||
logHandler = newNoAnsiEscapeHandler(opts.Stdout, opts.Stderr, false, nil)
|
logHandler = newNoAnsiEscapeHandler(opts.StdErr, opts.StdErr, false, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
errorsw := &strings.Builder{}
|
errorsw := &strings.Builder{}
|
||||||
|
@ -137,7 +138,8 @@ func New(opts Options) Logger {
|
||||||
logCounters: logCounters,
|
logCounters: logCounters,
|
||||||
errors: errorsw,
|
errors: errorsw,
|
||||||
reset: reset,
|
reset: reset,
|
||||||
out: opts.Stdout,
|
stdOut: opts.StdOut,
|
||||||
|
stdErr: opts.StdErr,
|
||||||
level: opts.Level,
|
level: opts.Level,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
tracel: l.WithLevel(logg.LevelTrace),
|
tracel: l.WithLevel(logg.LevelTrace),
|
||||||
|
@ -153,8 +155,6 @@ func NewDefault() Logger {
|
||||||
opts := Options{
|
opts := Options{
|
||||||
DistinctLevel: logg.LevelWarn,
|
DistinctLevel: logg.LevelWarn,
|
||||||
Level: logg.LevelWarn,
|
Level: logg.LevelWarn,
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stderr: os.Stdout,
|
|
||||||
}
|
}
|
||||||
return New(opts)
|
return New(opts)
|
||||||
}
|
}
|
||||||
|
@ -163,8 +163,6 @@ func NewTrace() Logger {
|
||||||
opts := Options{
|
opts := Options{
|
||||||
DistinctLevel: logg.LevelWarn,
|
DistinctLevel: logg.LevelWarn,
|
||||||
Level: logg.LevelTrace,
|
Level: logg.LevelTrace,
|
||||||
Stdout: os.Stdout,
|
|
||||||
Stderr: os.Stdout,
|
|
||||||
}
|
}
|
||||||
return New(opts)
|
return New(opts)
|
||||||
}
|
}
|
||||||
|
@ -189,7 +187,8 @@ type Logger interface {
|
||||||
Level() logg.Level
|
Level() logg.Level
|
||||||
LoggCount(logg.Level) int
|
LoggCount(logg.Level) int
|
||||||
Logger() logg.Logger
|
Logger() logg.Logger
|
||||||
Out() io.Writer
|
StdOut() io.Writer
|
||||||
|
StdErr() io.Writer
|
||||||
Printf(format string, v ...any)
|
Printf(format string, v ...any)
|
||||||
Println(v ...any)
|
Println(v ...any)
|
||||||
PrintTimerIfDelayed(start time.Time, name string)
|
PrintTimerIfDelayed(start time.Time, name string)
|
||||||
|
@ -207,7 +206,8 @@ type logAdapter struct {
|
||||||
logCounters *logLevelCounter
|
logCounters *logLevelCounter
|
||||||
errors *strings.Builder
|
errors *strings.Builder
|
||||||
reset func()
|
reset func()
|
||||||
out io.Writer
|
stdOut io.Writer
|
||||||
|
stdErr io.Writer
|
||||||
level logg.Level
|
level logg.Level
|
||||||
logger logg.Logger
|
logger logg.Logger
|
||||||
tracel logg.LevelLogger
|
tracel logg.LevelLogger
|
||||||
|
@ -259,8 +259,12 @@ func (l *logAdapter) Logger() logg.Logger {
|
||||||
return l.logger
|
return l.logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logAdapter) Out() io.Writer {
|
func (l *logAdapter) StdOut() io.Writer {
|
||||||
return l.out
|
return l.stdOut
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *logAdapter) StdErr() io.Writer {
|
||||||
|
return l.stdErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
|
// 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") {
|
if !strings.HasSuffix(format, "\n") {
|
||||||
format += "\n"
|
format += "\n"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(l.out, format, v...)
|
fmt.Fprintf(l.stdOut, format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logAdapter) Println(v ...any) {
|
func (l *logAdapter) Println(v ...any) {
|
||||||
fmt.Fprintln(l.out, v...)
|
fmt.Fprintln(l.stdOut, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logAdapter) Reset() {
|
func (l *logAdapter) Reset() {
|
||||||
|
|
|
@ -31,8 +31,8 @@ func TestLogDistinct(t *testing.T) {
|
||||||
opts := loggers.Options{
|
opts := loggers.Options{
|
||||||
DistinctLevel: logg.LevelWarn,
|
DistinctLevel: logg.LevelWarn,
|
||||||
StoreErrors: true,
|
StoreErrors: true,
|
||||||
Stdout: io.Discard,
|
StdOut: io.Discard,
|
||||||
Stderr: io.Discard,
|
StdErr: io.Discard,
|
||||||
}
|
}
|
||||||
|
|
||||||
l := loggers.New(opts)
|
l := loggers.New(opts)
|
||||||
|
@ -54,8 +54,8 @@ func TestHookLast(t *testing.T) {
|
||||||
HandlerPost: func(e *logg.Entry) error {
|
HandlerPost: func(e *logg.Entry) error {
|
||||||
panic(e.Message)
|
panic(e.Message)
|
||||||
},
|
},
|
||||||
Stdout: io.Discard,
|
StdOut: io.Discard,
|
||||||
Stderr: io.Discard,
|
StdErr: io.Discard,
|
||||||
}
|
}
|
||||||
|
|
||||||
l := loggers.New(opts)
|
l := loggers.New(opts)
|
||||||
|
@ -70,8 +70,8 @@ func TestOptionStoreErrors(t *testing.T) {
|
||||||
|
|
||||||
opts := loggers.Options{
|
opts := loggers.Options{
|
||||||
StoreErrors: true,
|
StoreErrors: true,
|
||||||
Stderr: &sb,
|
StdErr: &sb,
|
||||||
Stdout: &sb,
|
StdOut: &sb,
|
||||||
}
|
}
|
||||||
|
|
||||||
l := loggers.New(opts)
|
l := loggers.New(opts)
|
||||||
|
@ -131,8 +131,8 @@ func TestReset(t *testing.T) {
|
||||||
opts := loggers.Options{
|
opts := loggers.Options{
|
||||||
StoreErrors: true,
|
StoreErrors: true,
|
||||||
DistinctLevel: logg.LevelWarn,
|
DistinctLevel: logg.LevelWarn,
|
||||||
Stdout: io.Discard,
|
StdOut: io.Discard,
|
||||||
Stderr: io.Discard,
|
StdErr: io.Discard,
|
||||||
}
|
}
|
||||||
|
|
||||||
l := loggers.New(opts)
|
l := loggers.New(opts)
|
||||||
|
|
8
deps/deps.go
vendored
8
deps/deps.go
vendored
|
@ -405,9 +405,11 @@ type DepsCfg struct {
|
||||||
// The logging level to use.
|
// The logging level to use.
|
||||||
LogLevel logg.Level
|
LogLevel logg.Level
|
||||||
|
|
||||||
// Where to write the logs.
|
// Logging output.
|
||||||
// Currently we typically write everything to stdout.
|
StdErr io.Writer
|
||||||
LogOut io.Writer
|
|
||||||
|
// The console output.
|
||||||
|
StdOut io.Writer
|
||||||
|
|
||||||
// The file systems to use
|
// The file systems to use
|
||||||
Fs *hugofs.Fs
|
Fs *hugofs.Fs
|
||||||
|
|
|
@ -660,8 +660,8 @@ func (s *IntegrationTestBuilder) initBuilder() error {
|
||||||
|
|
||||||
logger := loggers.New(
|
logger := loggers.New(
|
||||||
loggers.Options{
|
loggers.Options{
|
||||||
Stdout: w,
|
StdOut: w,
|
||||||
Stderr: w,
|
StdErr: w,
|
||||||
Level: s.Cfg.LogLevel,
|
Level: s.Cfg.LogLevel,
|
||||||
DistinctLevel: logg.LevelWarn,
|
DistinctLevel: logg.LevelWarn,
|
||||||
},
|
},
|
||||||
|
@ -685,7 +685,7 @@ func (s *IntegrationTestBuilder) initBuilder() error {
|
||||||
|
|
||||||
s.Assert(err, qt.IsNil)
|
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)
|
sites, err := NewHugoSites(depsCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
initErr = err
|
initErr = err
|
||||||
|
|
|
@ -145,8 +145,11 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
|
||||||
if cfg.Configs.Base.PanicOnWarning {
|
if cfg.Configs.Base.PanicOnWarning {
|
||||||
logHookLast = loggers.PanicOnWarningHook
|
logHookLast = loggers.PanicOnWarningHook
|
||||||
}
|
}
|
||||||
if cfg.LogOut == nil {
|
if cfg.StdOut == nil {
|
||||||
cfg.LogOut = os.Stdout
|
cfg.StdOut = os.Stdout
|
||||||
|
}
|
||||||
|
if cfg.StdErr == nil {
|
||||||
|
cfg.StdErr = os.Stderr
|
||||||
}
|
}
|
||||||
if cfg.LogLevel == 0 {
|
if cfg.LogLevel == 0 {
|
||||||
cfg.LogLevel = logg.LevelWarn
|
cfg.LogLevel = logg.LevelWarn
|
||||||
|
@ -156,8 +159,8 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
|
||||||
Level: cfg.LogLevel,
|
Level: cfg.LogLevel,
|
||||||
DistinctLevel: logg.LevelWarn, // This will drop duplicate log warning and errors.
|
DistinctLevel: logg.LevelWarn, // This will drop duplicate log warning and errors.
|
||||||
HandlerPost: logHookLast,
|
HandlerPost: logHookLast,
|
||||||
Stdout: cfg.LogOut,
|
StdOut: cfg.StdOut,
|
||||||
Stderr: cfg.LogOut,
|
StdErr: cfg.StdErr,
|
||||||
StoreErrors: conf.Watching(),
|
StoreErrors: conf.Watching(),
|
||||||
SuppressStatements: conf.IgnoredLogs(),
|
SuppressStatements: conf.IgnoredLogs(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -365,7 +365,7 @@ func (c *Client) Get(args ...string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 fmt.Errorf("failed to get %q: %w", args, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -375,7 +375,7 @@ func (c *Client) get(args ...string) error {
|
||||||
// If path is empty, Go will try to guess.
|
// If path is empty, Go will try to guess.
|
||||||
// If this succeeds, this project will be marked as Go Module.
|
// If this succeeds, this project will be marked as Go Module.
|
||||||
func (c *Client) Init(path string) error {
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to init modules: %w", err)
|
return fmt.Errorf("failed to init modules: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
|
|
||||||
# Test deprecation logging.
|
# Test deprecation logging.
|
||||||
hugo -e info --logLevel info
|
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
|
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
|
! hugo -e error --logLevel warn
|
||||||
stdout 'ERROR deprecated: item was deprecated in Hugo'
|
stderr 'ERROR deprecated: item was deprecated in Hugo'
|
||||||
|
|
||||||
-- hugo.toml --
|
-- hugo.toml --
|
||||||
baseURL = "https://example.com/"
|
baseURL = "https://example.com/"
|
||||||
|
|
|
@ -4,3 +4,4 @@ hugo
|
||||||
|
|
||||||
-- config/_default/hugo.toml --
|
-- config/_default/hugo.toml --
|
||||||
baseURL = "https://example.com/"
|
baseURL = "https://example.com/"
|
||||||
|
disableKinds = ["RSS", "page", "sitemap", "robotsTXT", "404", "taxonomy", "term", "home"]
|
|
@ -1,6 +1,6 @@
|
||||||
hugo --printPathWarnings
|
hugo --printPathWarnings
|
||||||
|
|
||||||
stdout 'Duplicate'
|
stderr 'Duplicate'
|
||||||
|
|
||||||
-- hugo.toml --
|
-- hugo.toml --
|
||||||
-- assets/css/styles.css --
|
-- assets/css/styles.css --
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
hugo --printPathWarnings
|
hugo --printPathWarnings
|
||||||
|
|
||||||
stdout 'Duplicate target paths: .index.html \(2\)'
|
stderr 'Duplicate target paths: .index.html \(2\)'
|
||||||
|
|
||||||
-- hugo.toml --
|
-- hugo.toml --
|
||||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section"]
|
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section"]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
hugo --printUnusedTemplates
|
hugo --printUnusedTemplates
|
||||||
|
|
||||||
stdout 'Template _default/list.html is unused'
|
stderr 'Template _default/list.html is unused'
|
||||||
|
|
||||||
-- hugo.toml --
|
-- hugo.toml --
|
||||||
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section", "page"]
|
disableKinds = ["taxonomy", "term", "RSS", "sitemap", "robotsTXT", "404", "section", "page"]
|
||||||
|
|
13
testscripts/commands/warnf_stderr.txt
Normal file
13
testscripts/commands/warnf_stderr.txt
Normal file
|
@ -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" }}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue