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