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