Replace the old log setup, with structured logging etc.

Fixes #11124
This commit is contained in:
Bjørn Erik Pedersen 2023-06-16 08:17:42 +02:00
parent 0e79446586
commit 7c9fada778
80 changed files with 1273 additions and 1082 deletions

View 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)

View 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.")

View 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
}

View 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 {

View 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)
}
}
}

View 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 {

View file

@ -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
}

View file

@ -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
}

View file

@ -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
})
}

View file

@ -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
}

View file

@ -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,
}
}

303
common/loggers/logger.go Normal file
View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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,
}
}

View file

@ -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 |")
}

View 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

View 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

View 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
}

View 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")

View 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)
}
}

View 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(),
}
}

32
deps/deps.go vendored
View 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 Logger to use.
Logger loggers.Logger
// The logging level to use.
LogLevel logg.Level
// Where to write the logs.
// Currently we typically write everything to stdout.
LogOut io.Writer
// The file systems to use
Fs *hugofs.Fs

6
go.mod
View file

@ -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

12
go.sum
View file

@ -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=

View 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)
}
}

View 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

View 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)
}

View file

@ -19,7 +19,6 @@ import (
"path/filepath"
"github.com/gohugoio/hugo/common/loggers"
"github.com/spf13/afero"
)

View 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)
}
}

View file

@ -86,7 +86,7 @@ func NewWalkway(cfg WalkwayConfig) *Walkway {
logger := cfg.Logger
if logger == nil {
logger = loggers.NewWarningLogger()
logger = loggers.NewDefault()
}
return &Walkway{

View 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"

View 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"

View 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|

View 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)

View 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)

View 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()
}
}

View 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.

View 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 }}
`)

View 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

View 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

View 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)

View 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{})
}

View 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 }}|

View 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"
)

View 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)

View 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) {

View 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

View 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
}

View 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) {

View 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.

View 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)

View 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 {

View 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)
}
})

View 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),
},

View 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,

View 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{})

View 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)
}

View 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)

View 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)

View 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)

View 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

View 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)

View 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)

View 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)
}

View 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)

View 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}
}

View 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")

View 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()

View 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)

View 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()

View 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)
}
},
})

View 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`)
}

View 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

View 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
}

View 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())
}

View 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{

View 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 ""
}

View 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
}

View 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