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