notepad.go (5482B)
1 // Copyright © 2016 Steve Francia <spf@spf13.com>. 2 // 3 // Use of this source code is governed by an MIT-style 4 // license that can be found in the LICENSE file. 5 6 package jwalterweatherman 7 8 import ( 9 "fmt" 10 "io" 11 "io/ioutil" 12 "log" 13 ) 14 15 type Threshold int 16 17 func (t Threshold) String() string { 18 return prefixes[t] 19 } 20 21 const ( 22 LevelTrace Threshold = iota 23 LevelDebug 24 LevelInfo 25 LevelWarn 26 LevelError 27 LevelCritical 28 LevelFatal 29 ) 30 31 var prefixes map[Threshold]string = map[Threshold]string{ 32 LevelTrace: "TRACE", 33 LevelDebug: "DEBUG", 34 LevelInfo: "INFO", 35 LevelWarn: "WARN", 36 LevelError: "ERROR", 37 LevelCritical: "CRITICAL", 38 LevelFatal: "FATAL", 39 } 40 41 // Notepad is where you leave a note! 42 type Notepad struct { 43 TRACE *log.Logger 44 DEBUG *log.Logger 45 INFO *log.Logger 46 WARN *log.Logger 47 ERROR *log.Logger 48 CRITICAL *log.Logger 49 FATAL *log.Logger 50 51 LOG *log.Logger 52 FEEDBACK *Feedback 53 54 loggers [7]**log.Logger 55 logHandle io.Writer 56 outHandle io.Writer 57 logThreshold Threshold 58 stdoutThreshold Threshold 59 prefix string 60 flags int 61 62 logListeners []LogListener 63 } 64 65 // A LogListener can ble supplied to a Notepad to listen on log writes for a given 66 // threshold. This can be used to capture log events in unit tests and similar. 67 // Note that this function will be invoked once for each log threshold. If 68 // the given threshold is not of interest to you, return nil. 69 // Note that these listeners will receive log events for a given threshold, even 70 // if the current configuration says not to log it. That way you can count ERRORs even 71 // if you don't print them to the console. 72 type LogListener func(t Threshold) io.Writer 73 74 // NewNotepad creates a new Notepad. 75 func NewNotepad( 76 outThreshold Threshold, 77 logThreshold Threshold, 78 outHandle, logHandle io.Writer, 79 prefix string, flags int, 80 logListeners ...LogListener, 81 ) *Notepad { 82 83 n := &Notepad{logListeners: logListeners} 84 85 n.loggers = [7]**log.Logger{&n.TRACE, &n.DEBUG, &n.INFO, &n.WARN, &n.ERROR, &n.CRITICAL, &n.FATAL} 86 n.outHandle = outHandle 87 n.logHandle = logHandle 88 n.stdoutThreshold = outThreshold 89 n.logThreshold = logThreshold 90 91 if len(prefix) != 0 { 92 n.prefix = "[" + prefix + "] " 93 } else { 94 n.prefix = "" 95 } 96 97 n.flags = flags 98 99 n.LOG = log.New(n.logHandle, 100 "LOG: ", 101 n.flags) 102 n.FEEDBACK = &Feedback{out: log.New(outHandle, "", 0), log: n.LOG} 103 104 n.init() 105 return n 106 } 107 108 // init creates the loggers for each level depending on the notepad thresholds. 109 func (n *Notepad) init() { 110 logAndOut := io.MultiWriter(n.outHandle, n.logHandle) 111 112 for t, logger := range n.loggers { 113 threshold := Threshold(t) 114 prefix := n.prefix + threshold.String() + " " 115 116 switch { 117 case threshold >= n.logThreshold && threshold >= n.stdoutThreshold: 118 *logger = log.New(n.createLogWriters(threshold, logAndOut), prefix, n.flags) 119 120 case threshold >= n.logThreshold: 121 *logger = log.New(n.createLogWriters(threshold, n.logHandle), prefix, n.flags) 122 123 case threshold >= n.stdoutThreshold: 124 *logger = log.New(n.createLogWriters(threshold, n.outHandle), prefix, n.flags) 125 126 default: 127 *logger = log.New(n.createLogWriters(threshold, ioutil.Discard), prefix, n.flags) 128 } 129 } 130 } 131 132 func (n *Notepad) createLogWriters(t Threshold, handle io.Writer) io.Writer { 133 if len(n.logListeners) == 0 { 134 return handle 135 } 136 writers := []io.Writer{handle} 137 for _, l := range n.logListeners { 138 w := l(t) 139 if w != nil { 140 writers = append(writers, w) 141 } 142 } 143 144 if len(writers) == 1 { 145 return handle 146 } 147 148 return io.MultiWriter(writers...) 149 } 150 151 // SetLogThreshold changes the threshold above which messages are written to the 152 // log file. 153 func (n *Notepad) SetLogThreshold(threshold Threshold) { 154 n.logThreshold = threshold 155 n.init() 156 } 157 158 // SetLogOutput changes the file where log messages are written. 159 func (n *Notepad) SetLogOutput(handle io.Writer) { 160 n.logHandle = handle 161 n.init() 162 } 163 164 // GetStdoutThreshold returns the defined Treshold for the log logger. 165 func (n *Notepad) GetLogThreshold() Threshold { 166 return n.logThreshold 167 } 168 169 // SetStdoutThreshold changes the threshold above which messages are written to the 170 // standard output. 171 func (n *Notepad) SetStdoutThreshold(threshold Threshold) { 172 n.stdoutThreshold = threshold 173 n.init() 174 } 175 176 // GetStdoutThreshold returns the Treshold for the stdout logger. 177 func (n *Notepad) GetStdoutThreshold() Threshold { 178 return n.stdoutThreshold 179 } 180 181 // SetPrefix changes the prefix used by the notepad. Prefixes are displayed between 182 // brackets at the beginning of the line. An empty prefix won't be displayed at all. 183 func (n *Notepad) SetPrefix(prefix string) { 184 if len(prefix) != 0 { 185 n.prefix = "[" + prefix + "] " 186 } else { 187 n.prefix = "" 188 } 189 n.init() 190 } 191 192 // SetFlags choose which flags the logger will display (after prefix and message 193 // level). See the package log for more informations on this. 194 func (n *Notepad) SetFlags(flags int) { 195 n.flags = flags 196 n.init() 197 } 198 199 // Feedback writes plainly to the outHandle while 200 // logging with the standard extra information (date, file, etc). 201 type Feedback struct { 202 out *log.Logger 203 log *log.Logger 204 } 205 206 func (fb *Feedback) Println(v ...interface{}) { 207 fb.output(fmt.Sprintln(v...)) 208 } 209 210 func (fb *Feedback) Printf(format string, v ...interface{}) { 211 fb.output(fmt.Sprintf(format, v...)) 212 } 213 214 func (fb *Feedback) Print(v ...interface{}) { 215 fb.output(fmt.Sprint(v...)) 216 } 217 218 func (fb *Feedback) output(s string) { 219 if fb.out != nil { 220 fb.out.Output(2, s) 221 } 222 if fb.log != nil { 223 fb.log.Output(2, s) 224 } 225 }