github.com/btwiuse/jiri@v0.0.0-20191125065820-53353bcfef54/log/logger.go (about) 1 // Copyright 2017 The Fuchsia Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package log 6 7 import ( 8 "bytes" 9 "container/list" 10 "fmt" 11 "io" 12 "io/ioutil" 13 glog "log" 14 "os" 15 "sync" 16 "sync/atomic" 17 "time" 18 19 "github.com/btwiuse/jiri/color" 20 "github.com/btwiuse/jiri/isatty" 21 ) 22 23 // Logger provides for convenient logging in jiri. It supports logger 24 // level using global flags. To use it "InitializeGlobalLogger" needs to 25 // be called once, then GetLogger function can be used to get the logger or 26 // log functions can be called directly 27 // 28 // The default logging level is Info. It uses golang logger to log messages internally. 29 // As an example to use debug logger one needs to run 30 // log.GetLogger().Debugf(....) 31 // or 32 // log.Debugf(....) 33 // By default Error logger prints to os.Stderr and others print to os.Stdout. 34 // Capture function can be used to temporarily capture the logs. 35 36 type TaskData struct { 37 msg string 38 progress int 39 } 40 41 type Task struct { 42 taskData *TaskData 43 e *list.Element 44 l *Logger 45 } 46 47 type Logger struct { 48 lock *sync.Mutex 49 LoggerLevel LogLevel 50 goLogger *glog.Logger 51 goErrorLogger *glog.Logger 52 goBufferLogger *glog.Logger 53 color color.Color 54 progressLines int 55 progressWindowSize uint 56 enableProgress uint32 57 progressUpdateNeeded bool 58 timeLogThreshold time.Duration 59 tasks *list.List 60 logBuffer *bytes.Buffer 61 } 62 63 type LogLevel int 64 65 const ( 66 NoLogLevel LogLevel = iota 67 ErrorLevel 68 WarningLevel 69 InfoLevel 70 DebugLevel 71 TraceLevel 72 ) 73 74 func NewLogger(loggerLevel LogLevel, color color.Color, enableProgress bool, progressWindowSize uint, timeLogThreshold time.Duration, outWriter, errWriter io.Writer) *Logger { 75 var logBuffer bytes.Buffer 76 if outWriter == nil { 77 outWriter = os.Stdout 78 } 79 if errWriter == nil { 80 errWriter = os.Stderr 81 } 82 outWriter = io.MultiWriter(outWriter, &logBuffer) 83 errWriter = io.MultiWriter(errWriter, &logBuffer) 84 term := os.Getenv("TERM") 85 switch term { 86 case "dumb", "": 87 enableProgress = false 88 } 89 if enableProgress { 90 enableProgress = isatty.IsTerminal() 91 } 92 l := &Logger{ 93 LoggerLevel: loggerLevel, 94 lock: &sync.Mutex{}, 95 goLogger: glog.New(outWriter, "", 0), 96 goErrorLogger: glog.New(errWriter, "", 0), 97 goBufferLogger: glog.New(&logBuffer, "", 0), 98 color: color, 99 progressLines: 0, 100 enableProgress: 0, 101 progressWindowSize: progressWindowSize, 102 progressUpdateNeeded: false, 103 timeLogThreshold: timeLogThreshold, 104 tasks: list.New(), 105 logBuffer: &logBuffer, 106 } 107 if enableProgress { 108 l.enableProgress = 1 109 } 110 go func() { 111 for l.IsProgressEnabled() { 112 l.repaintProgressMsgs() 113 time.Sleep(time.Second / 30) 114 } 115 }() 116 return l 117 } 118 119 func (l *Logger) IsProgressEnabled() bool { 120 return atomic.LoadUint32(&l.enableProgress) == 1 121 } 122 123 func (l *Logger) TimeLogThreshold() time.Duration { 124 return l.timeLogThreshold 125 } 126 127 func (l *Logger) DisableProgress() { 128 l.lock.Lock() 129 defer l.lock.Unlock() 130 l.clearProgress() 131 atomic.StoreUint32(&l.enableProgress, 0) 132 } 133 134 func (l *Logger) AddTaskMsg(format string, a ...interface{}) Task { 135 if !l.IsProgressEnabled() { 136 return Task{taskData: &TaskData{}, l: l} 137 } 138 t := &TaskData{ 139 msg: fmt.Sprintf(format, a...), 140 progress: 0, 141 } 142 l.lock.Lock() 143 defer l.lock.Unlock() 144 e := l.tasks.PushBack(t) 145 l.progressUpdateNeeded = true 146 return Task{ 147 taskData: t, 148 e: e, 149 l: l, 150 } 151 } 152 153 func (t *Task) Done() { 154 t.taskData.progress = 100 155 if !t.l.IsProgressEnabled() { 156 return 157 } 158 t.l.lock.Lock() 159 defer t.l.lock.Unlock() 160 t.l.progressUpdateNeeded = true 161 } 162 163 func (l *Logger) repaintProgressMsgs() { 164 l.lock.Lock() 165 defer l.lock.Unlock() 166 if !l.IsProgressEnabled() || !l.progressUpdateNeeded { 167 return 168 } 169 l.clearProgress() 170 e := l.tasks.Front() 171 for i := 0; i < int(l.progressWindowSize); i++ { 172 if e == nil { 173 break 174 } 175 t := e.Value.(*TaskData) 176 if t.progress < 100 { 177 l.printProgressMsg(t.msg) 178 e = e.Next() 179 } else { 180 temp := e.Next() 181 l.tasks.Remove(e) 182 e = temp 183 i-- 184 } 185 } 186 l.progressUpdateNeeded = false 187 } 188 189 // This is thread unsafe 190 func (l *Logger) printProgressMsg(msg string) { 191 // Disable wrap and print progress 192 str := fmt.Sprintf("\033[?7l%s: %s\033[?7h\n", l.color.Green("PROGRESS"), msg) 193 fmt.Printf(str) 194 l.progressLines++ 195 } 196 197 // This is thread unsafe 198 func (l *Logger) clearProgress() { 199 if !l.IsProgressEnabled() || l.progressLines == 0 { 200 return 201 } 202 buf := "" 203 for i := 0; i < l.progressLines; i++ { 204 buf = buf + "\033[1A\033[2K\r" 205 } 206 fmt.Printf(buf) 207 l.progressLines = 0 208 } 209 210 func (l *Logger) log(prefix, format string, a ...interface{}) { 211 l.lock.Lock() 212 stamp := time.Now().Format("15:04:05.000") 213 defer l.lock.Unlock() 214 l.clearProgress() 215 l.goLogger.Printf("[%s] %s%s", stamp, prefix, fmt.Sprintf(format, a...)) 216 } 217 218 func (l *Logger) logToBufferOnly(prefix, format string, a ...interface{}) { 219 l.lock.Lock() 220 stamp := time.Now().Format("15:04:05.000") 221 defer l.lock.Unlock() 222 l.goBufferLogger.Printf("[%s] %s%s", stamp, prefix, fmt.Sprintf(format, a...)) 223 } 224 225 func (l *Logger) Logf(loglevel LogLevel, format string, a ...interface{}) { 226 switch loglevel { 227 case InfoLevel: 228 l.Infof(format, a...) 229 case DebugLevel: 230 l.Debugf(format, a...) 231 case TraceLevel: 232 l.Tracef(format, a...) 233 case WarningLevel: 234 l.Warningf(format, a...) 235 case ErrorLevel: 236 l.Errorf(format, a...) 237 default: 238 panic(fmt.Sprintf("Undefined loglevel: %v, log message: %s", loglevel, fmt.Sprintf(format, a...))) 239 } 240 } 241 242 func (l *Logger) Infof(format string, a ...interface{}) { 243 if l.LoggerLevel >= InfoLevel { 244 l.log("", format, a...) 245 } else { 246 l.logToBufferOnly("", format, a...) 247 } 248 } 249 250 func (l *Logger) Debugf(format string, a ...interface{}) { 251 if l.LoggerLevel >= DebugLevel { 252 l.log(l.color.Cyan("DEBUG: "), format, a...) 253 } else { 254 l.logToBufferOnly(l.color.Cyan("DEBUG: "), format, a...) 255 } 256 } 257 258 func (l *Logger) Tracef(format string, a ...interface{}) { 259 if l.LoggerLevel >= TraceLevel { 260 l.log(l.color.Blue("TRACE: "), format, a...) 261 } else { 262 l.logToBufferOnly(l.color.Blue("TRACE: "), format, a...) 263 } 264 } 265 266 func (l *Logger) Warningf(format string, a ...interface{}) { 267 if l.LoggerLevel >= WarningLevel { 268 l.log(l.color.Yellow("WARN: "), format, a...) 269 } else { 270 l.logToBufferOnly(l.color.Yellow("WARN: "), format, a...) 271 } 272 } 273 274 func (l *Logger) Errorf(format string, a ...interface{}) { 275 if l.LoggerLevel >= ErrorLevel { 276 l.lock.Lock() 277 defer l.lock.Unlock() 278 l.clearProgress() 279 l.goErrorLogger.Printf("%s%s", l.color.Red("ERROR: "), fmt.Sprintf(format, a...)) 280 } else { 281 l.logToBufferOnly(l.color.Red("ERROR: "), format, a...) 282 } 283 } 284 285 // WriteLogToFile writes current logs into file. 286 func (l *Logger) WriteLogToFile(filename string) error { 287 return ioutil.WriteFile(filename, l.logBuffer.Bytes(), 0644) 288 } 289 290 // GetLogBuffer returns current buffer for the logger. 291 func (l *Logger) GetLogBuffer() *bytes.Buffer { 292 return l.logBuffer 293 }