tpl/fmt: Add erroridf template func

Fixes #8613
This commit is contained in:
Bjørn Erik Pedersen 2021-06-07 16:36:48 +02:00
parent 282f1aa3db
commit f55d2f4376
No known key found for this signature in database
GPG Key ID: 330E6E2BD4859D8F
19 changed files with 170 additions and 71 deletions

View File

@ -58,7 +58,7 @@ func (s *staticSyncer) syncsStaticEvents(staticEvents []fsnotify.Event) error {
syncer.DestFs = c.Fs.Destination
// prevent spamming the log on changes
logger := helpers.NewDistinctFeedbackLogger()
logger := helpers.NewDistinctErrorLogger()
for _, ev := range staticEvents {
// Due to our approach of layering both directories and the content's rendered output

View File

@ -22,6 +22,7 @@ import (
type IgnorableLogger interface {
Logger
Errorsf(statementID, format string, v ...interface{})
Apply(logger Logger) IgnorableLogger
}
type ignorableLogger struct {
@ -55,3 +56,10 @@ ignoreErrors = [%q]`, statementID)
l.Errorf(format, v...)
}
func (l ignorableLogger) Apply(logger Logger) IgnorableLogger {
return ignorableLogger{
Logger: logger,
statements: l.statements,
}
}

View File

@ -59,6 +59,8 @@ type Logger interface {
Println(v ...interface{})
PrintTimerIfDelayed(start time.Time, name string)
Debug() *log.Logger
Debugf(format string, v ...interface{})
Debugln(v ...interface{})
Info() *log.Logger
Infof(format string, v ...interface{})
Infoln(v ...interface{})
@ -108,6 +110,14 @@ func (l *logger) Debug() *log.Logger {
return l.DEBUG
}
func (l *logger) Debugf(format string, v ...interface{}) {
l.DEBUG.Printf(format, v...)
}
func (l *logger) Debugln(v ...interface{}) {
l.DEBUG.Println(v...)
}
func (l *logger) Infof(format string, v ...interface{}) {
l.INFO.Printf(format, v...)
}

11
deps/deps.go vendored
View File

@ -34,10 +34,7 @@ type Deps struct {
Log loggers.Logger `json:"-"`
// Used to log errors that may repeat itself many times.
DistinctErrorLog *helpers.DistinctLogger
// Used to log warnings that may repeat itself many times.
DistinctWarningLog *helpers.DistinctLogger
LogDistinct loggers.Logger
// The templates to use. This will usually implement the full tpl.TemplateManager.
tmpl tpl.TemplateHandler
@ -266,14 +263,12 @@ func New(cfg DepsCfg) (*Deps, error) {
ignoreErrors := cast.ToStringSlice(cfg.Cfg.Get("ignoreErrors"))
ignorableLogger := loggers.NewIgnorableLogger(logger, ignoreErrors...)
distinctErrorLogger := helpers.NewDistinctLogger(logger.Error())
distinctWarnLogger := helpers.NewDistinctLogger(logger.Warn())
logDistinct := helpers.NewDistinctLogger(logger)
d := &Deps{
Fs: fs,
Log: ignorableLogger,
DistinctErrorLog: distinctErrorLogger,
DistinctWarningLog: distinctWarnLogger,
LogDistinct: logDistinct,
templateProvider: cfg.TemplateProvider,
translationProvider: cfg.TranslationProvider,
WithTemplate: cfg.WithTemplate,

View File

@ -31,3 +31,22 @@ Both functions return an empty string, so the messages are only printed to the c
```
Note that `errorf` and `warnf` support all the formatting verbs of the [fmt](https://golang.org/pkg/fmt/) package.
## Suppress errors
Some times it may make sense to let the user suppress an ERROR and make the build succeed.
You can do this by using the `erroridf` function. This functions takes an error ID as the first arument.
``
{{ erroridf "my-custom-error" "You should consider fixing this."}}
```
This will produce:
```
ERROR 2021/06/07 17:47:38 You should consider fixing this.
If you feel that this should not be logged as an ERROR, you can ignore it by adding this to your site config:
ignoreErrors = ["my-custom-error"]
```

View File

@ -29,6 +29,8 @@ import (
"unicode"
"unicode/utf8"
"github.com/gohugoio/hugo/common/loggers"
"github.com/mitchellh/hashstructure"
"github.com/gohugoio/hugo/hugofs"
@ -40,7 +42,6 @@ import (
"github.com/jdkato/prose/transform"
bp "github.com/gohugoio/hugo/bufferpool"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/pflag"
)
@ -253,9 +254,9 @@ type LogPrinter interface {
// DistinctLogger ignores duplicate log statements.
type DistinctLogger struct {
loggers.Logger
sync.RWMutex
getLogger func() LogPrinter
m map[string]bool
m map[string]bool
}
func (l *DistinctLogger) Reset() {
@ -270,52 +271,105 @@ func (l *DistinctLogger) Reset() {
func (l *DistinctLogger) Println(v ...interface{}) {
// fmt.Sprint doesn't add space between string arguments
logStatement := strings.TrimSpace(fmt.Sprintln(v...))
l.print(logStatement)
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.
// Note: A newline is appended.
func (l *DistinctLogger) Printf(format string, v ...interface{}) {
logStatement := fmt.Sprintf(format, v...)
l.print(logStatement)
l.printIfNotPrinted("printf", logStatement, func() {
l.Logger.Printf(format, v...)
})
}
func (l *DistinctLogger) print(logStatement string) {
func (l *DistinctLogger) Debugf(format string, v ...interface{}) {
logStatement := fmt.Sprintf(format, v...)
l.printIfNotPrinted("debugf", logStatement, func() {
l.Logger.Debugf(format, v...)
})
}
func (l *DistinctLogger) Debugln(v ...interface{}) {
logStatement := fmt.Sprint(v...)
l.printIfNotPrinted("debugln", logStatement, func() {
l.Logger.Debugln(v...)
})
}
func (l *DistinctLogger) Infof(format string, v ...interface{}) {
logStatement := fmt.Sprintf(format, v...)
l.printIfNotPrinted("info", logStatement, func() {
l.Logger.Infof(format, v...)
})
}
func (l *DistinctLogger) Infoln(v ...interface{}) {
logStatement := fmt.Sprint(v...)
l.printIfNotPrinted("infoln", logStatement, func() {
l.Logger.Infoln(v...)
})
}
func (l *DistinctLogger) Warnf(format string, v ...interface{}) {
logStatement := fmt.Sprintf(format, v...)
l.printIfNotPrinted("warnf", logStatement, func() {
l.Logger.Warnf(format, v...)
})
}
func (l *DistinctLogger) Warnln(v ...interface{}) {
logStatement := fmt.Sprint(v...)
l.printIfNotPrinted("warnln", logStatement, func() {
l.Logger.Warnln(v...)
})
}
func (l *DistinctLogger) Errorf(format string, v ...interface{}) {
logStatement := fmt.Sprint(v...)
l.printIfNotPrinted("errorf", logStatement, func() {
l.Logger.Errorf(format, v...)
})
}
func (l *DistinctLogger) Errorln(v ...interface{}) {
logStatement := fmt.Sprint(v...)
l.printIfNotPrinted("errorln", logStatement, func() {
l.Logger.Errorln(v...)
})
}
func (l *DistinctLogger) hasPrinted(key string) bool {
l.RLock()
if l.m[logStatement] {
l.RUnlock()
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.RUnlock()
l.Lock()
if !l.m[logStatement] {
l.getLogger().Println(logStatement)
l.m[logStatement] = true
}
print()
l.m[key] = true
l.Unlock()
}
// NewDistinctErrorLogger creates a new DistinctLogger that logs ERRORs
func NewDistinctErrorLogger() *DistinctLogger {
return &DistinctLogger{m: make(map[string]bool), getLogger: func() LogPrinter { return jww.ERROR }}
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 LogPrinter) *DistinctLogger {
return &DistinctLogger{m: make(map[string]bool), getLogger: func() LogPrinter { return 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() *DistinctLogger {
return &DistinctLogger{m: make(map[string]bool), getLogger: func() LogPrinter { return jww.WARN }}
}
// NewDistinctFeedbackLogger creates a new DistinctLogger that can be used
// to give feedback to the user while not spamming with duplicates.
func NewDistinctFeedbackLogger() *DistinctLogger {
return &DistinctLogger{m: make(map[string]bool), getLogger: func() LogPrinter { return jww.FEEDBACK }}
func NewDistinctWarnLogger() loggers.Logger {
return &DistinctLogger{m: make(map[string]bool), Logger: loggers.NewWarningLogger()}
}
var (
@ -324,16 +378,13 @@ var (
// DistinctWarnLog can be used to avoid spamming the logs with warnings.
DistinctWarnLog = NewDistinctWarnLogger()
// DistinctFeedbackLog can be used to avoid spamming the logs with info messages.
DistinctFeedbackLog = NewDistinctFeedbackLogger()
)
// InitLoggers resets the global distinct loggers.
func InitLoggers() {
DistinctErrorLog.Reset()
DistinctWarnLog.Reset()
DistinctFeedbackLog.Reset()
}
// Deprecated informs about a deprecation, but only once for a given set of arguments' values.

View File

@ -79,7 +79,7 @@ func (s *Site) writeDestAlias(path, permalink string, outputFormat output.Format
func (s *Site) publishDestAlias(allowRoot bool, path, permalink string, outputFormat output.Format, p page.Page) (err error) {
handler := newAliasHandler(s.Tmpl(), s.Log, allowRoot)
s.Log.Debug().Println("creating alias:", path, "redirecting to", permalink)
s.Log.Debugln("creating alias:", path, "redirecting to", permalink)
targetPath, err := handler.targetPathAlias(path)
if err != nil {

View File

@ -28,7 +28,6 @@ var (
// implementations have no value on their own.
// Slice is not meant to be used externally. It's a bridge function
// for the template functions. See collections.Slice.
func (p *pageState) Slice(items interface{}) (interface{}, error) {
return page.ToPages(items)
}

View File

@ -579,7 +579,8 @@ func (h *HugoSites) resetLogs() {
h.Log.Reset()
loggers.GlobalErrorCounter.Reset()
for _, s := range h.Sites {
s.Deps.DistinctErrorLog = helpers.NewDistinctLogger(h.Log.Error())
s.Deps.Log.Reset()
s.Deps.LogDistinct.Reset()
}
}

View File

@ -102,7 +102,7 @@ func newPageFromMeta(
meta map[string]interface{},
metaProvider *pageMeta) (*pageState, error) {
if metaProvider.f == nil {
metaProvider.f = page.NewZeroFile(metaProvider.s.DistinctWarningLog)
metaProvider.f = page.NewZeroFile(metaProvider.s.LogDistinct)
}
ps, err := newPageBase(metaProvider)

View File

@ -1007,7 +1007,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
changeIdentities := make(identity.Identities)
s.Log.Debug().Printf("Rebuild for events %q", events)
s.Log.Debugf("Rebuild for events %q", events)
h := s.h
@ -1026,7 +1026,7 @@ func (s *Site) processPartial(config *BuildCfg, init func(config *BuildCfg) erro
sourceFilesChanged = make(map[string]bool)
// prevent spamming the log on changes
logger = helpers.NewDistinctFeedbackLogger()
logger = helpers.NewDistinctErrorLogger()
)
var cachePartitions []string
@ -1385,7 +1385,7 @@ func (s *Site) getMenusFromConfig() navigation.Menus {
s.Log.Errorln(err)
} else {
for _, entry := range m {
s.Log.Debug().Printf("found menu: %q, in site config\n", name)
s.Log.Debugf("found menu: %q, in site config\n", name)
menuEntry := navigation.MenuEntry{Menu: name}
ime, err := maps.ToStringMapE(entry)
@ -1646,7 +1646,7 @@ func (s *Site) lookupLayouts(layouts ...string) tpl.Template {
}
func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath string, d interface{}, templ tpl.Template) error {
s.Log.Debug().Printf("Render XML for %q to %q", name, targetPath)
s.Log.Debugf("Render XML for %q to %q", name, targetPath)
renderBuffer := bp.GetBuffer()
defer bp.PutBuffer(renderBuffer)
@ -1668,7 +1668,7 @@ func (s *Site) renderAndWriteXML(statCounter *uint64, name string, targetPath st
}
func (s *Site) renderAndWritePage(statCounter *uint64, name string, targetPath string, p *pageState, templ tpl.Template) error {
s.Log.Debug().Printf("Render %s to %q", name, targetPath)
s.Log.Debugf("Render %s to %q", name, targetPath)
renderBuffer := bp.GetBuffer()
defer bp.PutBuffer(renderBuffer)

View File

@ -389,13 +389,13 @@ func (s *Site) renderMainLanguageRedirect() error {
mainLang := s.h.multilingual.DefaultLang
if s.Info.defaultContentLanguageInSubdir {
mainLangURL := s.PathSpec.AbsURL(mainLang.Lang+"/", false)
s.Log.Debug().Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
s.Log.Debugf("Write redirect to main language %s: %s", mainLang, mainLangURL)
if err := s.publishDestAlias(true, "/", mainLangURL, html, nil); err != nil {
return err
}
} else {
mainLangURL := s.PathSpec.AbsURL("", false)
s.Log.Debug().Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
s.Log.Debugf("Write redirect to main language %s: %s", mainLang, mainLangURL)
if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, html, nil); err != nil {
return err
}

View File

@ -30,7 +30,7 @@ import (
type translateFunc func(translationID string, templateData interface{}) string
var i18nWarningLogger = helpers.NewDistinctFeedbackLogger()
var i18nWarningLogger = helpers.NewDistinctErrorLogger()
// Translator handles i18n translations.
type Translator struct {

View File

@ -16,17 +16,17 @@
package page
import (
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/source"
)
// ZeroFile represents a zero value of source.File with warnings if invoked.
type zeroFile struct {
log *helpers.DistinctLogger
log loggers.Logger
}
func NewZeroFile(log *helpers.DistinctLogger) source.File {
func NewZeroFile(log loggers.Logger) source.File {
return zeroFile{log: log}
}

View File

@ -380,7 +380,7 @@ func (ns *Namespace) IsSet(a interface{}, key interface{}) (bool, error) {
return av.MapIndex(kv).IsValid(), nil
}
default:
helpers.DistinctFeedbackLog.Printf("WARNING: calling IsSet with unsupported type %q (%T) will always return false.\n", av.Kind(), a)
helpers.DistinctErrorLog.Printf("WARNING: calling IsSet with unsupported type %q (%T) will always return false.\n", av.Kind(), a)
}
return false, nil

View File

@ -213,12 +213,12 @@ func newDeps(cfg config.Provider) *deps.Deps {
}
return &deps.Deps{
Cfg: cfg,
Fs: fs,
FileCaches: fileCaches,
ContentSpec: cs,
Log: logger,
DistinctErrorLog: helpers.NewDistinctLogger(logger.Error()),
Cfg: cfg,
Fs: fs,
FileCaches: fileCaches,
ContentSpec: cs,
Log: logger,
LogDistinct: helpers.NewDistinctLogger(logger),
}
}

View File

@ -17,20 +17,22 @@ package fmt
import (
_fmt "fmt"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/deps"
"github.com/gohugoio/hugo/helpers"
)
// New returns a new instance of the fmt-namespaced template functions.
func New(d *deps.Deps) *Namespace {
ignorableLogger := d.Log.(loggers.IgnorableLogger)
distinctLogger := helpers.NewDistinctLogger(d.Log)
ns := &Namespace{
errorLogger: helpers.NewDistinctLogger(d.Log.Error()),
warnLogger: helpers.NewDistinctLogger(d.Log.Warn()),
distinctLogger: ignorableLogger.Apply(distinctLogger),
}
d.BuildStartListeners.Add(func() {
ns.errorLogger.Reset()
ns.warnLogger.Reset()
ns.distinctLogger.Reset()
})
return ns
@ -38,8 +40,7 @@ func New(d *deps.Deps) *Namespace {
// Namespace provides template functions for the "fmt" namespace.
type Namespace struct {
errorLogger *helpers.DistinctLogger
warnLogger *helpers.DistinctLogger
distinctLogger loggers.IgnorableLogger
}
// Print returns string representation of the passed arguments.
@ -60,13 +61,21 @@ func (ns *Namespace) Println(a ...interface{}) string {
// Errorf formats according to a format specifier and logs an ERROR.
// It returns an empty string.
func (ns *Namespace) Errorf(format string, a ...interface{}) string {
ns.errorLogger.Printf(format, a...)
ns.distinctLogger.Errorf(format, a...)
return ""
}
// Erroridf formats according to a format specifier and logs an ERROR and
// 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, a ...interface{}) string {
ns.distinctLogger.Errorsf(id, format, a...)
return ""
}
// Warnf formats according to a format specifier and logs a WARNING.
// It returns an empty string.
func (ns *Namespace) Warnf(format string, a ...interface{}) string {
ns.warnLogger.Printf(format, a...)
ns.distinctLogger.Warnf(format, a...)
return ""
}

View File

@ -57,6 +57,13 @@ func init() {
},
)
ns.AddMethodMapping(ctx.Erroridf,
[]string{"erroridf"},
[][2]string{
{`{{ erroridf "my-err-id" "%s." "failed" }}`, ``},
},
)
ns.AddMethodMapping(ctx.Warnf,
[]string{"warnf"},
[][2]string{

View File

@ -30,7 +30,7 @@ func TestInit(t *testing.T) {
var ns *internal.TemplateFuncsNamespace
for _, nsf := range internal.TemplateFuncsNamespaceRegistry {
ns = nsf(&deps.Deps{Log: loggers.NewErrorLogger()})
ns = nsf(&deps.Deps{Log: loggers.NewIgnorableLogger(loggers.NewErrorLogger())})
if ns.Name == name {
found = true
break