loggers.go (8456B)
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 ...any)
62 Println(v ...any)
63 PrintTimerIfDelayed(start time.Time, name string)
64 Debug() *log.Logger
65 Debugf(format string, v ...any)
66 Debugln(v ...any)
67 Info() *log.Logger
68 Infof(format string, v ...any)
69 Infoln(v ...any)
70 Warn() *log.Logger
71 Warnf(format string, v ...any)
72 Warnln(v ...any)
73 Error() *log.Logger
74 Errorf(format string, v ...any)
75 Errorln(v ...any)
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 ...any) {
105 l.FEEDBACK.Printf(format, v...)
106 }
107
108 func (l *logger) Println(v ...any) {
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 ...any) {
117 l.DEBUG.Printf(format, v...)
118 }
119
120 func (l *logger) Debugln(v ...any) {
121 l.DEBUG.Println(v...)
122 }
123
124 func (l *logger) Infof(format string, v ...any) {
125 l.INFO.Printf(format, v...)
126 }
127
128 func (l *logger) Infoln(v ...any) {
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 ...any) {
139 l.WARN.Printf(format, v...)
140 if PanicOnWarning {
141 panic(panicOnWarningMessage)
142 }
143 }
144
145 func (l *logger) Warnln(v ...any) {
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 ...any) {
157 l.ERROR.Printf(format, v...)
158 }
159
160 func (l *logger) Errorln(v ...any) {
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 // RemoveANSIColours removes all ANSI colours from the given string.
244 func RemoveANSIColours(s string) string {
245 return ansiColorRe.ReplaceAllString(s, "")
246 }
247
248 var (
249 ansiColorRe = regexp.MustCompile("(?s)\\033\\[\\d*(;\\d*)*m")
250 errorRe = regexp.MustCompile("^(ERROR|FATAL|WARN)")
251 )
252
253 type ansiCleaner struct {
254 w io.Writer
255 }
256
257 func (a ansiCleaner) Write(p []byte) (n int, err error) {
258 return a.w.Write(ansiColorRe.ReplaceAll(p, []byte("")))
259 }
260
261 type labelColorizer struct {
262 w io.Writer
263 }
264
265 func (a labelColorizer) Write(p []byte) (n int, err error) {
266 replaced := errorRe.ReplaceAllStringFunc(string(p), func(m string) string {
267 switch m {
268 case "ERROR", "FATAL":
269 return terminal.Error(m)
270 case "WARN":
271 return terminal.Warning(m)
272 default:
273 return m
274 }
275 })
276 // io.MultiWriter will abort if we return a bigger write count than input
277 // bytes, so we lie a little.
278 _, err = a.w.Write([]byte(replaced))
279 return len(p), err
280 }
281
282 // InitGlobalLogger initializes the global logger, used in some rare cases.
283 func InitGlobalLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer) {
284 outHandle, logHandle = getLogWriters(outHandle, logHandle)
285
286 jww.SetStdoutOutput(outHandle)
287 jww.SetLogOutput(logHandle)
288 jww.SetLogThreshold(logThreshold)
289 jww.SetStdoutThreshold(stdoutThreshold)
290 }
291
292 func getLogWriters(outHandle, logHandle io.Writer) (io.Writer, io.Writer) {
293 isTerm := terminal.PrintANSIColors(os.Stdout)
294 if logHandle != ioutil.Discard && isTerm {
295 // Remove any Ansi coloring from log output
296 logHandle = ansiCleaner{w: logHandle}
297 }
298
299 if isTerm {
300 outHandle = labelColorizer{w: outHandle}
301 }
302
303 return outHandle, logHandle
304 }
305
306 type fatalLogWriter int
307
308 func (s fatalLogWriter) Write(p []byte) (n int, err error) {
309 trace := make([]byte, 1500)
310 runtime.Stack(trace, true)
311 fmt.Printf("\n===========\n\n%s\n", trace)
312 os.Exit(-1)
313
314 return 0, nil
315 }
316
317 var fatalLogListener = func(t jww.Threshold) io.Writer {
318 if t != jww.LevelError {
319 // Only interested in ERROR
320 return nil
321 }
322
323 return new(fatalLogWriter)
324 }
325
326 func newLogger(stdoutThreshold, logThreshold jww.Threshold, outHandle, logHandle io.Writer, saveErrors bool) *logger {
327 errorCounter := &jww.Counter{}
328 warnCounter := &jww.Counter{}
329 outHandle, logHandle = getLogWriters(outHandle, logHandle)
330
331 listeners := []jww.LogListener{jww.LogCounter(errorCounter, jww.LevelError), jww.LogCounter(warnCounter, jww.LevelWarn)}
332 var errorBuff *bytes.Buffer
333 if saveErrors {
334 errorBuff = new(bytes.Buffer)
335 errorCapture := func(t jww.Threshold) io.Writer {
336 if t != jww.LevelError {
337 // Only interested in ERROR
338 return nil
339 }
340 return errorBuff
341 }
342
343 listeners = append(listeners, errorCapture)
344 }
345
346 return &logger{
347 Notepad: jww.NewNotepad(stdoutThreshold, logThreshold, outHandle, logHandle, "", log.Ldate|log.Ltime, listeners...),
348 out: outHandle,
349 logCounters: &LogCounters{
350 ErrorCounter: errorCounter,
351 WarnCounter: warnCounter,
352 },
353 errors: errorBuff,
354 }
355 }