github.com/neohugo/neohugo@v0.123.8/common/loggers/logger.go (about) 1 // Copyright 2024 The Hugo Authors. All rights reserved. 2 // Some functions in this file (see comments) is based on the Go source code, 3 // copyright The Go Authors and governed by a BSD-style license. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package loggers 17 18 import ( 19 "fmt" 20 "io" 21 "os" 22 "strings" 23 "time" 24 25 "github.com/bep/logg" 26 "github.com/bep/logg/handlers/multi" 27 "github.com/neohugo/neohugo/common/terminal" 28 ) 29 30 var ( 31 reservedFieldNamePrefix = "__h_field_" 32 // FieldNameCmd is the name of the field that holds the command name. 33 FieldNameCmd = reservedFieldNamePrefix + "_cmd" 34 // Used to suppress statements. 35 FieldNameStatementID = reservedFieldNamePrefix + "__h_field_statement_id" 36 ) 37 38 // Options defines options for the logger. 39 type Options struct { 40 Level logg.Level 41 Stdout io.Writer 42 Stderr io.Writer 43 DistinctLevel logg.Level 44 StoreErrors bool 45 HandlerPost func(e *logg.Entry) error 46 SuppressStatements map[string]bool 47 } 48 49 // New creates a new logger with the given options. 50 func New(opts Options) Logger { 51 if opts.Stdout == nil { 52 opts.Stdout = os.Stdout 53 } 54 if opts.Stderr == nil { 55 opts.Stderr = os.Stdout 56 } 57 if opts.Level == 0 { 58 opts.Level = logg.LevelWarn 59 } 60 61 var logHandler logg.Handler 62 if terminal.PrintANSIColors(os.Stdout) { 63 logHandler = newDefaultHandler(opts.Stdout, opts.Stderr) 64 } else { 65 logHandler = newNoColoursHandler(opts.Stdout, opts.Stderr, false, nil) 66 } 67 68 errorsw := &strings.Builder{} 69 logCounters := newLogLevelCounter() 70 handlers := []logg.Handler{ 71 logCounters, 72 } 73 74 if opts.Level == logg.LevelTrace { 75 // Trace is used during development only, and it's useful to 76 // only see the trace messages. 77 handlers = append(handlers, 78 logg.HandlerFunc(func(e *logg.Entry) error { 79 if e.Level != logg.LevelTrace { 80 return logg.ErrStopLogEntry 81 } 82 return nil 83 }), 84 ) 85 } 86 87 handlers = append(handlers, whiteSpaceTrimmer(), logHandler) 88 89 if opts.HandlerPost != nil { 90 var hookHandler logg.HandlerFunc = func(e *logg.Entry) error { 91 opts.HandlerPost(e) // nolint 92 return nil 93 } 94 handlers = append(handlers, hookHandler) 95 } 96 97 if opts.StoreErrors { 98 h := newNoColoursHandler(io.Discard, errorsw, true, func(e *logg.Entry) bool { 99 return e.Level >= logg.LevelError 100 }) 101 102 handlers = append(handlers, h) 103 } 104 105 logHandler = multi.New(handlers...) 106 107 var logOnce *logOnceHandler 108 if opts.DistinctLevel != 0 { 109 logOnce = newLogOnceHandler(opts.DistinctLevel) 110 logHandler = newStopHandler(logOnce, logHandler) 111 } 112 113 if opts.SuppressStatements != nil && len(opts.SuppressStatements) > 0 { 114 logHandler = newStopHandler(newSuppressStatementsHandler(opts.SuppressStatements), logHandler) 115 } 116 117 logger := logg.New( 118 logg.Options{ 119 Level: opts.Level, 120 Handler: logHandler, 121 }, 122 ) 123 124 l := logger.WithLevel(opts.Level) 125 126 reset := func() { 127 logCounters.mu.Lock() 128 defer logCounters.mu.Unlock() 129 logCounters.counters = make(map[logg.Level]int) 130 errorsw.Reset() 131 if logOnce != nil { 132 logOnce.reset() 133 } 134 } 135 136 return &logAdapter{ 137 logCounters: logCounters, 138 errors: errorsw, 139 reset: reset, 140 out: opts.Stdout, 141 level: opts.Level, 142 logger: logger, 143 tracel: l.WithLevel(logg.LevelTrace), 144 debugl: l.WithLevel(logg.LevelDebug), 145 infol: l.WithLevel(logg.LevelInfo), 146 warnl: l.WithLevel(logg.LevelWarn), 147 errorl: l.WithLevel(logg.LevelError), 148 } 149 } 150 151 // NewDefault creates a new logger with the default options. 152 func NewDefault() Logger { 153 opts := Options{ 154 DistinctLevel: logg.LevelWarn, 155 Level: logg.LevelWarn, 156 Stdout: os.Stdout, 157 Stderr: os.Stdout, 158 } 159 return New(opts) 160 } 161 162 func NewTrace() Logger { 163 opts := Options{ 164 DistinctLevel: logg.LevelWarn, 165 Level: logg.LevelTrace, 166 Stdout: os.Stdout, 167 Stderr: os.Stdout, 168 } 169 return New(opts) 170 } 171 172 func LevelLoggerToWriter(l logg.LevelLogger) io.Writer { 173 return logWriter{l: l} 174 } 175 176 type Logger interface { 177 Debug() logg.LevelLogger 178 Debugf(format string, v ...any) 179 Debugln(v ...any) 180 Error() logg.LevelLogger 181 Errorf(format string, v ...any) 182 Erroridf(id, format string, v ...any) 183 Errorln(v ...any) 184 Errors() string 185 Info() logg.LevelLogger 186 InfoCommand(command string) logg.LevelLogger 187 Infof(format string, v ...any) 188 Infoln(v ...any) 189 Level() logg.Level 190 LoggCount(logg.Level) int 191 Logger() logg.Logger 192 Out() io.Writer 193 Printf(format string, v ...any) 194 Println(v ...any) 195 PrintTimerIfDelayed(start time.Time, name string) 196 Reset() 197 Warn() logg.LevelLogger 198 WarnCommand(command string) logg.LevelLogger 199 Warnf(format string, v ...any) 200 Warnidf(id, format string, v ...any) 201 Warnln(v ...any) 202 Deprecatef(fail bool, format string, v ...any) 203 Trace(s logg.StringFunc) 204 } 205 206 type logAdapter struct { 207 logCounters *logLevelCounter 208 errors *strings.Builder 209 reset func() 210 out io.Writer 211 level logg.Level 212 logger logg.Logger 213 tracel logg.LevelLogger 214 debugl logg.LevelLogger 215 infol logg.LevelLogger 216 warnl logg.LevelLogger 217 errorl logg.LevelLogger 218 } 219 220 func (l *logAdapter) Debug() logg.LevelLogger { 221 return l.debugl 222 } 223 224 func (l *logAdapter) Debugf(format string, v ...any) { 225 l.debugl.Logf(format, v...) 226 } 227 228 func (l *logAdapter) Debugln(v ...any) { 229 l.debugl.Logf(l.sprint(v...)) 230 } 231 232 func (l *logAdapter) Info() logg.LevelLogger { 233 return l.infol 234 } 235 236 func (l *logAdapter) InfoCommand(command string) logg.LevelLogger { 237 return l.infol.WithField(FieldNameCmd, command) 238 } 239 240 func (l *logAdapter) Infof(format string, v ...any) { 241 l.infol.Logf(format, v...) 242 } 243 244 func (l *logAdapter) Infoln(v ...any) { 245 l.infol.Logf(l.sprint(v...)) 246 } 247 248 func (l *logAdapter) Level() logg.Level { 249 return l.level 250 } 251 252 func (l *logAdapter) LoggCount(level logg.Level) int { 253 l.logCounters.mu.RLock() 254 defer l.logCounters.mu.RUnlock() 255 return l.logCounters.counters[level] 256 } 257 258 func (l *logAdapter) Logger() logg.Logger { 259 return l.logger 260 } 261 262 func (l *logAdapter) Out() io.Writer { 263 return l.out 264 } 265 266 // PrintTimerIfDelayed prints a time statement to the FEEDBACK logger 267 // if considerable time is spent. 268 func (l *logAdapter) PrintTimerIfDelayed(start time.Time, name string) { 269 elapsed := time.Since(start) 270 milli := int(1000 * elapsed.Seconds()) 271 if milli < 500 { 272 return 273 } 274 l.Printf("%s in %v ms", name, milli) 275 } 276 277 func (l *logAdapter) Printf(format string, v ...any) { 278 // Add trailing newline if not present. 279 if !strings.HasSuffix(format, "\n") { 280 format += "\n" 281 } 282 fmt.Fprintf(l.out, format, v...) 283 } 284 285 func (l *logAdapter) Println(v ...any) { 286 fmt.Fprintln(l.out, v...) 287 } 288 289 func (l *logAdapter) Reset() { 290 l.reset() 291 } 292 293 func (l *logAdapter) Warn() logg.LevelLogger { 294 return l.warnl 295 } 296 297 func (l *logAdapter) Warnf(format string, v ...any) { 298 l.warnl.Logf(format, v...) 299 } 300 301 func (l *logAdapter) WarnCommand(command string) logg.LevelLogger { 302 return l.warnl.WithField(FieldNameCmd, command) 303 } 304 305 func (l *logAdapter) Warnln(v ...any) { 306 l.warnl.Logf(l.sprint(v...)) 307 } 308 309 func (l *logAdapter) Error() logg.LevelLogger { 310 return l.errorl 311 } 312 313 func (l *logAdapter) Errorf(format string, v ...any) { 314 l.errorl.Logf(format, v...) 315 } 316 317 func (l *logAdapter) Errorln(v ...any) { 318 l.errorl.Logf(l.sprint(v...)) 319 } 320 321 func (l *logAdapter) Errors() string { 322 return l.errors.String() 323 } 324 325 func (l *logAdapter) Erroridf(id, format string, v ...any) { 326 format += l.idfInfoStatement("error", id, format) 327 l.errorl.WithField(FieldNameStatementID, id).Logf(format, v...) 328 } 329 330 func (l *logAdapter) Warnidf(id, format string, v ...any) { 331 format += l.idfInfoStatement("warning", id, format) 332 l.warnl.WithField(FieldNameStatementID, id).Logf(format, v...) 333 } 334 335 func (l *logAdapter) idfInfoStatement(what, id, format string) string { 336 return fmt.Sprintf("\nYou can suppress this %s by adding the following to your site configuration:\nignoreLogs = ['%s']", what, id) 337 } 338 339 func (l *logAdapter) Trace(s logg.StringFunc) { 340 l.tracel.Log(s) 341 } 342 343 func (l *logAdapter) sprint(v ...any) string { 344 return strings.TrimRight(fmt.Sprintln(v...), "\n") 345 } 346 347 func (l *logAdapter) Deprecatef(fail bool, format string, v ...any) { 348 format = "DEPRECATED: " + format 349 if fail { 350 l.errorl.Logf(format, v...) 351 } else { 352 l.warnl.Logf(format, v...) 353 } 354 } 355 356 type logWriter struct { 357 l logg.LevelLogger 358 } 359 360 func (w logWriter) Write(p []byte) (n int, err error) { 361 w.l.Log(logg.String(string(p))) 362 return len(p), nil 363 } 364 365 func TimeTrackf(l logg.LevelLogger, start time.Time, fields logg.Fields, format string, a ...any) { 366 elapsed := time.Since(start) 367 if fields != nil { 368 l = l.WithFields(fields) 369 } 370 l.WithField("duration", elapsed).Logf(format, a...) 371 } 372 373 func TimeTrackfn(fn func() (logg.LevelLogger, error)) error { 374 start := time.Now() 375 l, err := fn() 376 elapsed := time.Since(start) 377 l.WithField("duration", elapsed).Logf("") 378 return err 379 }