mirror of
https://github.com/gohugoio/hugo.git
synced 2025-08-29 22:29:56 +02:00
106
common/loggers/handlerdefault.go
Normal file
106
common/loggers/handlerdefault.go
Normal 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
|
||||
}
|
158
common/loggers/handlersmisc.go
Normal file
158
common/loggers/handlersmisc.go
Normal 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
|
||||
})
|
||||
}
|
90
common/loggers/handlerterminal.go
Normal file
90
common/loggers/handlerterminal.go
Normal 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
|
||||
}
|
@@ -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
303
common/loggers/logger.go
Normal 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
|
||||
}
|
156
common/loggers/logger_test.go
Normal file
156
common/loggers/logger_test.go
Normal 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)
|
||||
|
||||
}
|
||||
}
|
53
common/loggers/loggerglobal.go
Normal file
53
common/loggers/loggerglobal.go
Normal 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)
|
||||
}
|
@@ -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,
|
||||
}
|
||||
}
|
@@ -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 |")
|
||||
}
|
Reference in New Issue
Block a user