]> git.maquefel.me Git - brevno-suite/hugo/commitdiff
Replace the old log setup, with structured logging etc.
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Fri, 16 Jun 2023 06:17:42 +0000 (08:17 +0200)
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Sun, 18 Jun 2023 11:03:04 +0000 (13:03 +0200)
Fixes #11124

80 files changed:
cache/filecache/integration_test.go
commands/commandeer.go
commands/deploy.go
commands/hugobuilder.go
commands/import.go
commands/server.go
commands/xcommand_template.go [deleted file]
common/loggers/handlerdefault.go [new file with mode: 0644]
common/loggers/handlersmisc.go [new file with mode: 0644]
common/loggers/handlerterminal.go [new file with mode: 0644]
common/loggers/ignorableLogger.go [deleted file]
common/loggers/logger.go [new file with mode: 0644]
common/loggers/logger_test.go [new file with mode: 0644]
common/loggers/loggerglobal.go [new file with mode: 0644]
common/loggers/loggers.go [deleted file]
common/loggers/loggers_test.go [deleted file]
config/allconfig/allconfig.go
config/allconfig/load.go
config/commonConfig.go
config/commonConfig_test.go
deploy/deploy.go
deploy/deploy_test.go
deps/deps.go
go.mod
go.sum
helpers/general.go
helpers/general_test.go
helpers/testhelpers_test.go
hugofs/nosymlink_fs.go
hugofs/nosymlink_test.go
hugofs/walk.go
hugolib/alias.go
hugolib/alias_test.go
hugolib/config_test.go
hugolib/filesystems/basefs.go
hugolib/hugo_modules_test.go
hugolib/hugo_sites.go
hugolib/hugo_sites_build.go
hugolib/hugo_sites_build_test.go
hugolib/integrationtest_builder.go
hugolib/mount_filters_test.go
hugolib/page__new.go
hugolib/page_test.go
hugolib/pagebundler_test.go
hugolib/pages_capture.go
hugolib/pages_capture_test.go
hugolib/renderstring_test.go
hugolib/resource_chain_test.go
hugolib/site.go
hugolib/site_new.go
hugolib/site_render.go
hugolib/testhelpers_test.go
langs/i18n/i18n.go
langs/i18n/i18n_test.go
markup/asciidocext/convert_test.go
markup/goldmark/convert_test.go
markup/goldmark/toc_test.go
markup/org/convert.go
markup/org/convert_test.go
markup/pandoc/convert_test.go
markup/rst/convert_test.go
modules/client.go
parser/metadecoders/decoder.go
resources/page/pagemeta/page_frontmatter.go
resources/page/testhelpers_page_test.go
resources/resource_spec.go
resources/resource_transformers/babel/babel.go
resources/resource_transformers/babel/integration_test.go
resources/resource_transformers/postcss/integration_test.go
resources/resource_transformers/postcss/postcss.go
resources/resource_transformers/postcss/postcss_test.go
resources/resource_transformers/tocss/dartsass/client.go
resources/resource_transformers/tocss/dartsass/integration_test.go
tpl/collections/collections.go
tpl/data/data.go
tpl/data/data_test.go
tpl/data/resources_test.go
tpl/fmt/fmt.go
transform/livereloadinject/livereloadinject.go
transform/metainject/hugogenerator.go

index a59ea048d42b9719e21722cc3c7c5642029009de..a8a45988e02162c0adb6f2f399b62bdc1672bc16 100644 (file)
@@ -16,11 +16,10 @@ package filecache_test
 import (
        "path/filepath"
 
-       jww "github.com/spf13/jwalterweatherman"
-
        "testing"
        "time"
 
+       "github.com/bep/logg"
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/htesting"
        "github.com/gohugoio/hugo/hugolib"
@@ -80,7 +79,7 @@ iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAA
 `
 
        b := hugolib.NewIntegrationTestBuilder(
-               hugolib.IntegrationTestConfig{T: t, TxtarString: files, Running: true, RunGC: true, NeedsOsFS: true, LogLevel: jww.LevelInfo},
+               hugolib.IntegrationTestConfig{T: t, TxtarString: files, Running: true, RunGC: true, NeedsOsFS: true, LogLevel: logg.LevelInfo},
        ).Build()
 
        b.Assert(b.GCCount, qt.Equals, 0)
index f7b71197330476c8edbbff4ef6731d314cde7799..7322a210a6929adb066490e033a6043a670b28c7 100644 (file)
@@ -28,12 +28,11 @@ import (
        "syscall"
        "time"
 
-       jww "github.com/spf13/jwalterweatherman"
-
        "go.uber.org/automaxprocs/maxprocs"
 
        "github.com/bep/clock"
        "github.com/bep/lazycache"
+       "github.com/bep/logg"
        "github.com/bep/overlayfs"
        "github.com/bep/simplecobra"
 
@@ -114,7 +113,6 @@ type rootCommand struct {
        baseURL              string
        gc                   bool
        poll                 string
-       panicOnWarning       bool
        forceSyncStatic      bool
        printPathWarnings    bool
        printUnusedTemplates bool
@@ -308,7 +306,7 @@ func (r *rootCommand) ConfigFromProvider(key int32, cfg config.Provider) (*commo
 
 func (r *rootCommand) HugFromConfig(conf *commonConfig) (*hugolib.HugoSites, error) {
        h, _, err := r.hugoSites.GetOrCreate(r.configVersionID.Load(), func(key int32) (*hugolib.HugoSites, error) {
-               depsCfg := deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, Logger: r.logger}
+               depsCfg := deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level()}
                return hugolib.NewHugoSites(depsCfg)
        })
        return h, err
@@ -320,7 +318,7 @@ func (r *rootCommand) Hugo(cfg config.Provider) (*hugolib.HugoSites, error) {
                if err != nil {
                        return nil, err
                }
-               depsCfg := deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, Logger: r.logger}
+               depsCfg := deps.DepsCfg{Configs: conf.configs, Fs: conf.fs, LogOut: r.logger.Out(), LogLevel: r.logger.Level()}
                return hugolib.NewHugoSites(depsCfg)
        })
        return h, err
@@ -410,7 +408,6 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
                return err
        }
 
-       loggers.PanicOnWarning.Store(r.panicOnWarning)
        r.commonConfigs = lazycache.New[int32, *commonConfig](lazycache.Options{MaxEntries: 5})
        r.hugoSites = lazycache.New[int32, *hugolib.HugoSites](lazycache.Options{MaxEntries: 5})
 
@@ -418,43 +415,48 @@ func (r *rootCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
 }
 
 func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
-       var (
-               outHandle       = r.Out
-               stdoutThreshold = jww.LevelWarn
-       )
-
-       if r.verbose {
-               helpers.Deprecated("--verbose", "use --logLevel info", false)
-               stdoutThreshold = jww.LevelInfo
-       }
-
-       if r.debug {
-               helpers.Deprecated("--debug", "use --logLevel debug", false)
-               stdoutThreshold = jww.LevelDebug
-       }
+       level := logg.LevelWarn
 
        if r.logLevel != "" {
                switch strings.ToLower(r.logLevel) {
                case "debug":
-                       stdoutThreshold = jww.LevelDebug
+                       level = logg.LevelDebug
                case "info":
-                       stdoutThreshold = jww.LevelInfo
+                       level = logg.LevelInfo
                case "warn", "warning":
-                       stdoutThreshold = jww.LevelWarn
+                       level = logg.LevelWarn
                case "error":
-                       stdoutThreshold = jww.LevelError
+                       level = logg.LevelError
                default:
                        return nil, fmt.Errorf("invalid log level: %q, must be one of debug, warn, info or error", r.logLevel)
                }
+       } else {
+               if r.verbose {
+                       helpers.Deprecated("--verbose", "use --logLevel info", false)
+                       level = logg.LevelInfo
+               }
+
+               if r.debug {
+                       helpers.Deprecated("--debug", "use --logLevel debug", false)
+                       level = logg.LevelDebug
+               }
        }
 
-       loggers.InitGlobalLogger(stdoutThreshold, jww.LevelWarn, outHandle, io.Discard)
-       helpers.InitLoggers()
-       return loggers.NewLogger(stdoutThreshold, jww.LevelWarn, outHandle, io.Discard, running), nil
+       optsLogger := loggers.Options{
+               Distinct:    true,
+               Level:       level,
+               Stdout:      r.Out,
+               Stderr:      r.Out,
+               StoreErrors: running,
+       }
+
+       return loggers.New(optsLogger), nil
+
 }
 
 func (r *rootCommand) Reset() {
        r.logger.Reset()
+       loggers.Log().Reset()
 }
 
 // IsTestRun reports whether the command is running as a test.
@@ -530,7 +532,7 @@ func applyLocalFlagsBuild(cmd *cobra.Command, r *rootCommand) {
        cmd.Flags().StringP("layoutDir", "l", "", "filesystem path to layout directory")
        cmd.Flags().BoolVar(&r.gc, "gc", false, "enable to run some cleanup tasks (remove unused cache files) after the build")
        cmd.Flags().StringVar(&r.poll, "poll", "", "set this to a poll interval, e.g --poll 700ms, to use a poll based approach to watch for file system changes")
-       cmd.Flags().BoolVar(&r.panicOnWarning, "panicOnWarning", false, "panic on first WARNING log")
+       cmd.Flags().Bool("panicOnWarning", false, "panic on first WARNING log")
        cmd.Flags().Bool("templateMetrics", false, "display metrics about template executions")
        cmd.Flags().Bool("templateMetricsHints", false, "calculate some improvement hints when combined with --templateMetrics")
        cmd.Flags().BoolVar(&r.forceSyncStatic, "forceSyncStatic", false, "copy all files when static is changed.")
index 8dae4bd882446831dc2359c768c465563474aab3..82127adf4c7c42b430f99cd02e1e55f7c9ce3033 100644 (file)
@@ -52,7 +52,7 @@ documentation.
                        if err != nil {
                                return err
                        }
-                       deployer, err := deploy.New(h.Configs.GetFirstLanguageConfig(), h.PathSpec.PublishFs)
+                       deployer, err := deploy.New(h.Configs.GetFirstLanguageConfig(), h.Log, h.PathSpec.PublishFs)
                        if err != nil {
                                return err
                        }
index 95dbb1ca8980bee425781fd436b41d5efe5e73b5..60e558c70a6a7c5b0d48c95d6ce2298d9ac061b8 100644 (file)
@@ -26,11 +26,13 @@ import (
        "sync"
        "time"
 
+       "github.com/bep/logg"
        "github.com/bep/simplecobra"
        "github.com/fsnotify/fsnotify"
        "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/common/htime"
        "github.com/gohugoio/hugo/common/hugo"
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/common/terminal"
        "github.com/gohugoio/hugo/common/types"
@@ -68,7 +70,6 @@ type hugoBuilder struct {
        onConfigLoaded func(reloaded bool) error
 
        fastRenderMode     bool
-       buildWatch         bool
        showErrorInBrowser bool
 
        errState hugoBuilderErrState
@@ -131,7 +132,7 @@ func (e *hugoBuilderErrState) wasErr() bool {
 }
 
 func (c *hugoBuilder) errCount() int {
-       return int(c.r.logger.LogCounters().ErrorCounter.Count())
+       return c.r.logger.LoggCount(logg.LevelError) + loggers.Log().LoggCount(logg.LevelError)
 }
 
 // getDirList provides NewWatcher() with a list of directories to watch for changes.
@@ -363,7 +364,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
                configFiles = conf.configs.LoadingInfo.ConfigFiles
        })
 
-       c.r.logger.Println("Watching for config changes in", strings.Join(configFiles, ", "))
+       c.r.Println("Watching for config changes in", strings.Join(configFiles, ", "))
        for _, configFile := range configFiles {
                watcher.Add(configFile)
                configSet[configFile] = true
@@ -461,6 +462,7 @@ func (c *hugoBuilder) copyStatic() (map[string]uint64, error) {
 }
 
 func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint64, error) {
+       infol := c.r.logger.InfoCommand("copy static")
        publishDir := helpers.FilePathSeparator
 
        if sourceFs.PublishFolder != "" {
@@ -484,13 +486,13 @@ func (c *hugoBuilder) copyStaticTo(sourceFs *filesystems.SourceFilesystem) (uint
        syncer.SrcFs = fs
 
        if syncer.Delete {
-               c.r.logger.Infoln("removing all files from destination that don't exist in static dirs")
+               infol.Logf("removing all files from destination that don't exist in static dirs")
 
                syncer.DeleteFilter = func(f os.FileInfo) bool {
                        return f.IsDir() && strings.HasPrefix(f.Name(), ".")
                }
        }
-       c.r.logger.Infoln("syncing static files to", publishDir)
+       infol.Logf("syncing static files to %s", publishDir)
 
        // because we are using a baseFs (to get the union right).
        // set sync src to root
@@ -545,14 +547,13 @@ func (c *hugoBuilder) fullBuild(noBuildLock bool) error {
                langCount map[string]uint64
        )
 
-       if !c.r.quiet {
-               fmt.Println("Start building sites … ")
-               fmt.Println(hugo.BuildVersionString())
-               if terminal.IsTerminal(os.Stdout) {
-                       defer func() {
-                               fmt.Print(showCursor + clearLine)
-                       }()
-               }
+       c.r.logger.Println("Start building sites … ")
+       c.r.logger.Println(hugo.BuildVersionString())
+       c.r.logger.Println()
+       if terminal.IsTerminal(os.Stdout) {
+               defer func() {
+                       fmt.Print(showCursor + clearLine)
+               }()
        }
 
        copyStaticFunc := func() error {
index 30ada15f8f9d3a0ff84d893936cbf9fbafe59fc7..f2c56a9a1e99ce3d5b88c1770101e9f24d3afb4a 100644 (file)
@@ -19,12 +19,11 @@ import (
        "errors"
        "fmt"
        "io"
+       "log"
        "os"
        "path/filepath"
        "regexp"
 
-       jww "github.com/spf13/jwalterweatherman"
-
        "strconv"
        "strings"
        "time"
@@ -299,7 +298,7 @@ func (c *importCommand) convertJekyllMetaData(m any, postName string, postDate t
 }
 
 func (c *importCommand) convertJekyllPost(path, relPath, targetDir string, draft bool) error {
-       jww.TRACE.Println("Converting", path)
+       log.Println("Converting", path)
 
        filename := filepath.Base(path)
        postDate, postName, err := c.parseJekyllFilename(filename)
@@ -308,7 +307,7 @@ func (c *importCommand) convertJekyllPost(path, relPath, targetDir string, draft
                return nil
        }
 
-       jww.TRACE.Println(filename, postDate, postName)
+       log.Println(filename, postDate, postName)
 
        targetFile := filepath.Join(targetDir, relPath)
        targetParentDir := filepath.Dir(targetFile)
@@ -367,7 +366,7 @@ func (c *importCommand) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyl
                                if _, ok := jekyllPostDirs[entry.Name()]; !ok {
                                        err = hugio.CopyDir(fs, sfp, dfp, nil)
                                        if err != nil {
-                                               jww.ERROR.Println(err)
+                                               c.r.logger.Errorln(err)
                                        }
                                }
                        }
@@ -388,7 +387,7 @@ func (c *importCommand) copyJekyllFilesAndFolders(jekyllRoot, dest string, jekyl
                        if !isExcept && entry.Name()[0] != '.' && entry.Name()[0] != '_' {
                                err = hugio.CopyFile(fs, sfp, dfp)
                                if err != nil {
-                                       jww.ERROR.Println(err)
+                                       c.r.logger.Errorln(err)
                                }
                        }
                }
index 80036618c2b07a7e0ae1a01897a40c1fef7c3f45..7e6ec5b13eeaca7ba2bfb2f231f6f3d7259922e9 100644 (file)
@@ -67,7 +67,6 @@ import (
 )
 
 var (
-       logErrorRe                    = regexp.MustCompile(`(?s)ERROR \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} `)
        logDuplicateTemplateExecuteRe = regexp.MustCompile(`: template: .*?:\d+:\d+: executing ".*?"`)
        logDuplicateTemplateParseRe   = regexp.MustCompile(`: template: .*?:\d+:\d*`)
 )
@@ -106,9 +105,7 @@ func newServerCommand() *serverCommand {
        // Flags.
        var uninstall bool
 
-       var c *serverCommand
-
-       c = &serverCommand{
+       c := &serverCommand{
                quit: make(chan bool),
                commands: []simplecobra.Commander{
                        &simpleCommand{
@@ -654,8 +651,8 @@ func (c *serverCommand) getErrorWithContext() any {
 
        m := make(map[string]any)
 
-       //xwm["Error"] = errors.New(cleanErrorLog(removeErrorPrefixFromLog(c.r.logger.Errors())))
-       m["Error"] = errors.New(cleanErrorLog(removeErrorPrefixFromLog(c.r.logger.Errors())))
+       m["Error"] = cleanErrorLog(c.r.logger.Errors())
+
        m["Version"] = hugo.BuildVersionString()
        ferrors := herrors.UnwrapFileErrorsWithErrorContext(c.errState.buildErr())
        m["Files"] = ferrors
@@ -861,6 +858,9 @@ func (c *serverCommand) serve() error {
                        return err
                }
 
+               // We need the server to share the same logger as the Hugo build (for error counts etc.)
+               c.r.logger = h.Log
+
                if isMultiHost {
                        for _, l := range conf.configs.ConfigLangs() {
                                baseURLs = append(baseURLs, l.BaseURL().String())
@@ -1066,8 +1066,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
                        }
                })
 
-               // prevent spamming the log on changes
-               logger := helpers.NewDistinctErrorLogger()
+               logger := s.c.r.logger
 
                for _, ev := range staticEvents {
                        // Due to our approach of layering both directories and the content's rendered output
@@ -1206,10 +1205,6 @@ func pickOneWriteOrCreatePath(events []fsnotify.Event) string {
        return name
 }
 
-func removeErrorPrefixFromLog(content string) string {
-       return logErrorRe.ReplaceAllLiteralString(content, "")
-}
-
 func formatByteCount(b uint64) string {
        const unit = 1000
        if b < unit {
diff --git a/commands/xcommand_template.go b/commands/xcommand_template.go
deleted file mode 100644 (file)
index eeb9409..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-// Copyright 2023 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package commands
-
-import (
-       "context"
-       "fmt"
-
-       "github.com/bep/simplecobra"
-       "github.com/spf13/cobra"
-)
-
-func newSimpleTemplateCommand() simplecobra.Commander {
-       return &simpleCommand{
-               name: "template",
-               run: func(ctx context.Context, cd *simplecobra.Commandeer, r *rootCommand, args []string) error {
-
-                       return nil
-               },
-               withc: func(cmd *cobra.Command, r *rootCommand) {
-
-               },
-       }
-
-}
-
-func newTemplateCommand() *templateCommand {
-       return &templateCommand{
-               commands: []simplecobra.Commander{},
-       }
-
-}
-
-type templateCommand struct {
-       r *rootCommand
-
-       commands []simplecobra.Commander
-}
-
-func (c *templateCommand) Commands() []simplecobra.Commander {
-       return c.commands
-}
-
-func (c *templateCommand) Name() string {
-       return "template"
-}
-
-func (c *templateCommand) Run(ctx context.Context, cd *simplecobra.Commandeer, args []string) error {
-       conf, err := c.r.ConfigFromProvider(c.r.configVersionID.Load(), flagsToCfg(cd, nil))
-       if err != nil {
-               return err
-       }
-       fmt.Println("templateCommand.Run", conf)
-
-       return nil
-}
-
-func (c *templateCommand) Init(cd *simplecobra.Commandeer) error {
-       cmd := cd.CobraCommand
-       cmd.Short = "Print the site configuration"
-       cmd.Long = `Print the site configuration, both default and custom settings.`
-       return nil
-}
-
-func (c *templateCommand) PreRun(cd, runner *simplecobra.Commandeer) error {
-       c.r = cd.Root.Command.(*rootCommand)
-       return nil
-}
diff --git a/common/loggers/handlerdefault.go b/common/loggers/handlerdefault.go
new file mode 100644 (file)
index 0000000..28b85ed
--- /dev/null
@@ -0,0 +1,106 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+// Some functions in this file (see comments) is based on the Go source code,
+// copyright The Go Authors and  governed by a BSD-style license.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// package loggers contains some basic logging setup.
+package loggers
+
+import (
+       "fmt"
+       "io"
+       "strings"
+       "sync"
+
+       "github.com/bep/logg"
+
+       "github.com/fatih/color"
+)
+
+var bold = color.New(color.Bold)
+
+// levelColor mapping.
+var levelColor = [...]*color.Color{
+       logg.LevelDebug: color.New(color.FgWhite),
+       logg.LevelInfo:  color.New(color.FgBlue),
+       logg.LevelWarn:  color.New(color.FgYellow),
+       logg.LevelError: color.New(color.FgRed),
+}
+
+// levelString mapping.
+var levelString = [...]string{
+       logg.LevelDebug: "DEBUG",
+       logg.LevelInfo:  "INFO ",
+       logg.LevelWarn:  "WARN ",
+       logg.LevelError: "ERROR",
+}
+
+// newDefaultHandler handler.
+func newDefaultHandler(outWriter, errWriter io.Writer) logg.Handler {
+       return &defaultHandler{
+               outWriter: outWriter,
+               errWriter: errWriter,
+               Padding:   0,
+       }
+}
+
+// Default Handler implementation.
+// Based on https://github.com/apex/log/blob/master/handlers/cli/cli.go
+type defaultHandler struct {
+       mu        sync.Mutex
+       outWriter io.Writer // Defaults to os.Stdout.
+       errWriter io.Writer // Defaults to os.Stderr.
+
+       Padding int
+}
+
+// HandleLog implements logg.Handler.
+func (h *defaultHandler) HandleLog(e *logg.Entry) error {
+       color := levelColor[e.Level]
+       level := levelString[e.Level]
+
+       h.mu.Lock()
+       defer h.mu.Unlock()
+
+       var w io.Writer
+       if e.Level > logg.LevelInfo {
+               w = h.errWriter
+       } else {
+               w = h.outWriter
+       }
+
+       var prefix string
+       for _, field := range e.Fields {
+               if field.Name == FieldNameCmd {
+                       prefix = fmt.Sprint(field.Value)
+                       break
+               }
+       }
+
+       if prefix != "" {
+               prefix = prefix + ": "
+       }
+
+       color.Fprintf(w, "%s %s%s", bold.Sprintf("%*s", h.Padding+1, level), color.Sprint(prefix), e.Message)
+
+       for _, field := range e.Fields {
+               if strings.HasPrefix(field.Name, reservedFieldNamePrefix) {
+                       continue
+               }
+               fmt.Fprintf(w, " %s %v", color.Sprint(field.Name), field.Value)
+       }
+
+       fmt.Fprintln(w)
+
+       return nil
+}
diff --git a/common/loggers/handlersmisc.go b/common/loggers/handlersmisc.go
new file mode 100644 (file)
index 0000000..5c9d6c0
--- /dev/null
@@ -0,0 +1,158 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+// Some functions in this file (see comments) is based on the Go source code,
+// copyright The Go Authors and  governed by a BSD-style license.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package loggers
+
+import (
+       "fmt"
+       "strings"
+       "sync"
+
+       "github.com/bep/logg"
+       "github.com/gohugoio/hugo/identity"
+)
+
+// PanicOnWarningHook panics on warnings.
+var PanicOnWarningHook = func(e *logg.Entry) error {
+       if e.Level != logg.LevelWarn {
+               return nil
+       }
+       panic(e.Message)
+}
+
+func newLogLevelCounter() *logLevelCounter {
+       return &logLevelCounter{
+               counters: make(map[logg.Level]int),
+       }
+}
+
+func newLogOnceHandler(threshold logg.Level) *logOnceHandler {
+       return &logOnceHandler{
+               threshold: threshold,
+               seen:      make(map[uint64]bool),
+       }
+}
+
+func newStopHandler(h ...logg.Handler) *stopHandler {
+       return &stopHandler{
+               handlers: h,
+       }
+}
+
+func newSuppressStatementsHandler(statements map[string]bool) *suppressStatementsHandler {
+       return &suppressStatementsHandler{
+               statements: statements,
+       }
+}
+
+type logLevelCounter struct {
+       mu       sync.RWMutex
+       counters map[logg.Level]int
+}
+
+func (h *logLevelCounter) HandleLog(e *logg.Entry) error {
+       h.mu.Lock()
+       defer h.mu.Unlock()
+       h.counters[e.Level]++
+       return nil
+}
+
+var stopError = fmt.Errorf("stop")
+
+type logOnceHandler struct {
+       threshold logg.Level
+       mu        sync.Mutex
+       seen      map[uint64]bool
+}
+
+func (h *logOnceHandler) HandleLog(e *logg.Entry) error {
+       if e.Level < h.threshold {
+               // We typically only want to enable this for warnings and above.
+               // The common use case is that many go routines may log the same error.
+               return nil
+       }
+       h.mu.Lock()
+       defer h.mu.Unlock()
+       hash := identity.HashUint64(e.Level, e.Message, e.Fields)
+       if h.seen[hash] {
+               return stopError
+       }
+       h.seen[hash] = true
+       return nil
+}
+
+func (h *logOnceHandler) reset() {
+       h.mu.Lock()
+       defer h.mu.Unlock()
+       h.seen = make(map[uint64]bool)
+}
+
+type stopHandler struct {
+       handlers []logg.Handler
+}
+
+// HandleLog implements logg.Handler.
+func (h *stopHandler) HandleLog(e *logg.Entry) error {
+       for _, handler := range h.handlers {
+               if err := handler.HandleLog(e); err != nil {
+                       if err == stopError {
+                               return nil
+                       }
+                       return err
+               }
+       }
+       return nil
+}
+
+type suppressStatementsHandler struct {
+       statements map[string]bool
+}
+
+func (h *suppressStatementsHandler) HandleLog(e *logg.Entry) error {
+       for _, field := range e.Fields {
+               if field.Name == FieldNameStatementID {
+                       if h.statements[field.Value.(string)] {
+                               return stopError
+                       }
+               }
+       }
+       return nil
+}
+
+// replacer creates a new log handler that does string replacement in log messages.
+func replacer(repl *strings.Replacer) logg.Handler {
+       return logg.HandlerFunc(func(e *logg.Entry) error {
+               e.Message = repl.Replace(e.Message)
+               for i, field := range e.Fields {
+                       if s, ok := field.Value.(string); ok {
+                               e.Fields[i].Value = repl.Replace(s)
+                       }
+               }
+               return nil
+       })
+}
+
+// whiteSpaceTrimmer creates a new log handler that trims whitespace from log messages and string fields.
+func whiteSpaceTrimmer() logg.Handler {
+       return logg.HandlerFunc(func(e *logg.Entry) error {
+               e.Message = strings.TrimSpace(e.Message)
+               for i, field := range e.Fields {
+                       if s, ok := field.Value.(string); ok {
+                               e.Fields[i].Value = strings.TrimSpace(s)
+                       }
+               }
+               return nil
+       })
+}
diff --git a/common/loggers/handlerterminal.go b/common/loggers/handlerterminal.go
new file mode 100644 (file)
index 0000000..e3d377b
--- /dev/null
@@ -0,0 +1,90 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+// Some functions in this file (see comments) is based on the Go source code,
+// copyright The Go Authors and  governed by a BSD-style license.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package loggers
+
+import (
+       "fmt"
+       "io"
+       "strings"
+       "sync"
+
+       "github.com/bep/logg"
+)
+
+// newNoColoursHandler creates a new NoColoursHandler
+func newNoColoursHandler(outWriter, errWriter io.Writer, noLevelPrefix bool, predicate func(*logg.Entry) bool) *noColoursHandler {
+       if predicate == nil {
+               predicate = func(e *logg.Entry) bool { return true }
+       }
+       return &noColoursHandler{
+               noLevelPrefix: noLevelPrefix,
+               outWriter:     outWriter,
+               errWriter:     errWriter,
+               predicate:     predicate,
+       }
+}
+
+type noColoursHandler struct {
+       mu            sync.Mutex
+       outWriter     io.Writer // Defaults to os.Stdout.
+       errWriter     io.Writer // Defaults to os.Stderr.
+       predicate     func(*logg.Entry) bool
+       noLevelPrefix bool
+}
+
+func (h *noColoursHandler) HandleLog(e *logg.Entry) error {
+       if !h.predicate(e) {
+               return nil
+       }
+       h.mu.Lock()
+       defer h.mu.Unlock()
+
+       var w io.Writer
+       if e.Level > logg.LevelInfo {
+               w = h.errWriter
+       } else {
+               w = h.outWriter
+       }
+
+       var prefix string
+       for _, field := range e.Fields {
+               if field.Name == FieldNameCmd {
+                       prefix = fmt.Sprint(field.Value)
+                       break
+               }
+       }
+
+       if prefix != "" {
+               prefix = prefix + ": "
+       }
+
+       if h.noLevelPrefix {
+               fmt.Fprintf(w, "%s%s", prefix, e.Message)
+       } else {
+               fmt.Fprintf(w, "%s %s%s", levelString[e.Level], prefix, e.Message)
+       }
+
+       for _, field := range e.Fields {
+               if strings.HasPrefix(field.Name, reservedFieldNamePrefix) {
+                       continue
+               }
+               fmt.Fprintf(w, " %s %q", field.Name, field.Value)
+
+       }
+       fmt.Fprintln(w)
+
+       return nil
+}
diff --git a/common/loggers/ignorableLogger.go b/common/loggers/ignorableLogger.go
deleted file mode 100644 (file)
index c8aba56..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-// Copyright 2020 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package loggers
-
-import (
-       "fmt"
-)
-
-// IgnorableLogger is a logger that ignores certain log statements.
-type IgnorableLogger interface {
-       Logger
-       Errorsf(statementID, format string, v ...any)
-       Apply(logger Logger) IgnorableLogger
-}
-
-type ignorableLogger struct {
-       Logger
-       statements map[string]bool
-}
-
-// NewIgnorableLogger wraps the given logger and ignores the log statement IDs given.
-func NewIgnorableLogger(logger Logger, statements map[string]bool) IgnorableLogger {
-       if statements == nil {
-               statements = make(map[string]bool)
-       }
-       return ignorableLogger{
-               Logger:     logger,
-               statements: statements,
-       }
-}
-
-// Errorsf logs statementID as an ERROR if not configured as ignoreable.
-func (l ignorableLogger) Errorsf(statementID, format string, v ...any) {
-       if l.statements[statementID] {
-               // Ignore.
-               return
-       }
-       ignoreMsg := fmt.Sprintf(`
-If you feel that this should not be logged as an ERROR, you can ignore it by adding this to your site config:
-ignoreErrors = [%q]`, statementID)
-
-       format += ignoreMsg
-
-       l.Errorf(format, v...)
-}
-
-func (l ignorableLogger) Apply(logger Logger) IgnorableLogger {
-       return ignorableLogger{
-               Logger:     logger,
-               statements: l.statements,
-       }
-}
diff --git a/common/loggers/logger.go b/common/loggers/logger.go
new file mode 100644 (file)
index 0000000..85c75ef
--- /dev/null
@@ -0,0 +1,303 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+// Some functions in this file (see comments) is based on the Go source code,
+// copyright The Go Authors and  governed by a BSD-style license.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package loggers
+
+import (
+       "fmt"
+       "io"
+       "os"
+       "strings"
+       "time"
+
+       "github.com/bep/logg"
+       "github.com/bep/logg/handlers/multi"
+       "github.com/gohugoio/hugo/common/terminal"
+)
+
+var (
+       reservedFieldNamePrefix = "__h_field_"
+       // FieldNameCmd is the name of the field that holds the command name.
+       FieldNameCmd = reservedFieldNamePrefix + "_cmd"
+       // Used to suppress statements.
+       FieldNameStatementID = reservedFieldNamePrefix + "__h_field_statement_id"
+)
+
+// Options defines options for the logger.
+type Options struct {
+       Level               logg.Level
+       Stdout              io.Writer
+       Stderr              io.Writer
+       Distinct            bool
+       StoreErrors         bool
+       HandlerPost         func(e *logg.Entry) error
+       SuppresssStatements map[string]bool
+}
+
+// New creates a new logger with the given options.
+func New(opts Options) Logger {
+       if opts.Stdout == nil {
+               opts.Stdout = os.Stdout
+       }
+       if opts.Stderr == nil {
+               opts.Stderr = os.Stdout
+       }
+       if opts.Level == 0 {
+               opts.Level = logg.LevelWarn
+       }
+
+       var logHandler logg.Handler
+       if terminal.PrintANSIColors(os.Stdout) {
+               logHandler = newDefaultHandler(opts.Stdout, opts.Stderr)
+       } else {
+               logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil)
+       }
+
+       errorsw := &strings.Builder{}
+       logCounters := newLogLevelCounter()
+       handlers := []logg.Handler{
+               whiteSpaceTrimmer(),
+               logHandler,
+               logCounters,
+       }
+
+       if opts.HandlerPost != nil {
+               var hookHandler logg.HandlerFunc = func(e *logg.Entry) error {
+                       opts.HandlerPost(e)
+                       return nil
+               }
+               handlers = append(handlers, hookHandler)
+       }
+
+       if opts.StoreErrors {
+               h := newNoColoursHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool {
+                       return e.Level >= logg.LevelError
+               })
+
+               handlers = append(handlers, h)
+       }
+
+       logHandler = multi.New(handlers...)
+
+       var logOnce *logOnceHandler
+       if opts.Distinct {
+               logOnce = newLogOnceHandler(logg.LevelWarn)
+               logHandler = newStopHandler(logOnce, logHandler)
+       }
+
+       if opts.SuppresssStatements != nil && len(opts.SuppresssStatements) > 0 {
+               logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppresssStatements), logHandler)
+       }
+
+       logger := logg.New(
+               logg.Options{
+                       Level:   opts.Level,
+                       Handler: logHandler,
+               },
+       )
+
+       l := logger.WithLevel(opts.Level)
+
+       reset := func() {
+               logCounters.mu.Lock()
+               defer logCounters.mu.Unlock()
+               logCounters.counters = make(map[logg.Level]int)
+               errorsw.Reset()
+               if logOnce != nil {
+                       logOnce.reset()
+               }
+       }
+
+       return &logAdapter{
+               logCounters: logCounters,
+               errors:      errorsw,
+               reset:       reset,
+               out:         opts.Stdout,
+               level:       opts.Level,
+               logger:      logger,
+               debugl:      l.WithLevel(logg.LevelDebug),
+               infol:       l.WithLevel(logg.LevelInfo),
+               warnl:       l.WithLevel(logg.LevelWarn),
+               errorl:      l.WithLevel(logg.LevelError),
+       }
+}
+
+// NewDefault creates a new logger with the default options.
+func NewDefault() Logger {
+       opts := Options{
+               Distinct: true,
+               Level:    logg.LevelWarn,
+               Stdout:   os.Stdout,
+               Stderr:   os.Stdout,
+       }
+       return New(opts)
+}
+
+func LevelLoggerToWriter(l logg.LevelLogger) io.Writer {
+       return logWriter{l: l}
+}
+
+type Logger interface {
+       Debugf(format string, v ...any)
+       Debugln(v ...any)
+       Error() logg.LevelLogger
+       Errorf(format string, v ...any)
+       Errorln(v ...any)
+       Errors() string
+       Errorsf(id, format string, v ...any)
+       Info() logg.LevelLogger
+       InfoCommand(command string) logg.LevelLogger
+       Infof(format string, v ...any)
+       Infoln(v ...any)
+       Level() logg.Level
+       LoggCount(logg.Level) int
+       Logger() logg.Logger
+       Out() io.Writer
+       Printf(format string, v ...any)
+       Println(v ...any)
+       PrintTimerIfDelayed(start time.Time, name string)
+       Reset()
+       Warn() logg.LevelLogger
+       WarnCommand(command string) logg.LevelLogger
+       Warnf(format string, v ...any)
+       Warnln(v ...any)
+}
+
+type logAdapter struct {
+       logCounters *logLevelCounter
+       errors      *strings.Builder
+       reset       func()
+       out         io.Writer
+       level       logg.Level
+       logger      logg.Logger
+       debugl      logg.LevelLogger
+       infol       logg.LevelLogger
+       warnl       logg.LevelLogger
+       errorl      logg.LevelLogger
+}
+
+func (l *logAdapter) Debugf(format string, v ...any) {
+       l.debugl.Logf(format, v...)
+}
+
+func (l *logAdapter) Debugln(v ...any) {
+       l.debugl.Logf(l.sprint(v...))
+}
+
+func (l *logAdapter) Info() logg.LevelLogger {
+       return l.infol
+}
+
+func (l *logAdapter) InfoCommand(command string) logg.LevelLogger {
+       return l.infol.WithField(FieldNameCmd, command)
+}
+
+func (l *logAdapter) Infof(format string, v ...any) {
+       l.infol.Logf(format, v...)
+}
+
+func (l *logAdapter) Infoln(v ...any) {
+       l.infol.Logf(l.sprint(v...))
+}
+
+func (l *logAdapter) Level() logg.Level {
+       return l.level
+}
+
+func (l *logAdapter) LoggCount(level logg.Level) int {
+       l.logCounters.mu.RLock()
+       defer l.logCounters.mu.RUnlock()
+       return l.logCounters.counters[level]
+}
+
+func (l *logAdapter) Logger() logg.Logger {
+       return l.logger
+}
+
+func (l *logAdapter) Out() io.Writer {
+       return l.out
+}
+
+// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
+// if considerable time is spent.
+func (l *logAdapter) PrintTimerIfDelayed(start time.Time, name string) {
+       elapsed := time.Since(start)
+       milli := int(1000 * elapsed.Seconds())
+       if milli < 500 {
+               return
+       }
+       l.Printf("%s in %v ms", name, milli)
+}
+
+func (l *logAdapter) Printf(format string, v ...any) {
+       fmt.Fprintf(l.out, format, v...)
+}
+
+func (l *logAdapter) Println(v ...any) {
+       fmt.Fprintln(l.out, v...)
+}
+
+func (l *logAdapter) Reset() {
+       l.reset()
+}
+
+func (l *logAdapter) Warn() logg.LevelLogger {
+       return l.warnl
+}
+
+func (l *logAdapter) Warnf(format string, v ...any) {
+       l.warnl.Logf(format, v...)
+}
+
+func (l *logAdapter) WarnCommand(command string) logg.LevelLogger {
+       return l.warnl.WithField(FieldNameCmd, command)
+}
+
+func (l *logAdapter) Warnln(v ...any) {
+       l.warnl.Logf(l.sprint(v...))
+}
+
+func (l *logAdapter) Error() logg.LevelLogger {
+       return l.errorl
+}
+
+func (l *logAdapter) Errorf(format string, v ...any) {
+       l.errorl.Logf(format, v...)
+}
+
+func (l *logAdapter) Errorln(v ...any) {
+       l.errorl.Logf(l.sprint(v...))
+}
+
+func (l *logAdapter) Errors() string {
+       return l.errors.String()
+}
+
+func (l *logAdapter) Errorsf(id, format string, v ...any) {
+       l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...)
+}
+
+func (l *logAdapter) sprint(v ...any) string {
+       return strings.TrimRight(fmt.Sprintln(v...), "\n")
+}
+
+type logWriter struct {
+       l logg.LevelLogger
+}
+
+func (w logWriter) Write(p []byte) (n int, err error) {
+       w.l.Logf("%s", p)
+       return len(p), nil
+}
diff --git a/common/loggers/logger_test.go b/common/loggers/logger_test.go
new file mode 100644 (file)
index 0000000..6aa540b
--- /dev/null
@@ -0,0 +1,156 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+// Some functions in this file (see comments) is based on the Go source code,
+// copyright The Go Authors and  governed by a BSD-style license.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package loggers_test
+
+import (
+       "io"
+       "strings"
+       "testing"
+
+       "github.com/bep/logg"
+       qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/common/loggers"
+)
+
+func TestLogDistinct(t *testing.T) {
+       c := qt.New(t)
+
+       opts := loggers.Options{
+               Distinct:    true,
+               StoreErrors: true,
+               Stdout:      io.Discard,
+               Stderr:      io.Discard,
+       }
+
+       l := loggers.New(opts)
+
+       for i := 0; i < 10; i++ {
+               l.Errorln("error 1")
+               l.Errorln("error 2")
+               l.Warnln("warn 1")
+       }
+       c.Assert(strings.Count(l.Errors(), "error 1"), qt.Equals, 1)
+       c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 2)
+       c.Assert(l.LoggCount(logg.LevelWarn), qt.Equals, 1)
+}
+
+func TestHookLast(t *testing.T) {
+       c := qt.New(t)
+
+       opts := loggers.Options{
+               HandlerPost: func(e *logg.Entry) error {
+                       panic(e.Message)
+               },
+               Stdout: io.Discard,
+               Stderr: io.Discard,
+       }
+
+       l := loggers.New(opts)
+
+       c.Assert(func() { l.Warnln("warn 1") }, qt.PanicMatches, "warn 1")
+}
+
+func TestOptionStoreErrors(t *testing.T) {
+       c := qt.New(t)
+
+       var sb strings.Builder
+
+       opts := loggers.Options{
+               StoreErrors: true,
+               Stderr:      &sb,
+               Stdout:      &sb,
+       }
+
+       l := loggers.New(opts)
+       l.Errorln("error 1")
+       l.Errorln("error 2")
+
+       errorsStr := l.Errors()
+
+       c.Assert(errorsStr, qt.Contains, "error 1")
+       c.Assert(errorsStr, qt.Not(qt.Contains), "ERROR")
+
+       c.Assert(sb.String(), qt.Contains, "error 1")
+       c.Assert(sb.String(), qt.Contains, "ERROR")
+
+}
+
+func TestLogCount(t *testing.T) {
+       c := qt.New(t)
+
+       opts := loggers.Options{
+               StoreErrors: true,
+       }
+
+       l := loggers.New(opts)
+       l.Errorln("error 1")
+       l.Errorln("error 2")
+       l.Warnln("warn 1")
+
+       c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 2)
+       c.Assert(l.LoggCount(logg.LevelWarn), qt.Equals, 1)
+       c.Assert(l.LoggCount(logg.LevelInfo), qt.Equals, 0)
+}
+
+func TestSuppressStatements(t *testing.T) {
+       c := qt.New(t)
+
+       opts := loggers.Options{
+               StoreErrors: true,
+               SuppresssStatements: map[string]bool{
+                       "error-1": true,
+               },
+       }
+
+       l := loggers.New(opts)
+       l.Error().WithField(loggers.FieldNameStatementID, "error-1").Logf("error 1")
+       l.Errorln("error 2")
+
+       errorsStr := l.Errors()
+
+       c.Assert(errorsStr, qt.Not(qt.Contains), "error 1")
+       c.Assert(errorsStr, qt.Contains, "error 2")
+       c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 1)
+
+}
+
+func TestReset(t *testing.T) {
+       c := qt.New(t)
+
+       opts := loggers.Options{
+               StoreErrors: true,
+               Distinct:    true,
+               Stdout:      io.Discard,
+               Stderr:      io.Discard,
+       }
+
+       l := loggers.New(opts)
+
+       for i := 0; i < 3; i++ {
+               l.Errorln("error 1")
+               l.Errorln("error 2")
+               l.Errorln("error 1")
+               c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 2)
+
+               l.Reset()
+
+               errorsStr := l.Errors()
+
+               c.Assert(errorsStr, qt.Equals, "")
+               c.Assert(l.LoggCount(logg.LevelError), qt.Equals, 0)
+
+       }
+}
diff --git a/common/loggers/loggerglobal.go b/common/loggers/loggerglobal.go
new file mode 100644 (file)
index 0000000..92b2469
--- /dev/null
@@ -0,0 +1,53 @@
+// Copyright 2023 The Hugo Authors. All rights reserved.
+// Some functions in this file (see comments) is based on the Go source code,
+// copyright The Go Authors and  governed by a BSD-style license.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package loggers
+
+import (
+       "sync"
+
+       "github.com/bep/logg"
+)
+
+func InitGlobalLogger(panicOnWarnings bool) {
+       logMu.Lock()
+       defer logMu.Unlock()
+       var logHookLast func(e *logg.Entry) error
+       if panicOnWarnings {
+               logHookLast = PanicOnWarningHook
+       }
+
+       log = New(
+               Options{
+                       Distinct:    true,
+                       HandlerPost: logHookLast,
+               },
+       )
+}
+
+var logMu sync.Mutex
+
+func Log() Logger {
+       logMu.Lock()
+       defer logMu.Unlock()
+       return log
+}
+
+// The global logger.
+var log Logger
+
+func init() {
+       InitGlobalLogger(false)
+}
diff --git a/common/loggers/loggers.go b/common/loggers/loggers.go
deleted file mode 100644 (file)
index fbbbca4..0000000
+++ /dev/null
@@ -1,355 +0,0 @@
-// Copyright 2020 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package loggers
-
-import (
-       "bytes"
-       "fmt"
-       "io"
-       "log"
-       "os"
-       "regexp"
-       "runtime"
-       "sync/atomic"
-       "time"
-
-       "github.com/gohugoio/hugo/common/terminal"
-
-       jww "github.com/spf13/jwalterweatherman"
-)
-
-var (
-       // Counts ERROR logs to the global jww logger.
-       GlobalErrorCounter *jww.Counter
-       PanicOnWarning     atomic.Bool
-)
-
-func init() {
-       GlobalErrorCounter = &jww.Counter{}
-       jww.SetLogListeners(jww.LogCounter(GlobalErrorCounter, jww.LevelError))
-}
-
-func LoggerToWriterWithPrefix(logger *log.Logger, prefix string) io.Writer {
-       return prefixWriter{
-               logger: logger,
-               prefix: prefix,
-       }
-}
-
-type prefixWriter struct {
-       logger *log.Logger
-       prefix string
-}
-
-func (w prefixWriter) Write(p []byte) (n int, err error) {
-       w.logger.Printf("%s: %s", w.prefix, p)
-       return len(p), nil
-}
-
-type Logger interface {
-       Printf(format string, v ...any)
-       Println(v ...any)
-       PrintTimerIfDelayed(start time.Time, name string)
-       Debug() *log.Logger
-       Debugf(format string, v ...any)
-       Debugln(v ...any)
-       Info() *log.Logger
-       Infof(format string, v ...any)
-       Infoln(v ...any)
-       Warn() *log.Logger
-       Warnf(format string, v ...any)
-       Warnln(v ...any)
-       Error() *log.Logger
-       Errorf(format string, v ...any)
-       Errorln(v ...any)
-       Errors() string
-
-       Out() io.Writer
-
-       Reset()
-
-       // Used in tests.
-       LogCounters() *LogCounters
-}
-
-type LogCounters struct {
-       ErrorCounter *jww.Counter
-       WarnCounter  *jww.Counter
-}
-
-type logger struct {
-       *jww.Notepad
-
-       // The writer that represents stdout.
-       // Will be io.Discard when in quiet mode.
-       out io.Writer
-
-       logCounters *LogCounters
-
-       // This is only set in server mode.
-       errors *bytes.Buffer
-}
-
-func (l *logger) Printf(format string, v ...any) {
-       l.FEEDBACK.Printf(format, v...)
-}
-
-func (l *logger) Println(v ...any) {
-       l.FEEDBACK.Println(v...)
-}
-
-func (l *logger) Debug() *log.Logger {
-       return l.DEBUG
-}
-
-func (l *logger) Debugf(format string, v ...any) {
-       l.DEBUG.Printf(format, v...)
-}
-
-func (l *logger) Debugln(v ...any) {
-       l.DEBUG.Println(v...)
-}
-
-func (l *logger) Infof(format string, v ...any) {
-       l.INFO.Printf(format, v...)
-}
-
-func (l *logger) Infoln(v ...any) {
-       l.INFO.Println(v...)
-}
-
-func (l *logger) Info() *log.Logger {
-       return l.INFO
-}
-
-const panicOnWarningMessage = "Warning trapped. Remove the --panicOnWarning flag to continue."
-
-func (l *logger) Warnf(format string, v ...any) {
-       l.WARN.Printf(format, v...)
-       if PanicOnWarning.Load() {
-               panic(panicOnWarningMessage)
-       }
-}
-
-func (l *logger) Warnln(v ...any) {
-       l.WARN.Println(v...)
-       if PanicOnWarning.Load() {
-               panic(panicOnWarningMessage)
-       }
-}
-
-func (l *logger) Warn() *log.Logger {
-       return l.WARN
-}
-
-func (l *logger) Errorf(format string, v ...any) {
-       l.ERROR.Printf(format, v...)
-}
-
-func (l *logger) Errorln(v ...any) {
-       l.ERROR.Println(v...)
-}
-
-func (l *logger) Error() *log.Logger {
-       return l.ERROR
-}
-
-func (l *logger) LogCounters() *LogCounters {
-       return l.logCounters
-}
-
-func (l *logger) Out() io.Writer {
-       return l.out
-}
-
-// PrintTimerIfDelayed prints a time statement to the FEEDBACK logger
-// if considerable time is spent.
-func (l *logger) PrintTimerIfDelayed(start time.Time, name string) {
-       elapsed := time.Since(start)
-       milli := int(1000 * elapsed.Seconds())
-       if milli < 500 {
-               return
-       }
-       l.Printf("%s in %v ms", name, milli)
-}
-
-func (l *logger) PrintTimer(start time.Time, name string) {
-       elapsed := time.Since(start)
-       milli := int(1000 * elapsed.Seconds())
-       l.Printf("%s in %v ms", name, milli)
-}
-
-func (l *logger) Errors() string {
-       if l.errors == nil {
-               return ""
-       }
-       return ansiColorRe.ReplaceAllString(l.errors.String(), "")
-}
-
-// Reset resets the logger's internal state.
-func (l *logger) Reset() {
-       l.logCounters.ErrorCounter.Reset()
-       if l.errors != nil {
-               l.errors.Reset()
-       }
-}
-
-// NewLogger creates a new Logger for the given thresholds
-func NewLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) Logger {
-       return newLogger(stdoutThreshold, logThreshold, outHandle, logHandle, saveErrors)
-}
-
-// NewDebugLogger is a convenience function to create a debug logger.
-func NewDebugLogger() Logger {
-       return NewBasicLogger(jww.LevelDebug)
-}
-
-// NewWarningLogger is a convenience function to create a warning logger.
-func NewWarningLogger() Logger {
-       return NewBasicLogger(jww.LevelWarn)
-}
-
-// NewInfoLogger is a convenience function to create a info logger.
-func NewInfoLogger() Logger {
-       return NewBasicLogger(jww.LevelInfo)
-}
-
-// NewErrorLogger is a convenience function to create an error logger.
-func NewErrorLogger() Logger {
-       return NewBasicLogger(jww.LevelError)
-}
-
-// NewBasicLogger creates a new basic logger writing to Stdout.
-func NewBasicLogger(t jww.Threshold) Logger {
-       return newLogger(t, jww.LevelError, os.Stdout, io.Discard, false)
-}
-
-// NewBasicLoggerForWriter creates a new basic logger writing to w.
-func NewBasicLoggerForWriter(t jww.Threshold, w io.Writer) Logger {
-       return newLogger(t, jww.LevelError, w, io.Discard, false)
-}
-
-// RemoveANSIColours removes all ANSI colours from the given string.
-func RemoveANSIColours(s string) string {
-       return ansiColorRe.ReplaceAllString(s, "")
-}
-
-var (
-       ansiColorRe = regexp.MustCompile("(?s)\\033\\[\\d*(;\\d*)*m")
-       errorRe     = regexp.MustCompile("^(ERROR|FATAL|WARN)")
-)
-
-type ansiCleaner struct {
-       w io.Writer
-}
-
-func (a ansiCleaner) Write(p []byte) (n int, err error) {
-       return a.w.Write(ansiColorRe.ReplaceAll(p, []byte("")))
-}
-
-type labelColorizer struct {
-       w io.Writer
-}
-
-func (a labelColorizer) Write(p []byte) (n int, err error) {
-       replaced := errorRe.ReplaceAllStringFunc(string(p), func(m string) string {
-               switch m {
-               case "ERROR", "FATAL":
-                       return terminal.Error(m)
-               case "WARN":
-                       return terminal.Warning(m)
-               default:
-                       return m
-               }
-       })
-       // io.MultiWriter will abort if we return a bigger write count than input
-       // bytes, so we lie a little.
-       _, err = a.w.Write([]byte(replaced))
-       return len(p), err
-}
-
-// InitGlobalLogger initializes the global logger, used in some rare cases.
-func InitGlobalLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer) {
-       outHandle, logHandle = getLogWriters(outHandle, logHandle)
-
-       jww.SetStdoutOutput(outHandle)
-       jww.SetLogOutput(logHandle)
-       jww.SetLogThreshold(logThreshold)
-       jww.SetStdoutThreshold(stdoutThreshold)
-}
-
-func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) {
-       isTerm := terminal.PrintANSIColors(os.Stdout)
-       if logHandle != io.Discard && isTerm {
-               // Remove any Ansi coloring from log output
-               logHandle = ansiCleaner{w: logHandle}
-       }
-
-       if isTerm {
-               outHandle = labelColorizer{w: outHandle}
-       }
-
-       return outHandle, logHandle
-}
-
-type fatalLogWriter int
-
-func (s fatalLogWriter) Write(p []byte) (n int, err error) {
-       trace := make([]byte, 1500)
-       runtime.Stack(trace, true)
-       fmt.Printf("\n===========\n\n%s\n", trace)
-       os.Exit(-1)
-
-       return 0, nil
-}
-
-var fatalLogListener = func(t jww.Threshold) io.Writer {
-       if t != jww.LevelError {
-               // Only interested in ERROR
-               return nil
-       }
-
-       return new(fatalLogWriter)
-}
-
-func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *logger {
-       errorCounter := &jww.Counter{}
-       warnCounter := &jww.Counter{}
-       outHandle, logHandle = getLogWriters(outHandle, logHandle)
-
-       listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError), jww.LogCounter(warnCounter, jww.LevelWarn)}
-       var errorBuff *bytes.Buffer
-       if saveErrors {
-               errorBuff = new(bytes.Buffer)
-               errorCapture := func(t jww.Threshold) io.Writer {
-                       if t != jww.LevelError {
-                               // Only interested in ERROR
-                               return nil
-                       }
-                       return errorBuff
-               }
-
-               listeners = append(listeners, errorCapture)
-       }
-
-       return &logger{
-               Notepad: jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime, listeners...),
-               out:     outHandle,
-               logCounters: &LogCounters{
-                       ErrorCounter: errorCounter,
-                       WarnCounter:  warnCounter,
-               },
-               errors: errorBuff,
-       }
-}
diff --git a/common/loggers/loggers_test.go b/common/loggers/loggers_test.go
deleted file mode 100644 (file)
index a7bd1ae..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright 2018 The Hugo Authors. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package loggers
-
-import (
-       "bytes"
-       "fmt"
-       "log"
-       "testing"
-
-       qt "github.com/frankban/quicktest"
-)
-
-func TestLogger(t *testing.T) {
-       c := qt.New(t)
-       l := NewWarningLogger()
-
-       l.Errorln("One error")
-       l.Errorln("Two error")
-       l.Warnln("A warning")
-
-       c.Assert(l.LogCounters().ErrorCounter.Count(), qt.Equals, uint64(2))
-}
-
-func TestLoggerToWriterWithPrefix(t *testing.T) {
-       c := qt.New(t)
-
-       var b bytes.Buffer
-
-       logger := log.New(&b, "", 0)
-
-       w := LoggerToWriterWithPrefix(logger, "myprefix")
-
-       fmt.Fprint(w, "Hello Hugo!")
-
-       c.Assert(b.String(), qt.Equals, "myprefix: Hello Hugo!\n")
-}
-
-func TestRemoveANSIColours(t *testing.T) {
-       c := qt.New(t)
-
-       c.Assert(RemoveANSIColours(""), qt.Equals, "")
-       c.Assert(RemoveANSIColours("\033[31m"), qt.Equals, "")
-       c.Assert(RemoveANSIColours("\033[31mHello"), qt.Equals, "Hello")
-       c.Assert(RemoveANSIColours("\033[31mHello\033[0m"), qt.Equals, "Hello")
-       c.Assert(RemoveANSIColours("\033[31mHello\033[0m World"), qt.Equals, "Hello World")
-       c.Assert(RemoveANSIColours("\033[31mHello\033[0m World\033[31m!"), qt.Equals, "Hello World!")
-       c.Assert(RemoveANSIColours("\x1b[90m 5 |"), qt.Equals, " 5 |")
-}
index 08792d870d9df9870c5af6deb935746acb096ff7..ec7895eea1ef519fc52bae8d109e594c22e15e11 100644 (file)
@@ -490,6 +490,9 @@ type RootConfig struct {
        // ENable to print warnings for multiple files published to the same destination.
        LogPathWarnings bool
 
+       // Enable to panic on warning log entries. This may make it easier to detect the source.
+       PanicOnWarning bool
+
        // The configured environment. Default is "development" for server and "production" for build.
        Environment string
 
index eca9d06dfa9a859be2726c5beadaa20962ea899a..4e5478c408d1ea6af4cfec39c606f452c610f2e0 100644 (file)
@@ -46,7 +46,7 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
        }
 
        if d.Logger == nil {
-               d.Logger = loggers.NewErrorLogger()
+               d.Logger = loggers.NewDefault()
        }
 
        l := &configLoader{ConfigSourceDescriptor: d, cfg: config.New()}
@@ -90,8 +90,9 @@ func LoadConfig(d ConfigSourceDescriptor) (*Configs, error) {
                return nil, fmt.Errorf("failed to init config: %w", err)
        }
 
-       // This is unfortunate, but this is a global setting.
+       // This is unfortunate, but these are global settings.
        tpl.SetSecurityAllowActionJSTmpl(configs.Base.Security.GoTemplates.AllowActionJSTmpl)
+       loggers.InitGlobalLogger(configs.Base.PanicOnWarning)
 
        return configs, nil
 
index bd3e235bdfc37237a340d99ab5668eb4aef382f4..ac1dd39fab0e5cd01adeead8b1b97ab8ee764a0d 100644 (file)
@@ -19,6 +19,7 @@ import (
        "sort"
        "strings"
 
+       "github.com/bep/logg"
        "github.com/gobwas/glob"
        "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/common/types"
@@ -306,6 +307,7 @@ func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
        if c.compiledSource != nil {
                return nil
        }
+
        source := c.Source
        target := c.Target
        sourceRe, err := regexp.Compile(source)
@@ -313,6 +315,8 @@ func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
                return fmt.Errorf("failed to compile cache buster source %q: %w", c.Source, err)
        }
        var compileErr error
+       debugl := logger.Logger().WithLevel(logg.LevelDebug).WithField(loggers.FieldNameCmd, "cachebuster")
+
        c.compiledSource = func(s string) func(string) bool {
                m := sourceRe.FindStringSubmatch(s)
                matchString := "no match"
@@ -320,7 +324,7 @@ func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
                if match {
                        matchString = "match!"
                }
-               logger.Debugf("cachebuster: Matching %q with source %q: %s\n", s, source, matchString)
+               debugl.Logf("Matching %q with source %q: %s", s, source, matchString)
                if !match {
                        return nil
                }
@@ -341,7 +345,7 @@ func (c *CacheBuster) CompileConfig(logger loggers.Logger) error {
                        if match {
                                matchString = "match!"
                        }
-                       logger.Debugf("cachebuster: Matching %q with target %q: %s\n", s, target, matchString)
+                       logger.Debugf("Matching %q with target %q: %s", s, target, matchString)
 
                        return match
                }
index 106069bdcef0ef36d18198660dd6175852532a51..b8130eb0ddb19f9a82c1eab55b762bd7760c778b 100644 (file)
@@ -92,7 +92,7 @@ status = 301
 
        s, err := DecodeServer(cfg)
        c.Assert(err, qt.IsNil)
-       c.Assert(s.CompileConfig(loggers.NewErrorLogger()), qt.IsNil)
+       c.Assert(s.CompileConfig(loggers.NewDefault()), qt.IsNil)
 
        c.Assert(s.MatchHeaders("/foo.jpg"), qt.DeepEquals, []types.KeyValueStr{
                {Key: "X-Content-Type-Options", Value: "nosniff"},
@@ -145,7 +145,7 @@ func TestBuildConfigCacheBusters(t *testing.T) {
        c := qt.New(t)
        cfg := New()
        conf := DecodeBuildConfig(cfg)
-       l := loggers.NewInfoLogger()
+       l := loggers.NewDefault()
        c.Assert(conf.CompileConfig(l), qt.IsNil)
 
        m, err := conf.MatchCacheBuster(l, "assets/foo/main.js")
index db88996a9c18e46bef9b6b5a30ce271776d3e3fd..60a3da36382c77a11fbead4c5bbed22a46143d30 100644 (file)
@@ -37,10 +37,10 @@ import (
 
        "github.com/dustin/go-humanize"
        "github.com/gobwas/glob"
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/media"
        "github.com/spf13/afero"
-       jww "github.com/spf13/jwalterweatherman"
        "golang.org/x/text/unicode/norm"
 
        "gocloud.dev/blob"
@@ -56,9 +56,10 @@ type Deployer struct {
        bucket  *blob.Bucket
 
        mediaTypes media.Types // Hugo's MediaType to guess ContentType
-       quiet      bool        // true reduces STDOUT
+       quiet      bool        // true reduces STDOUT // TODO(bep) remove, this is a global feature.
 
-       cfg DeployConfig
+       cfg    DeployConfig
+       logger loggers.Logger
 
        target *Target // the target to deploy to
 
@@ -73,7 +74,7 @@ type deploySummary struct {
 const metaMD5Hash = "md5chksum" // the meta key to store md5hash in
 
 // New constructs a new *Deployer.
-func New(cfg config.AllProvider, localFs afero.Fs) (*Deployer, error) {
+func New(cfg config.AllProvider, logger loggers.Logger, localFs afero.Fs) (*Deployer, error) {
 
        dcfg := cfg.GetConfigSection(deploymentConfigKey).(DeployConfig)
        targetName := dcfg.Target
@@ -112,12 +113,16 @@ func (d *Deployer) openBucket(ctx context.Context) (*blob.Bucket, error) {
        if d.bucket != nil {
                return d.bucket, nil
        }
-       jww.FEEDBACK.Printf("Deploying to target %q (%s)\n", d.target.Name, d.target.URL)
+       d.logger.Printf("Deploying to target %q (%s)\n", d.target.Name, d.target.URL)
        return blob.OpenBucket(ctx, d.target.URL)
 }
 
 // Deploy deploys the site to a target.
 func (d *Deployer) Deploy(ctx context.Context) error {
+       if d.logger == nil {
+               d.logger = loggers.NewDefault()
+       }
+
        bucket, err := d.openBucket(ctx)
        if err != nil {
                return err
@@ -132,33 +137,33 @@ func (d *Deployer) Deploy(ctx context.Context) error {
        if d.target != nil {
                include, exclude = d.target.includeGlob, d.target.excludeGlob
        }
-       local, err := walkLocal(d.localFs, d.cfg.Matchers, include, exclude, d.mediaTypes)
+       local, err := d.walkLocal(d.localFs, d.cfg.Matchers, include, exclude, d.mediaTypes)
        if err != nil {
                return err
        }
-       jww.INFO.Printf("Found %d local files.\n", len(local))
+       d.logger.Infof("Found %d local files.\n", len(local))
        d.summary.NumLocal = len(local)
 
        // Load remote files from the target.
-       remote, err := walkRemote(ctx, bucket, include, exclude)
+       remote, err := d.walkRemote(ctx, bucket, include, exclude)
        if err != nil {
                return err
        }
-       jww.INFO.Printf("Found %d remote files.\n", len(remote))
+       d.logger.Infof("Found %d remote files.\n", len(remote))
        d.summary.NumRemote = len(remote)
 
        // Diff local vs remote to see what changes need to be applied.
-       uploads, deletes := findDiffs(local, remote, d.cfg.Force)
+       uploads, deletes := d.findDiffs(local, remote, d.cfg.Force)
        d.summary.NumUploads = len(uploads)
        d.summary.NumDeletes = len(deletes)
        if len(uploads)+len(deletes) == 0 {
                if !d.quiet {
-                       jww.FEEDBACK.Println("No changes required.")
+                       d.logger.Println("No changes required.")
                }
                return nil
        }
        if !d.quiet {
-               jww.FEEDBACK.Println(summarizeChanges(uploads, deletes))
+               d.logger.Println(summarizeChanges(uploads, deletes))
        }
 
        // Ask for confirmation before proceeding.
@@ -192,14 +197,14 @@ func (d *Deployer) Deploy(ctx context.Context) error {
                for _, upload := range uploads {
                        if d.cfg.DryRun {
                                if !d.quiet {
-                                       jww.FEEDBACK.Printf("[DRY RUN] Would upload: %v\n", upload)
+                                       d.logger.Printf("[DRY RUN] Would upload: %v\n", upload)
                                }
                                continue
                        }
 
                        sem <- struct{}{}
                        go func(upload *fileToUpload) {
-                               if err := doSingleUpload(ctx, bucket, upload); err != nil {
+                               if err := d.doSingleUpload(ctx, bucket, upload); err != nil {
                                        errMu.Lock()
                                        defer errMu.Unlock()
                                        errs = append(errs, err)
@@ -214,7 +219,7 @@ func (d *Deployer) Deploy(ctx context.Context) error {
        }
 
        if d.cfg.MaxDeletes != -1 && len(deletes) > d.cfg.MaxDeletes {
-               jww.WARN.Printf("Skipping %d deletes because it is more than --maxDeletes (%d). If this is expected, set --maxDeletes to a larger number, or -1 to disable this check.\n", len(deletes), d.cfg.MaxDeletes)
+               d.logger.Warnf("Skipping %d deletes because it is more than --maxDeletes (%d). If this is expected, set --maxDeletes to a larger number, or -1 to disable this check.\n", len(deletes), d.cfg.MaxDeletes)
                d.summary.NumDeletes = 0
        } else {
                // Apply deletes in parallel.
@@ -223,16 +228,16 @@ func (d *Deployer) Deploy(ctx context.Context) error {
                for _, del := range deletes {
                        if d.cfg.DryRun {
                                if !d.quiet {
-                                       jww.FEEDBACK.Printf("[DRY RUN] Would delete %s\n", del)
+                                       d.logger.Printf("[DRY RUN] Would delete %s\n", del)
                                }
                                continue
                        }
                        sem <- struct{}{}
                        go func(del string) {
-                               jww.INFO.Printf("Deleting %s...\n", del)
+                               d.logger.Infof("Deleting %s...\n", del)
                                if err := bucket.Delete(ctx, del); err != nil {
                                        if gcerrors.Code(err) == gcerrors.NotFound {
-                                               jww.WARN.Printf("Failed to delete %q because it wasn't found: %v", del, err)
+                                               d.logger.Warnf("Failed to delete %q because it wasn't found: %v", del, err)
                                        } else {
                                                errMu.Lock()
                                                defer errMu.Unlock()
@@ -250,24 +255,24 @@ func (d *Deployer) Deploy(ctx context.Context) error {
 
        if len(errs) > 0 {
                if !d.quiet {
-                       jww.FEEDBACK.Printf("Encountered %d errors.\n", len(errs))
+                       d.logger.Printf("Encountered %d errors.\n", len(errs))
                }
                return errs[0]
        }
        if !d.quiet {
-               jww.FEEDBACK.Println("Success!")
+               d.logger.Println("Success!")
        }
 
        if d.cfg.InvalidateCDN {
                if d.target.CloudFrontDistributionID != "" {
                        if d.cfg.DryRun {
                                if !d.quiet {
-                                       jww.FEEDBACK.Printf("[DRY RUN] Would invalidate CloudFront CDN with ID %s\n", d.target.CloudFrontDistributionID)
+                                       d.logger.Printf("[DRY RUN] Would invalidate CloudFront CDN with ID %s\n", d.target.CloudFrontDistributionID)
                                }
                        } else {
-                               jww.FEEDBACK.Println("Invalidating CloudFront CDN...")
+                               d.logger.Println("Invalidating CloudFront CDN...")
                                if err := InvalidateCloudFront(ctx, d.target.CloudFrontDistributionID); err != nil {
-                                       jww.FEEDBACK.Printf("Failed to invalidate CloudFront CDN: %v\n", err)
+                                       d.logger.Printf("Failed to invalidate CloudFront CDN: %v\n", err)
                                        return err
                                }
                        }
@@ -275,17 +280,17 @@ func (d *Deployer) Deploy(ctx context.Context) error {
                if d.target.GoogleCloudCDNOrigin != "" {
                        if d.cfg.DryRun {
                                if !d.quiet {
-                                       jww.FEEDBACK.Printf("[DRY RUN] Would invalidate Google Cloud CDN with origin %s\n", d.target.GoogleCloudCDNOrigin)
+                                       d.logger.Printf("[DRY RUN] Would invalidate Google Cloud CDN with origin %s\n", d.target.GoogleCloudCDNOrigin)
                                }
                        } else {
-                               jww.FEEDBACK.Println("Invalidating Google Cloud CDN...")
+                               d.logger.Println("Invalidating Google Cloud CDN...")
                                if err := InvalidateGoogleCloudCDN(ctx, d.target.GoogleCloudCDNOrigin); err != nil {
-                                       jww.FEEDBACK.Printf("Failed to invalidate Google Cloud CDN: %v\n", err)
+                                       d.logger.Printf("Failed to invalidate Google Cloud CDN: %v\n", err)
                                        return err
                                }
                        }
                }
-               jww.FEEDBACK.Println("Success!")
+               d.logger.Println("Success!")
        }
        return nil
 }
@@ -300,8 +305,8 @@ func summarizeChanges(uploads []*fileToUpload, deletes []string) string {
 }
 
 // doSingleUpload executes a single file upload.
-func doSingleUpload(ctx context.Context, bucket *blob.Bucket, upload *fileToUpload) error {
-       jww.INFO.Printf("Uploading %v...\n", upload)
+func (d *Deployer) doSingleUpload(ctx context.Context, bucket *blob.Bucket, upload *fileToUpload) error {
+       d.logger.Infof("Uploading %v...\n", upload)
        opts := &blob.WriterOptions{
                CacheControl:    upload.Local.CacheControl(),
                ContentEncoding: upload.Local.ContentEncoding(),
@@ -479,7 +484,7 @@ func knownHiddenDirectory(name string) bool {
 
 // walkLocal walks the source directory and returns a flat list of files,
 // using localFile.SlashPath as the map keys.
-func walkLocal(fs afero.Fs, matchers []*Matcher, include, exclude glob.Glob, mediaTypes media.Types) (map[string]*localFile, error) {
+func (d *Deployer) walkLocal(fs afero.Fs, matchers []*Matcher, include, exclude glob.Glob, mediaTypes media.Types) (map[string]*localFile, error) {
        retval := map[string]*localFile{}
        err := afero.Walk(fs, "", func(path string, info os.FileInfo, err error) error {
                if err != nil {
@@ -509,11 +514,11 @@ func walkLocal(fs afero.Fs, matchers []*Matcher, include, exclude glob.Glob, med
                // Check include/exclude matchers.
                slashpath := filepath.ToSlash(path)
                if include != nil && !include.Match(slashpath) {
-                       jww.INFO.Printf("  dropping %q due to include\n", slashpath)
+                       d.logger.Infof("  dropping %q due to include\n", slashpath)
                        return nil
                }
                if exclude != nil && exclude.Match(slashpath) {
-                       jww.INFO.Printf("  dropping %q due to exclude\n", slashpath)
+                       d.logger.Infof("  dropping %q due to exclude\n", slashpath)
                        return nil
                }
 
@@ -539,7 +544,7 @@ func walkLocal(fs afero.Fs, matchers []*Matcher, include, exclude glob.Glob, med
 }
 
 // walkRemote walks the target bucket and returns a flat list.
-func walkRemote(ctx context.Context, bucket *blob.Bucket, include, exclude glob.Glob) (map[string]*blob.ListObject, error) {
+func (d *Deployer) walkRemote(ctx context.Context, bucket *blob.Bucket, include, exclude glob.Glob) (map[string]*blob.ListObject, error) {
        retval := map[string]*blob.ListObject{}
        iter := bucket.List(nil)
        for {
@@ -552,11 +557,11 @@ func walkRemote(ctx context.Context, bucket *blob.Bucket, include, exclude glob.
                }
                // Check include/exclude matchers.
                if include != nil && !include.Match(obj.Key) {
-                       jww.INFO.Printf("  remote dropping %q due to include\n", obj.Key)
+                       d.logger.Infof("  remote dropping %q due to include\n", obj.Key)
                        continue
                }
                if exclude != nil && exclude.Match(obj.Key) {
-                       jww.INFO.Printf("  remote dropping %q due to exclude\n", obj.Key)
+                       d.logger.Infof("  remote dropping %q due to exclude\n", obj.Key)
                        continue
                }
                // If the remote didn't give us an MD5, use remote attributes MD5, if that doesn't exist compute one.
@@ -629,7 +634,7 @@ func (u *fileToUpload) String() string {
 // findDiffs diffs localFiles vs remoteFiles to see what changes should be
 // applied to the remote target. It returns a slice of *fileToUpload and a
 // slice of paths for files to delete.
-func findDiffs(localFiles map[string]*localFile, remoteFiles map[string]*blob.ListObject, force bool) ([]*fileToUpload, []string) {
+func (d *Deployer) findDiffs(localFiles map[string]*localFile, remoteFiles map[string]*blob.ListObject, force bool) ([]*fileToUpload, []string) {
        var uploads []*fileToUpload
        var deletes []string
 
@@ -680,10 +685,10 @@ func findDiffs(localFiles map[string]*localFile, remoteFiles map[string]*blob.Li
                        reason = reasonNotFound
                }
                if upload {
-                       jww.DEBUG.Printf("%s needs to be uploaded: %v\n", path, reason)
+                       d.logger.Debugf("%s needs to be uploaded: %v\n", path, reason)
                        uploads = append(uploads, &fileToUpload{lf, reason})
                } else {
-                       jww.DEBUG.Printf("%s exists at target and does not need to be uploaded", path)
+                       d.logger.Debugf("%s exists at target and does not need to be uploaded", path)
                }
        }
 
index fe874fbbd225d492b92c3e00264c516c94c194a2..66eece10b18c3a2d4f16695d655ba7f428e860e3 100644 (file)
@@ -30,6 +30,7 @@ import (
        "sort"
        "testing"
 
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/media"
        "github.com/google/go-cmp/cmp"
        "github.com/google/go-cmp/cmp/cmpopts"
@@ -197,7 +198,8 @@ func TestFindDiffs(t *testing.T) {
                        for _, r := range tc.Remote {
                                remote[r.Key] = r
                        }
-                       gotUpdates, gotDeletes := findDiffs(local, remote, tc.Force)
+                       d := newDeployer()
+                       gotUpdates, gotDeletes := d.findDiffs(local, remote, tc.Force)
                        gotUpdates = applyOrdering(nil, gotUpdates)[0]
                        sort.Slice(gotDeletes, func(i, j int) bool { return gotDeletes[i] < gotDeletes[j] })
                        if diff := cmp.Diff(gotUpdates, tc.WantUpdates, cmpopts.IgnoreUnexported(localFile{})); diff != "" {
@@ -249,7 +251,8 @@ func TestWalkLocal(t *testing.T) {
                                        fd.Close()
                                }
                        }
-                       if got, err := walkLocal(fs, nil, nil, nil, media.DefaultTypes); err != nil {
+                       d := newDeployer()
+                       if got, err := d.walkLocal(fs, nil, nil, nil, media.DefaultTypes); err != nil {
                                t.Fatal(err)
                        } else {
                                expect := map[string]any{}
@@ -1026,3 +1029,9 @@ func verifyRemote(ctx context.Context, bucket *blob.Bucket, local []*fileData) (
        }
        return diff, nil
 }
+
+func newDeployer() *Deployer {
+       return &Deployer{
+               logger: loggers.NewDefault(),
+       }
+}
index 39462de96fe773ecaa8f8400d6dc69a0b1899ea5..309555080a30fa85c8e6b2e077bf0ddba97eaa85 100644 (file)
@@ -3,12 +3,14 @@ package deps
 import (
        "context"
        "fmt"
+       "io"
        "path/filepath"
        "sort"
        "strings"
        "sync"
        "sync/atomic"
 
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/common/hexec"
        "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/config"
@@ -25,7 +27,6 @@ import (
        "github.com/gohugoio/hugo/source"
        "github.com/gohugoio/hugo/tpl"
        "github.com/spf13/afero"
-       jww "github.com/spf13/jwalterweatherman"
 )
 
 // Deps holds dependencies used by many.
@@ -36,9 +37,6 @@ type Deps struct {
        // The logger to use.
        Log loggers.Logger `json:"-"`
 
-       // Used to log errors that may repeat itself many times.
-       LogDistinct loggers.Logger
-
        ExecHelper *hexec.Exec
 
        // The templates to use. This will usually implement the full tpl.TemplateManager.
@@ -117,15 +115,13 @@ func (d *Deps) Init() error {
        }
 
        if d.Log == nil {
-               d.Log = loggers.NewErrorLogger()
-       }
-
-       if d.LogDistinct == nil {
-               d.LogDistinct = helpers.NewDistinctLogger(d.Log)
+               d.Log = loggers.NewDefault()
        }
 
        if d.globalErrHandler == nil {
-               d.globalErrHandler = &globalErrHandler{}
+               d.globalErrHandler = &globalErrHandler{
+                       logger: d.Log,
+               }
        }
 
        if d.BuildState == nil {
@@ -228,6 +224,8 @@ func (d *Deps) Compile(prototype *Deps) error {
 }
 
 type globalErrHandler struct {
+       logger loggers.Logger
+
        // Channel for some "hard to get to" build errors
        buildErrors chan error
        // Used to signal that the build is done.
@@ -246,8 +244,7 @@ func (e *globalErrHandler) SendError(err error) {
                }
                return
        }
-
-       jww.ERROR.Println(err)
+       e.logger.Errorln(err)
 }
 
 func (e *globalErrHandler) StartErrorCollector() chan error {
@@ -312,9 +309,16 @@ func (d *Deps) Close() error {
 // on a global level, i.e. logging etc.
 // Nil values will be given default values.
 type DepsCfg struct {
+       // The logger to use. Only set in some tests.
+       // TODO(bep) get rid of this.
+       TestLogger loggers.Logger
+
+       // The logging level to use.
+       LogLevel logg.Level
 
-       // The Logger to use.
-       Logger loggers.Logger
+       // Where to write the logs.
+       // Currently we typically write everything to stdout.
+       LogOut io.Writer
 
        // The file systems to use
        Fs *hugofs.Fs
diff --git a/go.mod b/go.mod
index 6371423e0f0909602cde185c21666714c1110067..92939a0867b7fa59cc9a68cec4fdfcc63cec0a0f 100644 (file)
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
        github.com/bep/gowebp v0.2.0
        github.com/bep/helpers v0.4.0
        github.com/bep/lazycache v0.2.0
+       github.com/bep/logg v0.2.0
        github.com/bep/mclib v1.20400.20402
        github.com/bep/overlayfs v0.6.0
        github.com/bep/simplecobra v0.3.2
@@ -25,6 +26,7 @@ require (
        github.com/disintegration/gift v1.2.1
        github.com/dustin/go-humanize v1.0.1
        github.com/evanw/esbuild v0.18.3
+       github.com/fatih/color v1.15.0
        github.com/fortytw2/leaktest v1.3.0
        github.com/frankban/quicktest v1.14.5
        github.com/fsnotify/fsnotify v1.6.0
@@ -58,7 +60,6 @@ require (
        github.com/spf13/cast v1.5.1
        github.com/spf13/cobra v1.7.0
        github.com/spf13/fsync v0.9.0
-       github.com/spf13/jwalterweatherman v1.1.0
        github.com/spf13/pflag v1.0.5
        github.com/tdewolff/minify/v2 v2.12.6
        github.com/tdewolff/parse/v2 v2.6.6
@@ -109,6 +110,7 @@ require (
        github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.1 // indirect
        github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect
        github.com/aws/smithy-go v1.13.5 // indirect
+       github.com/bep/clocks v0.5.0 // indirect
        github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
        github.com/dlclark/regexp2 v1.10.0 // indirect
        github.com/go-openapi/jsonpointer v0.19.5 // indirect
@@ -129,11 +131,13 @@ require (
        github.com/kr/pretty v0.3.1 // indirect
        github.com/kr/text v0.2.0 // indirect
        github.com/mailru/easyjson v0.7.7 // indirect
+       github.com/mattn/go-colorable v0.1.13 // indirect
        github.com/mattn/go-runewidth v0.0.9 // indirect
        github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
        github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
        github.com/perimeterx/marshmallow v1.1.4 // indirect
        github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
+       github.com/pkg/errors v0.9.1 // indirect
        github.com/russross/blackfriday/v2 v2.1.0 // indirect
        go.opencensus.io v0.24.0 // indirect
        golang.org/x/crypto v0.10.0 // indirect
diff --git a/go.sum b/go.sum
index 7ba2c773bedb14edb6d6f0ccd8f3cd9ce6830a34..ffadc9c48a8e0904839001366638ddec0be0bb60 100644 (file)
--- a/go.sum
+++ b/go.sum
@@ -612,6 +612,8 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bep/clock v0.3.0 h1:vfOA6+wVb6pPQEiXow9f/too92vNTLe9MuwO13PfI0M=
 github.com/bep/clock v0.3.0/go.mod h1:6Gz2lapnJ9vxpvPxQ2u6FcXFRoj4kkiqQ6pm0ERZlwk=
+github.com/bep/clocks v0.5.0 h1:hhvKVGLPQWRVsBP/UB7ErrHYIO42gINVbvqxvYTPVps=
+github.com/bep/clocks v0.5.0/go.mod h1:SUq3q+OOq41y2lRQqH5fsOoxN8GbxSiT6jvoVVLCVhU=
 github.com/bep/debounce v1.2.0 h1:wXds8Kq8qRfwAOpAxHrJDbCXgC5aHSzgQb/0gKsHQqo=
 github.com/bep/debounce v1.2.0/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
 github.com/bep/gitmap v1.1.2 h1:zk04w1qc1COTZPPYWDQHvns3y1afOsdRfraFQ3qI840=
@@ -630,6 +632,8 @@ github.com/bep/helpers v0.4.0 h1:ab9veaAiWY4ST48Oxp5usaqivDmYdB744fz+tcZ3Ifs=
 github.com/bep/helpers v0.4.0/go.mod h1:/QpHdmcPagDw7+RjkLFCvnlUc8lQ5kg4KDrEkb2Yyco=
 github.com/bep/lazycache v0.2.0 h1:HKrlZTrDxHIrNKqmnurH42ryxkngCMYLfBpyu40VcwY=
 github.com/bep/lazycache v0.2.0/go.mod h1:xUIsoRD824Vx0Q/n57+ZO7kmbEhMBOnTjM/iPixNGbg=
+github.com/bep/logg v0.2.0 h1:EWKB04ea/K/V0xd/7O6x5q+1l+Grub+9N48YMcevtF4=
+github.com/bep/logg v0.2.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0=
 github.com/bep/mclib v1.20400.20402 h1:olpCE2WSPpOAbFE1R4hnftSEmQ34+xzy2HRzd0m69rA=
 github.com/bep/mclib v1.20400.20402/go.mod h1:pkrk9Kyfqg34Uj6XlDq9tdEFJBiL1FvCoCgVKRzw1EY=
 github.com/bep/overlayfs v0.6.0 h1:sgLcq/qtIzbaQNl2TldGXOkHvqeZB025sPvHOQL+DYo=
@@ -916,6 +920,8 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
 github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
 github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
 github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
+github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
+github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
@@ -1496,6 +1502,8 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
 github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
 github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
@@ -1505,6 +1513,7 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME
 github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
 github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
 github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
@@ -1692,6 +1701,7 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
 github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
@@ -1840,7 +1850,6 @@ github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRM
 github.com/spf13/fsync v0.9.0 h1:f9CEt3DOB2mnHxZaftmEOFWjABEvKM/xpf3cUwJrGOY=
 github.com/spf13/fsync v0.9.0/go.mod h1:fNtJEfG3HiltN3y4cPOz6MLjos9+2pIEqLIgszqhp/0=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
-github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
 github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -2441,6 +2450,7 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220731174439-a90be440212d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
index 50f7920f6b578b2ef94ac68f51592ff90a666f4c..d1df2e335a52cc75b54dd0b2214f9a45ffd74db6 100644 (file)
@@ -24,13 +24,11 @@ import (
        "path/filepath"
        "sort"
        "strings"
-       "sync"
        "unicode"
        "unicode/utf8"
 
-       "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/gohugoio/hugo/common/hugo"
+       "github.com/gohugoio/hugo/common/loggers"
 
        "github.com/spf13/afero"
 
@@ -254,143 +252,6 @@ func compareStringSlices(a, b []string) bool {
        return true
 }
 
-// DistinctLogger ignores duplicate log statements.
-type DistinctLogger struct {
-       loggers.Logger
-       sync.RWMutex
-       m map[string]bool
-}
-
-func (l *DistinctLogger) Reset() {
-       l.Lock()
-       defer l.Unlock()
-
-       l.m = make(map[string]bool)
-}
-
-// Println will log the string returned from fmt.Sprintln given the arguments,
-// but not if it has been logged before.
-func (l *DistinctLogger) Println(v ...any) {
-       // fmt.Sprint doesn't add space between string arguments
-       logStatement := strings.TrimSpace(fmt.Sprintln(v...))
-       l.printIfNotPrinted("println", logStatement, func() {
-               l.Logger.Println(logStatement)
-       })
-}
-
-// Printf will log the string returned from fmt.Sprintf given the arguments,
-// but not if it has been logged before.
-func (l *DistinctLogger) Printf(format string, v ...any) {
-       logStatement := fmt.Sprintf(format, v...)
-       l.printIfNotPrinted("printf", logStatement, func() {
-               l.Logger.Printf(format, v...)
-       })
-}
-
-func (l *DistinctLogger) Debugf(format string, v ...any) {
-       logStatement := fmt.Sprintf(format, v...)
-       l.printIfNotPrinted("debugf", logStatement, func() {
-               l.Logger.Debugf(format, v...)
-       })
-}
-
-func (l *DistinctLogger) Debugln(v ...any) {
-       logStatement := fmt.Sprint(v...)
-       l.printIfNotPrinted("debugln", logStatement, func() {
-               l.Logger.Debugln(v...)
-       })
-}
-
-func (l *DistinctLogger) Infof(format string, v ...any) {
-       logStatement := fmt.Sprintf(format, v...)
-       l.printIfNotPrinted("info", logStatement, func() {
-               l.Logger.Infof(format, v...)
-       })
-}
-
-func (l *DistinctLogger) Infoln(v ...any) {
-       logStatement := fmt.Sprint(v...)
-       l.printIfNotPrinted("infoln", logStatement, func() {
-               l.Logger.Infoln(v...)
-       })
-}
-
-func (l *DistinctLogger) Warnf(format string, v ...any) {
-       logStatement := fmt.Sprintf(format, v...)
-       l.printIfNotPrinted("warnf", logStatement, func() {
-               l.Logger.Warnf(format, v...)
-       })
-}
-
-func (l *DistinctLogger) Warnln(v ...any) {
-       logStatement := fmt.Sprint(v...)
-       l.printIfNotPrinted("warnln", logStatement, func() {
-               l.Logger.Warnln(v...)
-       })
-}
-
-func (l *DistinctLogger) Errorf(format string, v ...any) {
-       logStatement := fmt.Sprint(v...)
-       l.printIfNotPrinted("errorf", logStatement, func() {
-               l.Logger.Errorf(format, v...)
-       })
-}
-
-func (l *DistinctLogger) Errorln(v ...any) {
-       logStatement := fmt.Sprint(v...)
-       l.printIfNotPrinted("errorln", logStatement, func() {
-               l.Logger.Errorln(v...)
-       })
-}
-
-func (l *DistinctLogger) hasPrinted(key string) bool {
-       l.RLock()
-       defer l.RUnlock()
-       _, found := l.m[key]
-       return found
-}
-
-func (l *DistinctLogger) printIfNotPrinted(level, logStatement string, print func()) {
-       key := level + logStatement
-       if l.hasPrinted(key) {
-               return
-       }
-       l.Lock()
-       defer l.Unlock()
-       l.m[key] = true // Placing this after print() can cause duplicate warning entries to be logged when --panicOnWarning is true.
-       print()
-
-}
-
-// NewDistinctErrorLogger creates a new DistinctLogger that logs ERRORs
-func NewDistinctErrorLogger() loggers.Logger {
-       return &DistinctLogger{m: make(map[string]bool), Logger: loggers.NewErrorLogger()}
-}
-
-// NewDistinctLogger creates a new DistinctLogger that logs to the provided logger.
-func NewDistinctLogger(logger loggers.Logger) loggers.Logger {
-       return &DistinctLogger{m: make(map[string]bool), Logger: logger}
-}
-
-// NewDistinctWarnLogger creates a new DistinctLogger that logs WARNs
-func NewDistinctWarnLogger() loggers.Logger {
-       return &DistinctLogger{m: make(map[string]bool), Logger: loggers.NewWarningLogger()}
-}
-
-var (
-       // DistinctErrorLog can be used to avoid spamming the logs with errors.
-       DistinctErrorLog = NewDistinctErrorLogger()
-
-       // DistinctWarnLog can be used to avoid spamming the logs with warnings.
-       DistinctWarnLog = NewDistinctWarnLogger()
-)
-
-// InitLoggers resets the global distinct loggers.
-func InitLoggers() {
-       DistinctErrorLog.Reset()
-       DistinctWarnLog.Reset()
-}
-
 // Deprecated informs about a deprecation, but only once for a given set of arguments' values.
 // If the err flag is enabled, it logs as an ERROR (will exit with -1) and the text will
 // point at the next Hugo release.
@@ -398,13 +259,9 @@ func InitLoggers() {
 // plenty of time to fix their templates.
 func Deprecated(item, alternative string, err bool) {
        if err {
-               DistinctErrorLog.Errorf("%s is deprecated and will be removed in Hugo %s. %s", item, hugo.CurrentVersion.Next().ReleaseVersion(), alternative)
+               loggers.Log().Errorf("%s is deprecated and will be removed in Hugo %s. %s", item, hugo.CurrentVersion.Next().ReleaseVersion(), alternative)
        } else {
-               var warnPanicMessage string
-               if !loggers.PanicOnWarning.Load() {
-                       warnPanicMessage = "\n\nRe-run Hugo with the flag --panicOnWarning to get a better error message."
-               }
-               DistinctWarnLog.Warnf("%s is deprecated and will be removed in a future release. %s%s", item, alternative, warnPanicMessage)
+               loggers.Log().Warnf("%s is deprecated and will be removed in a future release. %s%s", item, alternative)
        }
 }
 
index 9b2e4fc583352ccee245f91879003ba39e76e501..827411027245d84d0292a375057183fce85fcbc3 100644 (file)
@@ -18,9 +18,7 @@ import (
        "reflect"
        "strings"
        "testing"
-       "time"
 
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/helpers"
 
        qt "github.com/frankban/quicktest"
@@ -55,60 +53,6 @@ func TestResolveMarkup(t *testing.T) {
        }
 }
 
-func TestDistinctLoggerDoesNotLockOnWarningPanic(t *testing.T) {
-       // Testing to make sure logger mutex doesn't lock if warnings cause panics.
-       // func Warnf() of DistinctLogger is defined in general.go
-       l := helpers.NewDistinctLogger(loggers.NewWarningLogger())
-
-       // Set PanicOnWarning to true to reproduce issue 9380
-       // Ensure global variable loggers.PanicOnWarning is reset to old value after test
-       if !loggers.PanicOnWarning.Load() {
-               loggers.PanicOnWarning.Store(true)
-               defer func() {
-                       loggers.PanicOnWarning.Store(false)
-               }()
-       }
-
-       // Establish timeout in case a lock occurs:
-       timeIsUp := make(chan bool)
-       timeOutSeconds := 1
-       go func() {
-               time.Sleep(time.Second * time.Duration(timeOutSeconds))
-               timeIsUp <- true
-       }()
-
-       // Attempt to run multiple logging threads in parallel
-       counterC := make(chan int)
-       goroutines := 5
-
-       for i := 0; i < goroutines; i++ {
-               go func() {
-                       defer func() {
-                               // Intentional panic successfully recovered - notify counter channel
-                               recover()
-                               counterC <- 1
-                       }()
-
-                       l.Warnf("Placeholder template message: %v", "In this test, logging a warning causes a panic.")
-               }()
-       }
-
-       // All goroutines should complete before timeout
-       var counter int
-       for {
-               select {
-               case <-counterC:
-                       counter++
-                       if counter == goroutines {
-                               return
-                       }
-               case <-timeIsUp:
-                       t.Errorf("Unable to log warnings with --panicOnWarning within alloted time of: %v seconds. Investigate possible mutex locking on panic in distinct warning logger.", timeOutSeconds)
-                       return
-               }
-       }
-}
-
 func TestFirstUpper(t *testing.T) {
        for i, this := range []struct {
                in     string
index be8983fdb86c6d00400578371bac674d80796077..6c5f62488ea72ee9aeb895600c2bbb284d2db66a 100644 (file)
@@ -23,7 +23,7 @@ func newTestPathSpecFromCfgAndLang(cfg config.Provider, lang string) *helpers.Pa
                }
        }
        fs := hugofs.NewFrom(mfs, conf.BaseConfig())
-       ps, err := helpers.NewPathSpec(fs, conf, loggers.NewErrorLogger())
+       ps, err := helpers.NewPathSpec(fs, conf, loggers.NewDefault())
        if err != nil {
                panic(err)
        }
@@ -41,7 +41,7 @@ func newTestPathSpec(configKeyValues ...any) *helpers.PathSpec {
 func newTestContentSpec(cfg config.Provider) *helpers.ContentSpec {
        fs := afero.NewMemMapFs()
        conf := testconfig.GetTestConfig(fs, cfg)
-       spec, err := helpers.NewContentSpec(conf, loggers.NewErrorLogger(), fs, nil)
+       spec, err := helpers.NewContentSpec(conf, loggers.NewDefault(), fs, nil)
        if err != nil {
                panic(err)
        }
index d3cad5e7432c111a8c6dcf5ae303f30c0cb2732c..af559844fdd5e477d648dea55b07f85c88cd0cf1 100644 (file)
@@ -19,7 +19,6 @@ import (
        "path/filepath"
 
        "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/spf13/afero"
 )
 
index e00dcf1a8e4994e2b63c4259716d7baadc96b722..d0a8baaaa0d2fa889861e6213e8b1443ff696971 100644 (file)
@@ -18,8 +18,8 @@ import (
        "path/filepath"
        "testing"
 
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/gohugoio/hugo/htesting"
 
        "github.com/spf13/afero"
@@ -64,11 +64,11 @@ func TestNoSymlinkFs(t *testing.T) {
        blogDir := filepath.Join(workDir, "blog")
        blogFile1 := filepath.Join(blogDir, "a.txt")
 
-       logger := loggers.NewWarningLogger()
+       logger := loggers.NewDefault()
 
        for _, bfs := range []afero.Fs{NewBaseFileDecorator(Os), Os} {
                for _, allowFiles := range []bool{false, true} {
-                       logger.LogCounters().WarnCounter.Reset()
+                       logger.Reset()
                        fs := NewNoSymlinkFs(bfs, logger, allowFiles)
                        ls := fs.(afero.Lstater)
                        symlinkedDir := filepath.Join(workDir, "symlinkdedir")
@@ -139,7 +139,7 @@ func TestNoSymlinkFs(t *testing.T) {
                        _, err = f.Readdir(-1)
                        c.Assert(err, qt.IsNil)
                        f.Close()
-                       c.Assert(logger.LogCounters().WarnCounter.Count(), qt.Equals, uint64(1))
+                       c.Assert(logger.LoggCount(logg.LevelWarn), qt.Equals, 1)
 
                }
        }
index e847174c672601c3d52571fbe83374e2e8621297..e883f892e83b2862378b59a1bc6affc87bb60ab9 100644 (file)
@@ -86,7 +86,7 @@ func NewWalkway(cfg WalkwayConfig) *Walkway {
 
        logger := cfg.Logger
        if logger == nil {
-               logger = loggers.NewWarningLogger()
+               logger = loggers.NewDefault()
        }
 
        return &Walkway{
index d10f140bd988a54c1e006243740db0f461723d59..5165edb04138796e7703e3bbc8f255e5b4e0b356 100644 (file)
@@ -25,7 +25,6 @@ import (
        "strings"
 
        "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/gohugoio/hugo/output"
        "github.com/gohugoio/hugo/publisher"
        "github.com/gohugoio/hugo/resources/page"
index 124c9f4ca4fc8ba2235a6369c58dc99731d83334..e03107ada2c3546945fd3908cfde1694533c84cc 100644 (file)
@@ -18,9 +18,8 @@ import (
        "runtime"
        "testing"
 
-       "github.com/gohugoio/hugo/common/loggers"
-
        qt "github.com/frankban/quicktest"
+       "github.com/gohugoio/hugo/common/loggers"
 )
 
 const pageWithAlias = `---
@@ -117,7 +116,7 @@ func TestAliasTemplate(t *testing.T) {
 }
 
 func TestTargetPathHTMLRedirectAlias(t *testing.T) {
-       h := newAliasHandler(nil, loggers.NewErrorLogger(), false)
+       h := newAliasHandler(nil, loggers.NewDefault(), false)
 
        errIsNilForThisOS := runtime.GOOS != "windows"
 
index 5a3087207526e19175ccdda7f77d45e638bf27ad..02d25fa9be996ec4956cc2f5b862fce0fe318217 100644 (file)
@@ -20,6 +20,7 @@ import (
        "strings"
        "testing"
 
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/config/allconfig"
 
@@ -933,7 +934,7 @@ LanguageCode: {{ eq site.LanguageCode site.Language.LanguageCode }}|{{ site.Lang
        ).Build()
 
        {
-               b.Assert(b.H.Log.LogCounters().WarnCounter.Count(), qt.Equals, uint64(2))
+               b.Assert(b.H.Log.LoggCount(logg.LevelWarn), qt.Equals, 1)
        }
        b.AssertFileContent("public/index.html", `
 AllPages: 4|
index ed0d36de168c1ef4f60e3eb92bfba327a0a3ae69..d5c94799d5060a152391eb342bb6556a3b43e84c 100644 (file)
@@ -29,9 +29,9 @@ import (
        "github.com/gohugoio/hugo/hugofs/glob"
 
        "github.com/gohugoio/hugo/common/herrors"
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/common/types"
 
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/rogpeppe/go-internal/lockedfile"
 
        "github.com/gohugoio/hugo/hugofs/files"
@@ -471,7 +471,7 @@ var counter int
 func NewBase(p *paths.Paths, logger loggers.Logger, options ...func(*BaseFs) error) (*BaseFs, error) {
        fs := p.Fs
        if logger == nil {
-               logger = loggers.NewWarningLogger()
+               logger = loggers.NewDefault()
        }
 
        publishFs := hugofs.NewBaseFileDecorator(fs.PublishDir)
index ee4ef798ae2bab789ee531b86990d6335851411e..3353f508e5ff561659dc11c514c15b48aa528b58 100644 (file)
@@ -22,16 +22,16 @@ import (
        "testing"
        "time"
 
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/modules/npm"
 
-       "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/spf13/afero"
 
        "github.com/gohugoio/hugo/hugofs/files"
 
        "github.com/gohugoio/hugo/common/hugo"
+       "github.com/gohugoio/hugo/common/loggers"
 
        "github.com/gohugoio/hugo/htesting"
        "github.com/gohugoio/hugo/hugofs"
@@ -646,14 +646,14 @@ min_version = 0.55.0
 
 `)
 
-       logger := loggers.NewWarningLogger()
+       logger := loggers.NewDefault()
        b.WithLogger(logger)
 
        b.Build(BuildCfg{})
 
        c := qt.New(t)
 
-       c.Assert(logger.LogCounters().WarnCounter.Count(), qt.Equals, uint64(3))
+       c.Assert(logger.LoggCount(logg.LevelWarn), qt.Equals, 3)
 }
 
 func TestModulesSymlinks(t *testing.T) {
@@ -727,7 +727,7 @@ weight = 2
 `
 
        b := newTestSitesBuilder(t).WithNothingAdded().WithWorkingDir(workingDir)
-       b.WithLogger(loggers.NewErrorLogger())
+       b.WithLogger(loggers.NewDefault())
        b.Fs = fs
 
        b.WithConfigFile("toml", config)
index 290eebe82b606256a7c8c4821637819df9c37316..6ffef6fa88021faa222abfb323436e22f1f13845 100644 (file)
@@ -23,6 +23,7 @@ import (
        "sync"
        "sync/atomic"
 
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/config/allconfig"
        "github.com/gohugoio/hugo/hugofs/glob"
 
@@ -42,7 +43,6 @@ import (
        "github.com/gohugoio/hugo/source"
 
        "github.com/gohugoio/hugo/common/herrors"
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/lazy"
@@ -265,7 +265,7 @@ func (h *HugoSites) NumLogErrors() int {
        if h == nil {
                return 0
        }
-       return int(h.Log.LogCounters().ErrorCounter.Count())
+       return h.Log.LoggCount(logg.LevelError)
 }
 
 func (h *HugoSites) PrintProcessingStats(w io.Writer) {
@@ -352,10 +352,8 @@ func (h *HugoSites) reset(config *BuildCfg) {
 // resetLogs resets the log counters etc. Used to do a new build on the same sites.
 func (h *HugoSites) resetLogs() {
        h.Log.Reset()
-       loggers.GlobalErrorCounter.Reset()
        for _, s := range h.Sites {
                s.Deps.Log.Reset()
-               s.Deps.LogDistinct.Reset()
        }
 }
 
index c801ae3dff6dcada9092713dba44c3654538d1d3..d90d9ce7b08e38e601bbf7b5d6cf2546da608127 100644 (file)
@@ -22,6 +22,7 @@ import (
        "strings"
        "time"
 
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/langs"
        "github.com/gohugoio/hugo/publisher"
 
@@ -65,6 +66,8 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
                defer unlock()
        }
 
+       infol := h.Log.InfoCommand("build")
+
        errCollector := h.StartErrorCollector()
        errs := make(chan error)
 
@@ -120,11 +123,11 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
                                return nil
                        }
 
-                       if err := h.process(conf, init, events...); err != nil {
+                       if err := h.process(infol, conf, init, events...); err != nil {
                                return fmt.Errorf("process: %w", err)
                        }
 
-                       if err := h.assemble(conf); err != nil {
+                       if err := h.assemble(infol, conf); err != nil {
                                return fmt.Errorf("assemble: %w", err)
                        }
 
@@ -137,10 +140,10 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
        }
 
        if prepareErr == nil {
-               if err := h.render(conf); err != nil {
+               if err := h.render(infol, conf); err != nil {
                        h.SendError(fmt.Errorf("render: %w", err))
                }
-               if err := h.postProcess(); err != nil {
+               if err := h.postProcess(infol); err != nil {
                        h.SendError(fmt.Errorf("postProcess: %w", err))
                }
        }
@@ -164,7 +167,7 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
                return err
        }
 
-       errorCount := h.Log.LogCounters().ErrorCounter.Count()
+       errorCount := h.Log.LoggCount(logg.LevelError)
        if errorCount > 0 {
                return fmt.Errorf("logged %d error(s)", errorCount)
        }
@@ -195,13 +198,12 @@ func (h *HugoSites) initRebuild(config *BuildCfg) error {
 
        h.reset(config)
        h.resetLogs()
-       helpers.InitLoggers()
 
        return nil
 }
 
-func (h *HugoSites) process(config *BuildCfg, init func(config *BuildCfg) error, events ...fsnotify.Event) error {
-       defer h.timeTrack(time.Now(), "process")
+func (h *HugoSites) process(l logg.LevelLogger, config *BuildCfg, init func(config *BuildCfg) error, events ...fsnotify.Event) error {
+       defer h.timeTrack(l, time.Now(), "process")
 
        // We should probably refactor the Site and pull up most of the logic from there to here,
        // but that seems like a daunting task.
@@ -218,8 +220,8 @@ func (h *HugoSites) process(config *BuildCfg, init func(config *BuildCfg) error,
        return firstSite.process(*config)
 }
 
-func (h *HugoSites) assemble(bcfg *BuildCfg) error {
-       defer h.timeTrack(time.Now(), "assemble")
+func (h *HugoSites) assemble(l logg.LevelLogger, bcfg *BuildCfg) error {
+       defer h.timeTrack(l, time.Now(), "assemble")
 
        if !bcfg.whatChanged.source {
                return nil
@@ -236,13 +238,13 @@ func (h *HugoSites) assemble(bcfg *BuildCfg) error {
        return nil
 }
 
-func (h *HugoSites) timeTrack(start time.Time, name string) {
+func (h *HugoSites) timeTrack(l logg.LevelLogger, start time.Time, name string) {
        elapsed := time.Since(start)
-       h.Log.Infof("%s in %v ms\n", name, int(1000*elapsed.Seconds()))
+       l.WithField("step", name).WithField("duration", elapsed).Logf("running")
 }
 
-func (h *HugoSites) render(config *BuildCfg) error {
-       defer h.timeTrack(time.Now(), "render")
+func (h *HugoSites) render(l logg.LevelLogger, config *BuildCfg) error {
+       defer h.timeTrack(l, time.Now(), "render")
        if _, err := h.init.layouts.Do(context.Background()); err != nil {
                return err
        }
@@ -312,8 +314,8 @@ func (h *HugoSites) render(config *BuildCfg) error {
        return nil
 }
 
-func (h *HugoSites) postProcess() error {
-       defer h.timeTrack(time.Now(), "postProcess")
+func (h *HugoSites) postProcess(l logg.LevelLogger) error {
+       defer h.timeTrack(l, time.Now(), "postProcess")
 
        // Make sure to write any build stats to disk first so it's available
        // to the post processors.
index b2798c86365987656ce43bed3f744211433685a5..3c82a60b68b56ae4b63f9ea6d5ff301fe04f03d6 100644 (file)
@@ -1394,7 +1394,7 @@ other = %q
 }
 
 func TestRebuildOnAssetChange(t *testing.T) {
-       b := newTestSitesBuilder(t).Running().WithLogger(loggers.NewInfoLogger())
+       b := newTestSitesBuilder(t).Running().WithLogger(loggers.NewDefault())
        b.WithTemplatesAdded("index.html", `
 {{ (resources.Get "data.json").Content }}
 `)
index 0daa766ac965f07c148f208e39ebd71828099c29..cb34cb28b63eb6106557a2fbe1370adc234fe5a0 100644 (file)
@@ -13,7 +13,7 @@ import (
        "sync"
        "testing"
 
-       jww "github.com/spf13/jwalterweatherman"
+       "github.com/bep/logg"
 
        qt "github.com/frankban/quicktest"
        "github.com/fsnotify/fsnotify"
@@ -292,11 +292,9 @@ func (s *IntegrationTestBuilder) initBuilder() error {
                }
 
                if s.Cfg.LogLevel == 0 {
-                       s.Cfg.LogLevel = jww.LevelWarn
+                       s.Cfg.LogLevel = logg.LevelWarn
                }
 
-               logger := loggers.NewBasicLoggerForWriter(s.Cfg.LogLevel, &s.logBuff)
-
                isBinaryRe := regexp.MustCompile(`^(.*)(\.png|\.jpg)$`)
 
                const dataSourceFilenamePrefix = "sourcefilename:"
@@ -350,7 +348,7 @@ func (s *IntegrationTestBuilder) initBuilder() error {
                                Flags:     flags,
                                ConfigDir: configDir,
                                Fs:        afs,
-                               Logger:    logger,
+                               Logger:    loggers.NewDefault(),
                                Environ:   s.Cfg.Environ,
                        },
                )
@@ -364,7 +362,7 @@ func (s *IntegrationTestBuilder) initBuilder() error {
 
                s.Assert(err, qt.IsNil)
 
-               depsCfg := deps.DepsCfg{Configs: res, Fs: fs, Logger: logger}
+               depsCfg := deps.DepsCfg{Configs: res, Fs: fs, LogLevel: s.Cfg.LogLevel, LogOut: &s.logBuff}
                sites, err := NewHugoSites(depsCfg)
                if err != nil {
                        initErr = err
@@ -528,7 +526,7 @@ type IntegrationTestConfig struct {
        // Will print the log buffer after the build
        Verbose bool
 
-       LogLevel jww.Threshold
+       LogLevel logg.Level
 
        // Whether it needs the real file system (e.g. for js.Build tests).
        NeedsOsFS bool
index 688cf255846e638331fee665540bd8b1be53c7af..4f6a448d214a2d947ea4b62ab6bee76466c227d0 100644 (file)
@@ -20,7 +20,6 @@ import (
        "testing"
 
        "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/gohugoio/hugo/hugofs/files"
 
        "github.com/gohugoio/hugo/htesting"
@@ -39,7 +38,7 @@ func TestMountFilters(t *testing.T) {
        for _, component := range files.ComponentFolders {
                b.Assert(os.MkdirAll(filepath.Join(workingDir, component), 0777), qt.IsNil)
        }
-       b.WithWorkingDir(workingDir).WithLogger(loggers.NewInfoLogger())
+       b.WithWorkingDir(workingDir).WithLogger(loggers.NewDefault())
        b.WithConfigFile("toml", fmt.Sprintf(`
 workingDir = %q
 
index e9a8b1a50609353242ca6946dba0ae3cf08d61d8..14db28c3df64aaaef4031e37e3edc187b29f7e85 100644 (file)
@@ -99,7 +99,7 @@ func newPageFromMeta(
        meta map[string]any,
        metaProvider *pageMeta) (*pageState, error) {
        if metaProvider.f == nil {
-               metaProvider.f = page.NewZeroFile(metaProvider.s.LogDistinct)
+               metaProvider.f = page.NewZeroFile(metaProvider.s.Log)
        }
 
        ps, err := newPageBase(metaProvider)
index 79b6401dc373990fc4f106fbfb4de13d8355f4bc..6b817551ee9ef64a33895c04b9bcfcc808dfb162 100644 (file)
@@ -17,7 +17,6 @@ import (
        "context"
        "fmt"
        "html/template"
-       "os"
        "path/filepath"
        "strings"
        "testing"
@@ -36,7 +35,6 @@ import (
 
        "github.com/gohugoio/hugo/resources/page"
        "github.com/gohugoio/hugo/resources/resource"
-       "github.com/spf13/jwalterweatherman"
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/deps"
@@ -739,7 +737,7 @@ Here is the last report for commits in the year 2016. It covers hrev50718-hrev50
 func TestRenderStringForRegularPageTranslations(t *testing.T) {
        c := qt.New(t)
        b := newTestSitesBuilder(t)
-       b.WithLogger(loggers.NewBasicLoggerForWriter(jwalterweatherman.LevelError, os.Stderr))
+       b.WithLogger(loggers.NewDefault())
 
        b.WithConfigFile("toml",
                `baseurl = "https://example.org/"
@@ -800,7 +798,7 @@ home = ["HTML", "JSON"]`)
 // Issue 8919
 func TestContentProviderWithCustomOutputFormat(t *testing.T) {
        b := newTestSitesBuilder(t)
-       b.WithLogger(loggers.NewBasicLoggerForWriter(jwalterweatherman.LevelDebug, os.Stderr))
+       b.WithLogger(loggers.NewDefault())
        b.WithConfigFile("toml", `baseURL = 'http://example.org/'
 title = 'My New Hugo Site'
 
@@ -1437,7 +1435,7 @@ Content:{{ .Content }}
 
 // https://github.com/gohugoio/hugo/issues/5781
 func TestPageWithZeroFile(t *testing.T) {
-       newTestSitesBuilder(t).WithLogger(loggers.NewWarningLogger()).WithSimpleConfigFile().
+       newTestSitesBuilder(t).WithLogger(loggers.NewDefault()).WithSimpleConfigFile().
                WithTemplatesAdded("index.html", "{{ .File.Filename }}{{ with .File }}{{ .Dir }}{{ end }}").Build(BuildCfg{})
 }
 
index 2ec3718f0c10aed3c9d1181b440c8a8998e781b3..51ce8252637db260b85876dbdfb02bd32155a4f7 100644 (file)
@@ -20,6 +20,9 @@ import (
        "path"
        "path/filepath"
        "regexp"
+
+       "github.com/gohugoio/hugo/common/loggers"
+
        "strings"
        "testing"
 
@@ -31,7 +34,6 @@ import (
 
        "github.com/gohugoio/hugo/hugofs"
 
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/resources/page"
 
        "github.com/gohugoio/hugo/htesting"
@@ -93,7 +95,7 @@ func TestPageBundlerSiteRegular(t *testing.T) {
 
                                                c.Assert(err, qt.IsNil)
 
-                                               b := newTestSitesBuilderFromDepsCfg(c, deps.DepsCfg{Logger: loggers.NewErrorLogger(), Fs: fs, Configs: configs}).WithNothingAdded()
+                                               b := newTestSitesBuilderFromDepsCfg(c, deps.DepsCfg{Fs: fs, Configs: configs}).WithNothingAdded()
 
                                                b.Build(BuildCfg{})
 
@@ -1044,7 +1046,7 @@ title: %q
        }
 
        b := newTestSitesBuilder(t).WithConfigFile("toml", config)
-       b.WithLogger(loggers.NewWarningLogger())
+       b.WithLogger(loggers.NewDefault())
 
        b.WithTemplates("_default/list.html", `{{ range .Site.Pages }}
 {{ .Kind }}|{{ .Path }}|{{ with .CurrentSection }}CurrentSection: {{ .Path }}{{ end }}|{{ .RelPermalink }}{{ end }}
@@ -1215,7 +1217,7 @@ title: %q
        }
 
        b := newTestSitesBuilder(t).WithConfigFile("toml", config)
-       b.WithLogger(loggers.NewWarningLogger())
+       b.WithLogger(loggers.NewDefault())
 
        b.WithTemplates("_default/single.html", `{{ range .Resources }}
 {{ .ResourceType }}|{{ .Title }}|
index 8a2b875eaed2575f8ea84b2cf93ee142a39d2cb9..c57c707de1e11a6bbfe21d3237b91d165b5b5920 100644 (file)
@@ -21,6 +21,7 @@ import (
        "reflect"
 
        "github.com/gohugoio/hugo/common/herrors"
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/common/maps"
 
        "github.com/gohugoio/hugo/parser/pageparser"
@@ -29,7 +30,6 @@ import (
 
        "github.com/gohugoio/hugo/source"
 
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/hugofs"
        "github.com/spf13/afero"
 )
index 8c1023a15bc943cfd217d3f90f09f7b3b6f31f90..c771d30eee963df5e688ee7e694ad46f2e973ca3 100644 (file)
@@ -53,7 +53,7 @@ func TestPagesCapture(t *testing.T) {
        t.Run("Collect", func(t *testing.T) {
                c := qt.New(t)
                proc := &testPagesCollectorProcessor{}
-               coll := newPagesCollector(sourceSpec, nil, loggers.NewErrorLogger(), nil, proc)
+               coll := newPagesCollector(sourceSpec, nil, loggers.NewDefault(), nil, proc)
                c.Assert(coll.Collect(), qt.IsNil)
                // 2 bundles, 3 pages.
                c.Assert(len(proc.items), qt.Equals, 5)
index af66156e6926fd137402efb27ae9e0072db54b79..e0a4cd0369ad582af67f8b0d60493418693a6e90 100644 (file)
@@ -16,6 +16,7 @@ package hugolib
 import (
        "testing"
 
+       "github.com/bep/logg"
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/common/loggers"
 )
@@ -80,13 +81,13 @@ func TestRenderStringOnListPage(t *testing.T) {
 // Issue 9433
 func TestRenderStringOnPageNotBackedByAFile(t *testing.T) {
        t.Parallel()
-       logger := loggers.NewWarningLogger()
+       logger := loggers.NewDefault()
        b := newTestSitesBuilder(t).WithLogger(logger).WithConfigFile("toml", `
 disableKinds = ["page", "section", "taxonomy", "term"] 
 `)
        b.WithTemplates("index.html", `{{ .RenderString "**Hello**" }}`).WithContent("p1.md", "")
        b.BuildE(BuildCfg{})
-       b.Assert(int(logger.LogCounters().WarnCounter.Count()), qt.Equals, 0)
+       b.Assert(logger.LoggCount(logg.LevelWarn), qt.Equals, 0)
 }
 
 func TestRenderStringWithShortcode(t *testing.T) {
index 823f7db45f8e8d434fee26419e2d4da7ba0adc61..da90634aa145afa3a013ec95fd0a54b7b17e3588 100644 (file)
@@ -588,7 +588,7 @@ XML: {{ $xml.body }}
                        }
                        t.Parallel()
 
-                       b := newTestSitesBuilder(t).WithLogger(loggers.NewErrorLogger())
+                       b := newTestSitesBuilder(t).WithLogger(loggers.NewDefault())
                        b.WithContent("_index.md", `
 ---
 title: Home
index 035f543ee2ce4489fa440b06db8406820c4f67f2..fca8bca7518e09f8dd001c0610e1fe62f83b570e 100644 (file)
@@ -17,7 +17,6 @@ import (
        "context"
        "fmt"
        "io"
-       "log"
        "mime"
        "net/url"
        "path"
@@ -27,6 +26,7 @@ import (
        "strings"
        "time"
 
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/common/htime"
        "github.com/gohugoio/hugo/common/hugio"
@@ -285,7 +285,7 @@ func (s *Site) isEnabled(kind string) bool {
 type siteRefLinker struct {
        s *Site
 
-       errorLogger *log.Logger
+       errorLogger logg.LevelLogger
        notFoundURL string
 }
 
@@ -302,11 +302,11 @@ func newSiteRefLinker(s *Site) (siteRefLinker, error) {
 
 func (s siteRefLinker) logNotFound(ref, what string, p page.Page, position text.Position) {
        if position.IsValid() {
-               s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s: %s", s.s.Lang(), ref, position.String(), what)
+               s.errorLogger.Logf("[%s] REF_NOT_FOUND: Ref %q: %s: %s", s.s.Lang(), ref, position.String(), what)
        } else if p == nil {
-               s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q: %s", s.s.Lang(), ref, what)
+               s.errorLogger.Logf("[%s] REF_NOT_FOUND: Ref %q: %s", s.s.Lang(), ref, what)
        } else {
-               s.errorLogger.Printf("[%s] REF_NOT_FOUND: Ref %q from page %q: %s", s.s.Lang(), ref, p.Pathc(), what)
+               s.errorLogger.Logf("[%s] REF_NOT_FOUND: Ref %q from page %q: %s", s.s.Lang(), ref, p.Pathc(), what)
        }
 }
 
@@ -507,9 +507,6 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
                i18nChanged bool
 
                sourceFilesChanged = make(map[string]bool)
-
-               // prevent spamming the log on changes
-               logger = helpers.NewDistinctErrorLogger()
        )
 
        var cacheBusters []func(string) bool
@@ -531,7 +528,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
 
                        switch id.Type {
                        case files.ComponentFolderContent:
-                               logger.Println("Source changed", ev)
+                               s.Log.Println("Source changed", ev)
                                sourceChanged = append(sourceChanged, ev)
                        case files.ComponentFolderLayouts:
                                tmplChanged = true
@@ -539,16 +536,16 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
                                        tmplAdded = true
                                }
                                if tmplAdded {
-                                       logger.Println("Template added", ev)
+                                       s.Log.Println("Template added", ev)
                                } else {
-                                       logger.Println("Template changed", ev)
+                                       s.Log.Println("Template changed", ev)
                                }
 
                        case files.ComponentFolderData:
-                               logger.Println("Data changed", ev)
+                               s.Log.Println("Data changed", ev)
                                dataChanged = true
                        case files.ComponentFolderI18n:
-                               logger.Println("i18n changed", ev)
+                               s.Log.Println("i18n changed", ev)
                                i18nChanged = true
 
                        }
index 911414121e0ab55eaaef0b4b98fd37e828d56df9..c6c5cd2a35c1026900e789d4fe720435373d6c68 100644 (file)
@@ -18,10 +18,12 @@ import (
        "errors"
        "fmt"
        "html/template"
+       "os"
        "sort"
        "time"
 
        radix "github.com/armon/go-radix"
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/common/hugo"
        "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/common/maps"
@@ -100,19 +102,41 @@ func (s *Site) Debug() {
 func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
        conf := cfg.Configs.GetFirstLanguageConfig()
 
-       logger := cfg.Logger
-       if logger == nil {
-               logger = loggers.NewErrorLogger()
+       var logger loggers.Logger
+       if cfg.TestLogger != nil {
+               logger = cfg.TestLogger
+       } else {
+               var logHookLast func(e *logg.Entry) error
+               if cfg.Configs.Base.PanicOnWarning {
+                       logHookLast = loggers.PanicOnWarningHook
+               }
+               if cfg.LogOut == nil {
+                       cfg.LogOut = os.Stdout
+               }
+               if cfg.LogLevel == 0 {
+                       cfg.LogLevel = logg.LevelWarn
+               }
+
+               logOpts := loggers.Options{
+                       Level:               cfg.LogLevel,
+                       Distinct:            true, // This will drop duplicate log warning and errors.
+                       HandlerPost:         logHookLast,
+                       Stdout:              cfg.LogOut,
+                       Stderr:              cfg.LogOut,
+                       StoreErrors:         conf.Running(),
+                       SuppresssStatements: conf.IgnoredErrors(),
+               }
+               logger = loggers.New(logOpts)
        }
-       ignorableLogger := loggers.NewIgnorableLogger(logger, conf.IgnoredErrors())
 
        firstSiteDeps := &deps.Deps{
                Fs:                  cfg.Fs,
-               Log:                 ignorableLogger,
+               Log:                 logger,
                Conf:                conf,
                TemplateProvider:    tplimpl.DefaultTemplateProvider,
                TranslationProvider: i18n.NewTranslationProvider(),
        }
+
        if err := firstSiteDeps.Init(); err != nil {
                return nil, err
        }
@@ -128,7 +152,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
                k := language.Lang
                conf := confm.LanguageConfigMap[k]
 
-               frontmatterHandler, err := pagemeta.NewFrontmatterHandler(cfg.Logger, conf.Frontmatter)
+               frontmatterHandler, err := pagemeta.NewFrontmatterHandler(firstSiteDeps.Log, conf.Frontmatter)
                if err != nil {
                        return nil, err
                }
@@ -209,6 +233,7 @@ func NewHugoSites(cfg deps.DepsCfg) (*HugoSites, error) {
        }
 
        return h, err
+
 }
 
 func newHugoSitesNew(cfg deps.DepsCfg, d *deps.Deps, sites []*Site) (*HugoSites, error) {
index f076b98ddf11a8dce15024b5526ffbf92740f291..c4c3f389ba8ef58fe202d18392d7269d9f34f901 100644 (file)
@@ -181,7 +181,7 @@ func (s *Site) logMissingLayout(name, layout, kind, outputFormat string) {
 
        msg += ": " + errMsg
 
-       log.Printf(msg, args...)
+       log.Logf(msg, args...)
 }
 
 // renderPaginator must be run after the owning Page has been rendered.
index 7cf1a55965b8c826dd23cd1c034a893e9ed73dc5..e1fd537a8fda4fd86a543a77641ea5ad5863c0ee 100644 (file)
@@ -33,6 +33,7 @@ import (
 
        "github.com/fsnotify/fsnotify"
        "github.com/gohugoio/hugo/common/hexec"
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/deps"
@@ -47,7 +48,6 @@ import (
        "github.com/gohugoio/hugo/resources/resource"
 
        qt "github.com/frankban/quicktest"
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/hugofs"
 )
 
@@ -552,7 +552,7 @@ func (s *sitesBuilder) CreateSitesE() error {
        if depsCfg.Configs.IsZero() {
                depsCfg.Configs = s.Configs
        }
-       depsCfg.Logger = s.logger
+       depsCfg.TestLogger = s.logger
 
        sites, err := NewHugoSites(depsCfg)
 
index a9b7b4c975557c0025b1ff1d4dcb7b9f21a875cb..1ebd039cd8830246cdba7f9b789f39a0f19e4cf9 100644 (file)
@@ -24,7 +24,6 @@ import (
        "github.com/gohugoio/hugo/common/hreflect"
        "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/config"
-       "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/resources/page"
 
        "github.com/gohugoio/go-i18n/v2/i18n"
@@ -32,8 +31,6 @@ import (
 
 type translateFunc func(ctx context.Context, translationID string, templateData any) string
 
-var i18nWarningLogger = helpers.NewDistinctErrorLogger()
-
 // Translator handles i18n translations.
 type Translator struct {
        translateFuncs map[string]translateFunc
@@ -123,7 +120,7 @@ func (t Translator) initFuncs(bndl *i18n.Bundle) {
                        }
 
                        if t.cfg.LogI18nWarnings() {
-                               i18nWarningLogger.Printf("i18n|MISSING_TRANSLATION|%s|%s", currentLangStr, translationID)
+                               t.logger.Warnf("i18n|MISSING_TRANSLATION|%s|%s", currentLangStr, translationID)
                        }
 
                        if enableMissingTranslationPlaceholders {
index 1ac6144ddaa48c18bf132a5c16858dd99e088827..8629c35fc0a6b939a70472cd7c1bcc0c4251fbbf 100644 (file)
@@ -19,12 +19,13 @@ import (
        "path/filepath"
        "testing"
 
+       "github.com/bep/logg"
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/common/types"
        "github.com/gohugoio/hugo/config/testconfig"
 
        "github.com/gohugoio/hugo/tpl/tplimpl"
 
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/resources/page"
        "github.com/spf13/afero"
 
@@ -34,7 +35,7 @@ import (
        "github.com/gohugoio/hugo/config"
 )
 
-var logger = loggers.NewErrorLogger()
+var logger = loggers.NewDefault()
 
 type i18nTest struct {
        name                             string
@@ -406,7 +407,7 @@ other = "{{ . }} miesiąca"
 
                        for _, variant := range test.variants {
                                c.Assert(f(ctx, test.id, variant.Key), qt.Equals, variant.Value, qt.Commentf("input: %v", variant.Key))
-                               c.Assert(int(d.Log.LogCounters().WarnCounter.Count()), qt.Equals, 0)
+                               c.Assert(d.Log.LoggCount(logg.LevelWarn), qt.Equals, 0)
                        }
 
                })
index cdc981263257ffbcdb3b69aeb0428d593d58eac9..459686139e66c142e2209cd01708fff62f6cc676 100644 (file)
@@ -44,7 +44,7 @@ func TestAsciidoctorDefaultArgs(t *testing.T) {
        p, err := asciidocext.Provider.New(
                converter.ProviderConfig{
                        Conf:   conf,
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                },
        )
        c.Assert(err, qt.IsNil)
@@ -76,7 +76,7 @@ func TestAsciidoctorNonDefaultArgs(t *testing.T) {
        p, err := asciidocext.Provider.New(
                converter.ProviderConfig{
                        Conf:   conf,
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                },
        )
        c.Assert(err, qt.IsNil)
@@ -106,7 +106,7 @@ func TestAsciidoctorDisallowedArgs(t *testing.T) {
        p, err := asciidocext.Provider.New(
                converter.ProviderConfig{
                        Conf:   conf,
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                },
        )
        c.Assert(err, qt.IsNil)
@@ -130,7 +130,7 @@ func TestAsciidoctorArbitraryExtension(t *testing.T) {
        p, err := asciidocext.Provider.New(
                converter.ProviderConfig{
                        Conf:   conf,
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                },
        )
        c.Assert(err, qt.IsNil)
@@ -164,7 +164,7 @@ func TestAsciidoctorDisallowedExtension(t *testing.T) {
                p, err := asciidocext.Provider.New(
                        converter.ProviderConfig{
                                Conf:   conf,
-                               Logger: loggers.NewErrorLogger(),
+                               Logger: loggers.NewDefault(),
                        },
                )
                c.Assert(err, qt.IsNil)
@@ -195,7 +195,7 @@ trace = false
        p, err := asciidocext.Provider.New(
                converter.ProviderConfig{
                        Conf:   conf,
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                },
        )
        c.Assert(err, qt.IsNil)
@@ -232,7 +232,7 @@ extensions = ["asciidoctor-html5s", "asciidoctor-diagram"]
        p, err := asciidocext.Provider.New(
                converter.ProviderConfig{
                        Conf:   conf,
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                },
        )
        c.Assert(err, qt.IsNil)
@@ -272,7 +272,7 @@ my-attribute-name = "my value"
        p, err := asciidocext.Provider.New(
                converter.ProviderConfig{
                        Conf:   conf,
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                },
        )
        c.Assert(err, qt.IsNil)
@@ -311,7 +311,7 @@ allow = ['asciidoctor']
 
        p, err := asciidocext.Provider.New(
                converter.ProviderConfig{
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                        Conf:   conf,
                        Exec:   hexec.New(securityConfig),
                },
index 6665298770534c31664cf366bceed8dcbabe0873..a2f7b9597768f6569c38ec7ef75649ca55849d4b 100644 (file)
@@ -46,7 +46,7 @@ noclasses=false
 
 func convert(c *qt.C, conf config.AllProvider, content string) converter.ResultRender {
        pconf := converter.ProviderConfig{
-               Logger: loggers.NewErrorLogger(),
+               Logger: loggers.NewDefault(),
                Conf:   conf,
        }
 
@@ -442,7 +442,7 @@ LINE5
                conf := testconfig.GetTestConfig(nil, cfg)
                pcfg := converter.ProviderConfig{
                        Conf:   conf,
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                }
                p, err := goldmark.Provider.New(
                        pcfg,
index 78811cfb4c08373e5af826c22521a1a6684ceca9..f7f7bb7a0b25c8b175a815239982b140fe7f6335 100644 (file)
@@ -17,12 +17,11 @@ import (
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/config/testconfig"
        "github.com/gohugoio/hugo/markup/converter/hooks"
        "github.com/gohugoio/hugo/markup/goldmark"
 
-       "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/gohugoio/hugo/markup/converter"
 
        qt "github.com/frankban/quicktest"
@@ -56,7 +55,7 @@ And then some.
        p, err := goldmark.Provider.New(
                converter.ProviderConfig{
                        Conf:   testconfig.GetTestConfig(nil, nil),
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                })
        c.Assert(err, qt.IsNil)
        conv, err := p.New(converter.DocumentContext{})
@@ -86,12 +85,12 @@ func TestEscapeToc(t *testing.T) {
        safeP, _ := goldmark.Provider.New(
                converter.ProviderConfig{
                        Conf:   safeConf(),
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                })
        unsafeP, _ := goldmark.Provider.New(
                converter.ProviderConfig{
                        Conf:   unsafeConf(),
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                })
        safeConv, _ := safeP.New(converter.DocumentContext{})
        unsafeConv, _ := unsafeP.New(converter.DocumentContext{})
index 802b9aa5a226c1cfb4458dca8d68f86a6a8a8da4..141269f1d890fd236e45a29de58b0ef979b37a0c 100644 (file)
@@ -16,6 +16,7 @@ package org
 
 import (
        "bytes"
+       "log"
 
        "github.com/gohugoio/hugo/identity"
 
@@ -46,7 +47,7 @@ type orgConverter struct {
 func (c *orgConverter) Convert(ctx converter.RenderContext) (converter.ResultRender, error) {
        logger := c.cfg.Logger
        config := org.New()
-       config.Log = logger.Warn()
+       config.Log = log.Default() // TODO(bep)
        config.ReadFile = func(filename string) ([]byte, error) {
                return afero.ReadFile(c.cfg.ContentFs, filename)
        }
index 08841b2d739ea8f0e9b07065181b82b241924ecf..1422585afdda9f0c103f69748a57741dbb074986 100644 (file)
@@ -29,7 +29,7 @@ import (
 func TestConvert(t *testing.T) {
        c := qt.New(t)
        p, err := org.Provider.New(converter.ProviderConfig{
-               Logger: loggers.NewErrorLogger(),
+               Logger: loggers.NewDefault(),
                Conf:   testconfig.GetTestConfig(afero.NewMemMapFs(), nil),
        })
        c.Assert(err, qt.IsNil)
index f549d5f4ff8d21189093ae7e6ac46694787b9338..6a1535946c52c11e43ad9aca99a9bd3b706a83df 100644 (file)
@@ -32,7 +32,7 @@ func TestConvert(t *testing.T) {
        c := qt.New(t)
        sc := security.DefaultConfig
        sc.Exec.Allow = security.NewWhitelist("pandoc")
-       p, err := Provider.New(converter.ProviderConfig{Exec: hexec.New(sc), Logger: loggers.NewErrorLogger()})
+       p, err := Provider.New(converter.ProviderConfig{Exec: hexec.New(sc), Logger: loggers.NewDefault()})
        c.Assert(err, qt.IsNil)
        conv, err := p.New(converter.DocumentContext{})
        c.Assert(err, qt.IsNil)
index 5d2882de15bc4194a6f29c3592e8092344fcc50f..9e98d0405c5d08301c267f0c9920a3b9136663bf 100644 (file)
@@ -35,7 +35,7 @@ func TestConvert(t *testing.T) {
 
        p, err := Provider.New(
                converter.ProviderConfig{
-                       Logger: loggers.NewErrorLogger(),
+                       Logger: loggers.NewDefault(),
                        Exec:   hexec.New(sc),
                })
        c.Assert(err, qt.IsNil)
index 59f6b25d3d4c397ee2e8612f9924d170766e7b79..5d8daf92623f45baa6157ef003a6ed5c6edeae68 100644 (file)
@@ -30,6 +30,7 @@ import (
        "github.com/gohugoio/hugo/common/collections"
        "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/common/hexec"
+       "github.com/gohugoio/hugo/common/loggers"
 
        hglob "github.com/gohugoio/hugo/hugofs/glob"
 
@@ -39,8 +40,6 @@ import (
 
        "github.com/gohugoio/hugo/hugofs/files"
 
-       "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/gohugoio/hugo/config"
 
        "github.com/rogpeppe/go-internal/module"
@@ -98,7 +97,7 @@ func NewClient(cfg ClientConfig) *Client {
 
        logger := cfg.Logger
        if logger == nil {
-               logger = loggers.NewWarningLogger()
+               logger = loggers.NewDefault()
        }
 
        var noVendor glob.Glob
index a65731ae4ca633d7cb0a5e9dc586a9fe35be9480..93eb32e475ad4cd81dbce72c34a2f2e07c6997ad 100644 (file)
@@ -18,6 +18,7 @@ import (
        "encoding/csv"
        "encoding/json"
        "fmt"
+       "log"
        "regexp"
        "strings"
 
@@ -28,7 +29,6 @@ import (
        toml "github.com/pelletier/go-toml/v2"
        "github.com/spf13/afero"
        "github.com/spf13/cast"
-       jww "github.com/spf13/jwalterweatherman"
        yaml "gopkg.in/yaml.v2"
 )
 
@@ -231,7 +231,7 @@ func parseORGDate(s string) string {
 
 func (d Decoder) unmarshalORG(data []byte, v any) error {
        config := org.New()
-       config.Log = jww.WARN
+       config.Log = log.Default() // TODO(bep)
        document := config.Parse(bytes.NewReader(data), "")
        if document.Error != nil {
                return document.Error
@@ -242,7 +242,7 @@ func (d Decoder) unmarshalORG(data []byte, v any) error {
                if strings.HasSuffix(k, "[]") {
                        frontMatter[k[:len(k)-2]] = strings.Fields(v)
                } else if k == "tags" || k == "categories" || k == "aliases" {
-                       jww.WARN.Printf("Please use '#+%s[]:' notation, automatic conversion is deprecated.", k)
+                       log.Printf("warn: Please use '#+%s[]:' notation, automatic conversion is deprecated.", k)
                        frontMatter[k] = strings.Fields(v)
                } else if k == "date" || k == "lastmod" || k == "publishdate" || k == "expirydate" {
                        frontMatter[k] = parseORGDate(v)
index d827bfbad29db2d0efa42e6d030f233b5625d286..98ab6b2225c4efa9885eae0f71b460d9770d8a52 100644 (file)
@@ -18,9 +18,9 @@ import (
        "time"
 
        "github.com/gohugoio/hugo/common/htime"
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/common/paths"
 
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/resources/resource"
 
@@ -270,7 +270,7 @@ func toLowerSlice(in any) []string {
 // If no logger is provided, one will be created.
 func NewFrontmatterHandler(logger loggers.Logger, frontMatterConfig FrontmatterConfig) (FrontMatterHandler, error) {
        if logger == nil {
-               logger = loggers.NewErrorLogger()
+               logger = loggers.NewDefault()
        }
 
        allDateKeys := make(map[string]bool)
index c462e176f249ca7e192de7a6f57042fcec9644d0..95124cb58834e6496414c0ce67e5b8579a7f60f6 100644 (file)
@@ -30,7 +30,7 @@ func newTestPathSpecFor(cfg config.Provider) *helpers.PathSpec {
        mfs := afero.NewMemMapFs()
        conf := testconfig.GetTestConfig(mfs, cfg)
        fs := hugofs.NewFrom(mfs, conf.BaseConfig())
-       ps, err := helpers.NewPathSpec(fs, conf, loggers.NewErrorLogger())
+       ps, err := helpers.NewPathSpec(fs, conf, loggers.NewDefault())
        if err != nil {
                panic(err)
        }
index 5ecb021fe3a6b42783577746aed503f35a41e24e..3e1b5320561dd63506e152d891b6d2624aa14141 100644 (file)
@@ -31,6 +31,7 @@ import (
 
        "github.com/gohugoio/hugo/common/herrors"
        "github.com/gohugoio/hugo/common/hexec"
+       "github.com/gohugoio/hugo/common/loggers"
 
        "github.com/gohugoio/hugo/identity"
 
@@ -39,7 +40,6 @@ import (
        "github.com/gohugoio/hugo/resources/postpub"
 
        "github.com/gohugoio/hugo/cache/filecache"
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/media"
        "github.com/gohugoio/hugo/resources/images"
        "github.com/gohugoio/hugo/resources/page"
@@ -75,7 +75,7 @@ func NewSpec(
        }
 
        if logger == nil {
-               logger = loggers.NewErrorLogger()
+               logger = loggers.NewDefault()
        }
 
        permalinks, err := page.NewPermalinkExpander(s.URLize, conf.Permalinks)
index ff19d9dda4b5d3ba5ca00a4bbb2dc1ba88fe55f3..5f8fcb00f7ef10449803844b8e301d1c27b7f9be 100644 (file)
@@ -122,10 +122,10 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
        }
 
        var configFile string
-       logger := t.rs.Logger
+       infol := t.rs.Logger.InfoCommand(binaryName)
+       infoW := loggers.LevelLoggerToWriter(infol)
 
        var errBuf bytes.Buffer
-       infoW := loggers.LoggerToWriterWithPrefix(logger.Info(), "babel")
 
        if t.options.Config != "" {
                configFile = t.options.Config
@@ -149,7 +149,7 @@ func (t *babelTransformation) Transform(ctx *resources.ResourceTransformationCtx
        var cmdArgs []any
 
        if configFile != "" {
-               logger.Infoln("babel: use config file", configFile)
+               infol.Logf("use config file %q", configFile)
                cmdArgs = []any{"--config-file", configFile}
        }
 
index 164e7fd40beae07142d236c8642bee8890a5768e..44a13f103dc8dc4ac9a552eb83c498222be55dd5 100644 (file)
@@ -16,8 +16,7 @@ package babel_test
 import (
        "testing"
 
-       jww "github.com/spf13/jwalterweatherman"
-
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/htesting"
        "github.com/gohugoio/hugo/hugolib"
 )
@@ -82,7 +81,7 @@ Transpiled3: {{ $transpiled.Permalink }}
                        TxtarString:     files,
                        NeedsOsFS:       true,
                        NeedsNpmInstall: true,
-                       LogLevel:        jww.LevelInfo,
+                       LogLevel:        logg.LevelInfo,
                }).Build()
 
        b.AssertLogContains("babel: Hugo Environment: production")
index cfe5f8a2ca65b7516918736d5fd0a72cf4aae30a..c920fe17d461761cc62a0460114839461e549e6b 100644 (file)
@@ -16,11 +16,11 @@ package postcss_test
 import (
        "fmt"
        "path/filepath"
+       "runtime"
        "strings"
        "testing"
 
-       jww "github.com/spf13/jwalterweatherman"
-
+       "github.com/bep/logg"
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/htesting"
        "github.com/gohugoio/hugo/hugofs"
@@ -124,7 +124,7 @@ func TestTransformPostCSS(t *testing.T) {
                                T:               c,
                                NeedsOsFS:       true,
                                NeedsNpmInstall: true,
-                               LogLevel:        jww.LevelInfo,
+                               LogLevel:        logg.LevelInfo,
                                WorkingDir:      tempDir,
                                TxtarString:     files,
                        }).Build()
@@ -148,6 +148,11 @@ func TestTransformPostCSSError(t *testing.T) {
                t.Skip("Skip long running test when running locally")
        }
 
+       if runtime.GOOS == "windows" {
+               //TODO(bep) This has started to fail on Windows with Go 1.19 on GitHub Actions for some mysterious reason.
+               t.Skip("Skip on Windows")
+       }
+
        c := qt.New(t)
 
        s, err := hugolib.NewIntegrationTestBuilder(
@@ -176,7 +181,7 @@ func TestTransformPostCSSImportError(t *testing.T) {
                        T:               c,
                        NeedsOsFS:       true,
                        NeedsNpmInstall: true,
-                       LogLevel:        jww.LevelInfo,
+                       LogLevel:        logg.LevelInfo,
                        TxtarString:     strings.ReplaceAll(postCSSIntegrationTestFiles, `@import "components/all.css";`, `@import "components/doesnotexist.css";`),
                }).BuildE()
 
@@ -201,7 +206,7 @@ func TestTransformPostCSSImporSkipInlineImportsNotFound(t *testing.T) {
                        T:               c,
                        NeedsOsFS:       true,
                        NeedsNpmInstall: true,
-                       LogLevel:        jww.LevelInfo,
+                       LogLevel:        logg.LevelInfo,
                        TxtarString:     files,
                }).Build()
 
@@ -233,7 +238,7 @@ func TestTransformPostCSSResourceCacheWithPathInBaseURL(t *testing.T) {
                                T:               c,
                                NeedsOsFS:       true,
                                NeedsNpmInstall: true,
-                               LogLevel:        jww.LevelInfo,
+                               LogLevel:        logg.LevelInfo,
                                TxtarString:     files,
                                WorkingDir:      tempDir,
                        }).Build()
index 376d72182f04efc51644a5bd7c02199170d5b640..0836072468ec9d769c3a8f92aa39fd8b17e61245 100644 (file)
@@ -27,13 +27,12 @@ import (
 
        "github.com/gohugoio/hugo/common/collections"
        "github.com/gohugoio/hugo/common/hexec"
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/common/text"
        "github.com/gohugoio/hugo/hugofs"
 
        "github.com/gohugoio/hugo/common/hugo"
 
-       "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/gohugoio/hugo/resources/internal"
        "github.com/spf13/afero"
        "github.com/spf13/cast"
@@ -151,10 +150,12 @@ func (t *postcssTransformation) Key() internal.ResourceTransformationKey {
 func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationCtx) error {
        const binaryName = "postcss"
 
+       infol := t.rs.Logger.InfoCommand(binaryName)
+       infoW := loggers.LevelLoggerToWriter(infol)
+
        ex := t.rs.ExecHelper
 
        var configFile string
-       logger := t.rs.Logger
 
        var options Options
        if t.optionsm != nil {
@@ -185,7 +186,7 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC
        var cmdArgs []any
 
        if configFile != "" {
-               logger.Infoln("postcss: use config file", configFile)
+               infol.Logf("use config file %q", configFile)
                cmdArgs = []any{"--config", configFile}
        }
 
@@ -194,7 +195,6 @@ func (t *postcssTransformation) Transform(ctx *resources.ResourceTransformationC
        }
 
        var errBuf bytes.Buffer
-       infoW := loggers.LoggerToWriterWithPrefix(logger.Info(), "postcss")
 
        stderr := io.MultiWriter(infoW, &errBuf)
        cmdArgs = append(cmdArgs, hexec.WithStderr(stderr))
@@ -401,7 +401,6 @@ func (imp *importResolver) shouldImport(s string) bool {
 }
 
 func (imp *importResolver) toFileError(output string) error {
-       output = strings.TrimSpace(loggers.RemoveANSIColours(output))
        inErr := errors.New(output)
 
        match := cssSyntaxErrorRe.FindStringSubmatch(output)
index 6901d69defa4725fc7cb5c270d649af5465899a4..dd0695cd1e7848e4aaba420ea0cc4c5e87ae4afc 100644 (file)
@@ -18,9 +18,9 @@ import (
        "strings"
        "testing"
 
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/htesting/hqt"
 
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/helpers"
 
        "github.com/spf13/afero"
@@ -95,7 +95,7 @@ LOCAL_STYLE
                mainStyles,
                "styles.css",
                Options{},
-               fs, loggers.NewErrorLogger(),
+               fs, loggers.NewDefault(),
        )
 
        r, err := imp.resolve()
@@ -144,7 +144,7 @@ LOCAL_STYLE
 @import "e.css";
 @import "missing.css";`
 
-       logger := loggers.NewErrorLogger()
+       logger := loggers.NewDefault()
 
        for i := 0; i < b.N; i++ {
                b.StopTimer()
index 9ae3178869d22ba5fafcd66f8a32142a00f53c8b..63278f0db24ad98dabafc86d2cded019886856ab 100644 (file)
@@ -58,6 +58,8 @@ func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) (*Client, error)
                transpiler   *godartsass.Transpiler
                transpilerv1 *godartsassv1.Transpiler
                err          error
+               infol        = rs.Logger.InfoCommand("Dart Sass")
+               warnl        = rs.Logger.WarnCommand("Dart Sass")
        )
 
        if hugo.IsDartSassV2() {
@@ -68,10 +70,10 @@ func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) (*Client, error)
                                switch event.Type {
                                case godartsass.LogEventTypeDebug:
                                        // Log as Info for now, we may adjust this if it gets too chatty.
-                                       rs.Logger.Infof("Dart Sass: %s", message)
+                                       infol.Logf(message)
                                default:
                                        // The rest are either deprecations or @warn statements.
-                                       rs.Logger.Warnf("Dart Sass: %s", message)
+                                       warnl.Logf(message)
                                }
                        },
                })
@@ -84,10 +86,10 @@ func New(fs *filesystems.SourceFilesystem, rs *resources.Spec) (*Client, error)
                                switch event.Type {
                                case godartsassv1.LogEventTypeDebug:
                                        // Log as Info for now, we may adjust this if it gets too chatty.
-                                       rs.Logger.Infof("Dart Sass: %s", message)
+                                       infol.Logf(message)
                                default:
                                        // The rest are either deprecations or @warn statements.
-                                       rs.Logger.Warnf("Dart Sass: %s", message)
+                                       warnl.Logf(message)
                                }
                        },
                })
index d765923360c98fb28bd898edec9d27530abd9cd7..c370a1cc88b19081eba6e2a57b8f72e2fa8bf0c4 100644 (file)
@@ -17,10 +17,10 @@ import (
        "strings"
        "testing"
 
+       "github.com/bep/logg"
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/hugolib"
        "github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
-       jww "github.com/spf13/jwalterweatherman"
 )
 
 func TestTransformIncludePaths(t *testing.T) {
@@ -288,11 +288,11 @@ T1: {{ $r.Content }}
                        T:           t,
                        TxtarString: files,
                        NeedsOsFS:   true,
-                       LogLevel:    jww.LevelInfo,
+                       LogLevel:    logg.LevelInfo,
                }).Build()
 
-       b.AssertLogMatches(`WARN.*Dart Sass: foo`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:1:0: bar`)
+       b.AssertLogMatches(`Dart Sass: foo`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:1:0: bar`)
 
 }
 
@@ -513,20 +513,20 @@ T1: {{ $r.Content }}
                        T:           t,
                        TxtarString: files,
                        NeedsOsFS:   true,
-                       LogLevel:    jww.LevelInfo,
+                       LogLevel:    logg.LevelInfo,
                }).Build()
 
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:3:0: color`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:4:0: color`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:5:0: color`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:6:0: number`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:7:0: number`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:8:0: number`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:9:0: string`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:10:0: string`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:11:0: string`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:12:0: number`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:13:0: number`)
-       b.AssertLogMatches(`INFO.*Dart Sass: .*assets.*main.scss:14:0: number`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:3:0: color`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:4:0: color`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:5:0: color`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:6:0: number`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:7:0: number`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:8:0: number`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:9:0: string`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:10:0: string`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:11:0: string`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:12:0: number`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:13:0: number`)
+       b.AssertLogMatches(`Dart Sass: .*assets.*main.scss:14:0: number`)
 
 }
index 35a87394afee41f3e77b39f236b25c2a9a24efff..04b777dfba5bf7053b94e78dc113eb225922b131 100644 (file)
@@ -30,7 +30,6 @@ import (
        "github.com/gohugoio/hugo/common/maps"
        "github.com/gohugoio/hugo/common/types"
        "github.com/gohugoio/hugo/deps"
-       "github.com/gohugoio/hugo/helpers"
        "github.com/gohugoio/hugo/langs"
        "github.com/gohugoio/hugo/tpl/compare"
        "github.com/spf13/cast"
@@ -393,7 +392,7 @@ func (ns *Namespace) IsSet(c any, key any) (bool, error) {
                        return av.MapIndex(kv).IsValid(), nil
                }
        default:
-               helpers.DistinctErrorLog.Printf("WARNING: calling IsSet with unsupported type %q (%T) will always return false.\n", av.Kind(), c)
+               ns.deps.Log.Warnf("calling IsSet with unsupported type %q (%T) will always return false.\n", av.Kind(), c)
        }
 
        return false, nil
index 251cf1a4f780d3ba7d4bcee8cfd8acbe9c9f8b99..380c25685f6b500b05f637344abf27d427e009f4 100644 (file)
@@ -30,7 +30,6 @@ import (
        "github.com/gohugoio/hugo/common/types"
 
        "github.com/gohugoio/hugo/common/constants"
-       "github.com/gohugoio/hugo/common/loggers"
 
        "github.com/spf13/cast"
 
@@ -92,7 +91,7 @@ func (ns *Namespace) GetCSV(sep string, args ...any) (d [][]string, err error) {
                if security.IsAccessDenied(err) {
                        return nil, err
                }
-               ns.deps.Log.(loggers.IgnorableLogger).Errorsf(constants.ErrRemoteGetCSV, "Failed to get CSV resource %q: %s", url, err)
+               ns.deps.Log.Errorsf(constants.ErrRemoteGetCSV, "Failed to get CSV resource %q: %s", url, err)
                return nil, nil
        }
 
@@ -128,7 +127,7 @@ func (ns *Namespace) GetJSON(args ...any) (any, error) {
                if security.IsAccessDenied(err) {
                        return nil, err
                }
-               ns.deps.Log.(loggers.IgnorableLogger).Errorsf(constants.ErrRemoteGetJSON, "Failed to get JSON resource %q: %s", url, err)
+               ns.deps.Log.Errorsf(constants.ErrRemoteGetJSON, "Failed to get JSON resource %q: %s", url, err)
                return nil, nil
        }
 
index f10b88a320d046db7022cd3e95420264979147a9..c51dbbd8231e47bb2ba676825bc130fe7b07dd2c 100644 (file)
@@ -22,6 +22,7 @@ import (
        "strings"
        "testing"
 
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/common/maps"
 
        qt "github.com/frankban/quicktest"
@@ -108,13 +109,13 @@ func TestGetCSV(t *testing.T) {
                        got, err := ns.GetCSV(test.sep, test.url)
 
                        if _, ok := test.expect.(bool); ok {
-                               c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 1)
+                               c.Assert(int(ns.deps.Log.LoggCount(logg.LevelError)), qt.Equals, 1)
                                c.Assert(got, qt.IsNil)
                                return
                        }
 
                        c.Assert(err, qt.IsNil, msg)
-                       c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0)
+                       c.Assert(int(ns.deps.Log.LoggCount(logg.LevelError)), qt.Equals, 0)
                        c.Assert(got, qt.Not(qt.IsNil), msg)
                        c.Assert(got, qt.DeepEquals, test.expect, msg)
                })
@@ -200,11 +201,11 @@ func TestGetJSON(t *testing.T) {
                        got, _ := ns.GetJSON(test.url)
 
                        if _, ok := test.expect.(bool); ok {
-                               c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 1)
+                               c.Assert(int(ns.deps.Log.LoggCount(logg.LevelError)), qt.Equals, 1)
                                return
                        }
 
-                       c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0, msg)
+                       c.Assert(int(ns.deps.Log.LoggCount(logg.LevelError)), qt.Equals, 0, msg)
                        c.Assert(got, qt.Not(qt.IsNil), msg)
                        c.Assert(got, qt.DeepEquals, test.expect)
 
@@ -283,7 +284,7 @@ func TestHeaders(t *testing.T) {
                                err := fn("http://example.org/api", "?foo", test.headers)
 
                                c.Assert(err, qt.IsNil)
-                               c.Assert(int(ns.deps.Log.LogCounters().ErrorCounter.Count()), qt.Equals, 0)
+                               c.Assert(int(ns.deps.Log.LoggCount(logg.LevelError)), qt.Equals, 0)
                                test.assert(c, headers.String())
                        }
 
index ad4ab20f446199d05d5d5f0356cbbc15f859743c..d452a2a439ac0685c34ce8165a9c49be380ea76e 100644 (file)
@@ -15,6 +15,9 @@ package data
 
 import (
        "bytes"
+
+       "github.com/gohugoio/hugo/common/loggers"
+
        "net/http"
        "net/http/httptest"
        "net/url"
@@ -29,7 +32,6 @@ import (
 
        qt "github.com/frankban/quicktest"
        "github.com/gohugoio/hugo/cache/filecache"
-       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/config"
        "github.com/gohugoio/hugo/deps"
        "github.com/gohugoio/hugo/hugofs"
@@ -182,7 +184,7 @@ func TestScpGetRemoteParallel(t *testing.T) {
 
 func newDeps(cfg config.Provider) *deps.Deps {
        conf := testconfig.GetTestConfig(nil, cfg)
-       logger := loggers.NewIgnorableLogger(loggers.NewErrorLogger(), nil)
+       logger := loggers.NewDefault()
        fs := hugofs.NewFrom(afero.NewMemMapFs(), conf.BaseConfig())
 
        d := &deps.Deps{
index 0667bcedd39ef090850e573e3d267c575a8e39ff..c0d75e425bfd5c5786259c4be76b62ead2ea8e74 100644 (file)
@@ -16,27 +16,22 @@ package fmt
 
 import (
        _fmt "fmt"
+       "sort"
 
+       "github.com/bep/logg"
        "github.com/gohugoio/hugo/common/loggers"
-
        "github.com/gohugoio/hugo/deps"
-       "github.com/gohugoio/hugo/helpers"
+       "github.com/spf13/cast"
 )
 
 // New returns a new instance of the fmt-namespaced template functions.
 func New(d *deps.Deps) *Namespace {
-       ignorableLogger, ok := d.Log.(loggers.IgnorableLogger)
-       if !ok {
-               ignorableLogger = loggers.NewIgnorableLogger(d.Log, nil)
-       }
-
-       distinctLogger := helpers.NewDistinctLogger(d.Log)
        ns := &Namespace{
-               distinctLogger: ignorableLogger.Apply(distinctLogger),
+               logger: d.Log,
        }
 
        d.BuildStartListeners.Add(func() {
-               ns.distinctLogger.Reset()
+               ns.logger.Reset()
        })
 
        return ns
@@ -44,7 +39,7 @@ func New(d *deps.Deps) *Namespace {
 
 // Namespace provides template functions for the "fmt" namespace.
 type Namespace struct {
-       distinctLogger loggers.IgnorableLogger
+       logger loggers.Logger
 }
 
 // Print returns a string representation of args.
@@ -65,7 +60,7 @@ func (ns *Namespace) Println(args ...any) string {
 // Errorf formats args according to a format specifier and logs an ERROR.
 // It returns an empty string.
 func (ns *Namespace) Errorf(format string, args ...any) string {
-       ns.distinctLogger.Errorf(format, args...)
+       ns.logger.Errorf(format, args...)
        return ""
 }
 
@@ -73,13 +68,41 @@ func (ns *Namespace) Errorf(format string, args ...any) string {
 // an information text that the error with the given id can be suppressed in config.
 // It returns an empty string.
 func (ns *Namespace) Erroridf(id, format string, args ...any) string {
-       ns.distinctLogger.Errorsf(id, format, args...)
+       ns.logger.Errorsf(id, format, args...)
        return ""
 }
 
 // Warnf formats args according to a format specifier and logs a WARNING.
 // It returns an empty string.
 func (ns *Namespace) Warnf(format string, args ...any) string {
-       ns.distinctLogger.Warnf(format, args...)
+       ns.logger.Warnf(format, args...)
+       return ""
+}
+
+// Warnmf is epxermimental and subject to change at any time.
+func (ns *Namespace) Warnmf(m any, format string, args ...any) string {
+       return ns.logmf(ns.logger.Warn(), m, format, args...)
+}
+
+// Errormf is epxermimental and subject to change at any time.
+func (ns *Namespace) Errormf(m any, format string, args ...any) string {
+       return ns.logmf(ns.logger.Error(), m, format, args...)
+}
+
+func (ns *Namespace) logmf(l logg.LevelLogger, m any, format string, args ...any) string {
+       mm := cast.ToStringMap(m)
+       fields := make(logg.Fields, len(mm))
+       i := 0
+       for k, v := range mm {
+               fields[i] = logg.Field{Name: k, Value: v}
+               i++
+       }
+       // Sort the fields to make the output deterministic.
+       sort.Slice(fields, func(i, j int) bool {
+               return fields[i].Name < fields[j].Name
+       })
+
+       l.WithFields(fields).Logf(format, args...)
+
        return ""
 }
index d57ba4d244dae662042cab26b0ee117ff477c5d3..a29a64ebbbf55a475453d5f02a731c6e5135a085 100644 (file)
@@ -20,7 +20,8 @@ import (
        "net/url"
        "strings"
 
-       "github.com/gohugoio/hugo/helpers"
+       "github.com/gohugoio/hugo/common/loggers"
+
        "github.com/gohugoio/hugo/transform"
 )
 
@@ -90,7 +91,7 @@ func New(baseURL url.URL) transform.Transformer {
                c = append(c[:i], append(script, c[i:]...)...)
 
                if _, err := ft.To().Write(c); err != nil {
-                       helpers.DistinctWarnLog.Println("Failed to inject LiveReload script:", err)
+                       loggers.Log().Warnf("Failed to inject LiveReload script:", err)
                }
                return nil
        }
index fd3a6a4abaf7035c3fc3e8527e6414c6bccba2a6..43a477354b0271147c5384b0416178a34f86366d 100644 (file)
@@ -19,7 +19,7 @@ import (
        "regexp"
 
        "github.com/gohugoio/hugo/common/hugo"
-       "github.com/gohugoio/hugo/helpers"
+       "github.com/gohugoio/hugo/common/loggers"
        "github.com/gohugoio/hugo/transform"
 )
 
@@ -33,7 +33,7 @@ func HugoGenerator(ft transform.FromTo) error {
        b := ft.From().Bytes()
        if metaTagsCheck.Match(b) {
                if _, err := ft.To().Write(b); err != nil {
-                       helpers.DistinctWarnLog.Println("Failed to inject Hugo generator tag:", err)
+                       loggers.Log().Warnf("Failed to inject Hugo generator tag: %s", err)
                }
                return nil
        }
@@ -49,7 +49,7 @@ func HugoGenerator(ft transform.FromTo) error {
        }
 
        if _, err := ft.To().Write(newcontent); err != nil {
-               helpers.DistinctWarnLog.Println("Failed to inject Hugo generator tag:", err)
+               loggers.Log().Warnf("Failed to inject Hugo generator tag: %s", err)
        }
 
        return nil