github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/common/loggers/loggers.go (about) 1 // Copyright 2020 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package loggers 15 16 import ( 17 "bytes" 18 "fmt" 19 "io" 20 "log" 21 "os" 22 "regexp" 23 "runtime" 24 "time" 25 26 "github.com/gohugoio/hugo/common/terminal" 27 28 jww "github.com/spf13/jwalterweatherman" 29 ) 30 31 var ( 32 // Counts ERROR logs to the global jww logger. 33 GlobalErrorCounter *jww.Counter 34 PanicOnWarning bool 35 ) 36 37 func init() { 38 GlobalErrorCounter = &jww.Counter{} 39 jww.SetLogListeners(jww.LogCounter(GlobalErrorCounter, jww.LevelError)) 40 } 41 42 func LoggerToWriterWithPrefix(logger *log.Logger, prefix string) io.Writer { 43 return prefixWriter{ 44 logger: logger, 45 prefix: prefix, 46 } 47 } 48 49 type prefixWriter struct { 50 logger *log.Logger 51 prefix string 52 } 53 54 func (w prefixWriter) Write(p []byte) (n int, err error) { 55 w.logger.Printf("%s: %s", w.prefix, p) 56 return len(p), nil 57 } 58 59 type Logger interface { 60 Printf(format string, v ...any) 61 Println(v ...any) 62 PrintTimerIfDelayed(start time.Time, name string) 63 Debug() *log.Logger 64 Debugf(format string, v ...any) 65 Debugln(v ...any) 66 Info() *log.Logger 67 Infof(format string, v ...any) 68 Infoln(v ...any) 69 Warn() *log.Logger 70 Warnf(format string, v ...any) 71 Warnln(v ...any) 72 Error() *log.Logger 73 Errorf(format string, v ...any) 74 Errorln(v ...any) 75 Errors() string 76 77 Out() io.Writer 78 79 Reset() 80 81 // Used in tests. 82 LogCounters() *LogCounters 83 } 84 85 type LogCounters struct { 86 ErrorCounter *jww.Counter 87 WarnCounter *jww.Counter 88 } 89 90 type logger struct { 91 *jww.Notepad 92 93 // The writer that represents stdout. 94 // Will be io.Discard when in quiet mode. 95 out io.Writer 96 97 logCounters *LogCounters 98 99 // This is only set in server mode. 100 errors *bytes.Buffer 101 } 102 103 func (l *logger) Printf(format string, v ...any) { 104 l.FEEDBACK.Printf(format, v...) 105 } 106 107 func (l *logger) Println(v ...any) { 108 l.FEEDBACK.Println(v...) 109 } 110 111 func (l *logger) Debug() *log.Logger { 112 return l.DEBUG 113 } 114 115 func (l *logger) Debugf(format string, v ...any) { 116 l.DEBUG.Printf(format, v...) 117 } 118 119 func (l *logger) Debugln(v ...any) { 120 l.DEBUG.Println(v...) 121 } 122 123 func (l *logger) Infof(format string, v ...any) { 124 l.INFO.Printf(format, v...) 125 } 126 127 func (l *logger) Infoln(v ...any) { 128 l.INFO.Println(v...) 129 } 130 131 func (l *logger) Info() *log.Logger { 132 return l.INFO 133 } 134 135 const panicOnWarningMessage = "Warning trapped. Remove the --panicOnWarning flag to continue." 136 137 func (l *logger) Warnf(format string, v ...any) { 138 l.WARN.Printf(format, v...) 139 if PanicOnWarning { 140 panic(panicOnWarningMessage) 141 } 142 } 143 144 func (l *logger) Warnln(v ...any) { 145 l.WARN.Println(v...) 146 if PanicOnWarning { 147 panic(panicOnWarningMessage) 148 } 149 } 150 151 func (l *logger) Warn() *log.Logger { 152 return l.WARN 153 } 154 155 func (l *logger) Errorf(format string, v ...any) { 156 l.ERROR.Printf(format, v...) 157 } 158 159 func (l *logger) Errorln(v ...any) { 160 l.ERROR.Println(v...) 161 } 162 163 func (l *logger) Error() *log.Logger { 164 return l.ERROR 165 } 166 167 func (l *logger) LogCounters() *LogCounters { 168 return l.logCounters 169 } 170 171 func (l *logger) Out() io.Writer { 172 return l.out 173 } 174 175 // PrintTimerIfDelayed prints a time statement to the FEEDBACK logger 176 // if considerable time is spent. 177 func (l *logger) PrintTimerIfDelayed(start time.Time, name string) { 178 elapsed := time.Since(start) 179 milli := int(1000 * elapsed.Seconds()) 180 if milli < 500 { 181 return 182 } 183 l.Printf("%s in %v ms", name, milli) 184 } 185 186 func (l *logger) PrintTimer(start time.Time, name string) { 187 elapsed := time.Since(start) 188 milli := int(1000 * elapsed.Seconds()) 189 l.Printf("%s in %v ms", name, milli) 190 } 191 192 func (l *logger) Errors() string { 193 if l.errors == nil { 194 return "" 195 } 196 return ansiColorRe.ReplaceAllString(l.errors.String(), "") 197 } 198 199 // Reset resets the logger's internal state. 200 func (l *logger) Reset() { 201 l.logCounters.ErrorCounter.Reset() 202 if l.errors != nil { 203 l.errors.Reset() 204 } 205 } 206 207 // NewLogger creates a new Logger for the given thresholds 208 func NewLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) Logger { 209 return newLogger(stdoutThreshold, logThreshold, outHandle, logHandle, saveErrors) 210 } 211 212 // NewDebugLogger is a convenience function to create a debug logger. 213 func NewDebugLogger() Logger { 214 return NewBasicLogger(jww.LevelDebug) 215 } 216 217 // NewWarningLogger is a convenience function to create a warning logger. 218 func NewWarningLogger() Logger { 219 return NewBasicLogger(jww.LevelWarn) 220 } 221 222 // NewInfoLogger is a convenience function to create a info logger. 223 func NewInfoLogger() Logger { 224 return NewBasicLogger(jww.LevelInfo) 225 } 226 227 // NewErrorLogger is a convenience function to create an error logger. 228 func NewErrorLogger() Logger { 229 return NewBasicLogger(jww.LevelError) 230 } 231 232 // NewBasicLogger creates a new basic logger writing to Stdout. 233 func NewBasicLogger(t jww.Threshold) Logger { 234 return newLogger(t, jww.LevelError, os.Stdout, io.Discard, false) 235 } 236 237 // NewBasicLoggerForWriter creates a new basic logger writing to w. 238 func NewBasicLoggerForWriter(t jww.Threshold, w io.Writer) Logger { 239 return newLogger(t, jww.LevelError, w, io.Discard, false) 240 } 241 242 // RemoveANSIColours removes all ANSI colours from the given string. 243 func RemoveANSIColours(s string) string { 244 return ansiColorRe.ReplaceAllString(s, "") 245 } 246 247 var ( 248 ansiColorRe = regexp.MustCompile("(?s)\\033\\[\\d*(;\\d*)*m") 249 errorRe = regexp.MustCompile("^(ERROR|FATAL|WARN)") 250 ) 251 252 type ansiCleaner struct { 253 w io.Writer 254 } 255 256 func (a ansiCleaner) Write(p []byte) (n int, err error) { 257 return a.w.Write(ansiColorRe.ReplaceAll(p, []byte(""))) 258 } 259 260 type labelColorizer struct { 261 w io.Writer 262 } 263 264 func (a labelColorizer) Write(p []byte) (n int, err error) { 265 replaced := errorRe.ReplaceAllStringFunc(string(p), func(m string) string { 266 switch m { 267 case "ERROR", "FATAL": 268 return terminal.Error(m) 269 case "WARN": 270 return terminal.Warning(m) 271 default: 272 return m 273 } 274 }) 275 // io.MultiWriter will abort if we return a bigger write count than input 276 // bytes, so we lie a little. 277 _, err = a.w.Write([]byte(replaced)) 278 return len(p), err 279 } 280 281 // InitGlobalLogger initializes the global logger, used in some rare cases. 282 func InitGlobalLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer) { 283 outHandle, logHandle = getLogWriters(outHandle, logHandle) 284 285 jww.SetStdoutOutput(outHandle) 286 jww.SetLogOutput(logHandle) 287 jww.SetLogThreshold(logThreshold) 288 jww.SetStdoutThreshold(stdoutThreshold) 289 } 290 291 func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) { 292 isTerm := terminal.PrintANSIColors(os.Stdout) 293 if logHandle != io.Discard && isTerm { 294 // Remove any Ansi coloring from log output 295 logHandle = ansiCleaner{w: logHandle} 296 } 297 298 if isTerm { 299 outHandle = labelColorizer{w: outHandle} 300 } 301 302 return outHandle, logHandle 303 } 304 305 type fatalLogWriter int 306 307 func (s fatalLogWriter) Write(p []byte) (n int, err error) { 308 trace := make([]byte, 1500) 309 runtime.Stack(trace, true) 310 fmt.Printf("\n===========\n\n%s\n", trace) 311 os.Exit(-1) 312 313 return 0, nil 314 } 315 316 var fatalLogListener = func(t jww.Threshold) io.Writer { 317 if t != jww.LevelError { 318 // Only interested in ERROR 319 return nil 320 } 321 322 return new(fatalLogWriter) 323 } 324 325 func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *logger { 326 errorCounter := &jww.Counter{} 327 warnCounter := &jww.Counter{} 328 outHandle, logHandle = getLogWriters(outHandle, logHandle) 329 330 listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError), jww.LogCounter(warnCounter, jww.LevelWarn)} 331 var errorBuff *bytes.Buffer 332 if saveErrors { 333 errorBuff = new(bytes.Buffer) 334 errorCapture := func(t jww.Threshold) io.Writer { 335 if t != jww.LevelError { 336 // Only interested in ERROR 337 return nil 338 } 339 return errorBuff 340 } 341 342 listeners = append(listeners, errorCapture) 343 } 344 345 return &logger{ 346 Notepad: jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime, listeners...), 347 out: outHandle, 348 logCounters: &LogCounters{ 349 ErrorCounter: errorCounter, 350 WarnCounter: warnCounter, 351 }, 352 errors: errorBuff, 353 } 354 }