blob: 6a16013505d0b73ec5cefb1e48563d1ba8276c4b [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package log
import (
"bytes"
"container/list"
"fmt"
"io"
glog "log"
"os"
"sync"
"sync/atomic"
"time"
"go.fuchsia.dev/jiri/color"
"go.fuchsia.dev/jiri/isatty"
)
// Logger provides for convenient logging in jiri. It supports logger
// level using global flags. To use it "InitializeGlobalLogger" needs to
// be called once, then GetLogger function can be used to get the logger or
// log functions can be called directly
//
// The default logging level is Info. It uses golang logger to log messages internally.
// As an example to use debug logger one needs to run
// log.GetLogger().Debugf(....)
// or
// log.Debugf(....)
// By default Error logger prints to os.Stderr and others print to os.Stdout.
// Capture function can be used to temporarily capture the logs.
type TaskData struct {
msg string
progress int
}
type Task struct {
taskData *TaskData
e *list.Element
l *Logger
}
type Logger struct {
lock *sync.Mutex
LoggerLevel LogLevel
goLogger *glog.Logger
goErrorLogger *glog.Logger
goBufferLogger *glog.Logger
color color.Color
progressLines int
progressWindowSize uint
enableProgress uint32
progressUpdateNeeded bool
timeLogThreshold time.Duration
tasks *list.List
logBuffer *bytes.Buffer
}
type LogLevel int
const (
NoLogLevel LogLevel = iota
ErrorLevel
WarningLevel
InfoLevel
DebugLevel
TraceLevel
)
func NewLogger(loggerLevel LogLevel, color color.Color, enableProgress bool, progressWindowSize uint, timeLogThreshold time.Duration, outWriter, errWriter io.Writer) *Logger {
var logBuffer bytes.Buffer
if outWriter == nil {
outWriter = os.Stdout
}
if errWriter == nil {
errWriter = os.Stderr
}
outWriter = io.MultiWriter(outWriter, &logBuffer)
errWriter = io.MultiWriter(errWriter, &logBuffer)
term := os.Getenv("TERM")
switch term {
case "dumb", "":
enableProgress = false
}
if enableProgress {
enableProgress = isatty.IsTerminal()
}
l := &Logger{
LoggerLevel: loggerLevel,
lock: &sync.Mutex{},
goLogger: glog.New(outWriter, "", 0),
goErrorLogger: glog.New(errWriter, "", 0),
goBufferLogger: glog.New(&logBuffer, "", 0),
color: color,
progressLines: 0,
enableProgress: 0,
progressWindowSize: progressWindowSize,
progressUpdateNeeded: false,
timeLogThreshold: timeLogThreshold,
tasks: list.New(),
logBuffer: &logBuffer,
}
if enableProgress {
l.enableProgress = 1
}
go func() {
for l.IsProgressEnabled() {
l.repaintProgressMsgs()
time.Sleep(time.Second / 30)
}
}()
return l
}
func (l *Logger) IsProgressEnabled() bool {
return atomic.LoadUint32(&l.enableProgress) == 1
}
func (l *Logger) TimeLogThreshold() time.Duration {
return l.timeLogThreshold
}
func (l *Logger) DisableProgress() {
l.lock.Lock()
defer l.lock.Unlock()
l.clearProgress()
atomic.StoreUint32(&l.enableProgress, 0)
}
func (l *Logger) AddTaskMsg(format string, a ...interface{}) Task {
if !l.IsProgressEnabled() {
return Task{taskData: &TaskData{}, l: l}
}
t := &TaskData{
msg: fmt.Sprintf(format, a...),
progress: 0,
}
l.lock.Lock()
defer l.lock.Unlock()
e := l.tasks.PushBack(t)
l.progressUpdateNeeded = true
return Task{
taskData: t,
e: e,
l: l,
}
}
func (t *Task) Done() {
t.taskData.progress = 100
if !t.l.IsProgressEnabled() {
return
}
t.l.lock.Lock()
defer t.l.lock.Unlock()
t.l.progressUpdateNeeded = true
}
func (l *Logger) repaintProgressMsgs() {
l.lock.Lock()
defer l.lock.Unlock()
if !l.IsProgressEnabled() || !l.progressUpdateNeeded {
return
}
l.clearProgress()
e := l.tasks.Front()
for i := 0; i < int(l.progressWindowSize); i++ {
if e == nil {
break
}
t := e.Value.(*TaskData)
if t.progress < 100 {
l.printProgressMsg(t.msg)
e = e.Next()
} else {
temp := e.Next()
l.tasks.Remove(e)
e = temp
i--
}
}
l.progressUpdateNeeded = false
}
// This is thread unsafe
func (l *Logger) printProgressMsg(msg string) {
// Disable wrap and print progress
str := fmt.Sprintf("\033[?7l%s: %s\033[?7h\n", l.color.Green("PROGRESS"), msg)
fmt.Printf(str)
l.progressLines++
}
// This is thread unsafe
func (l *Logger) clearProgress() {
if !l.IsProgressEnabled() || l.progressLines == 0 {
return
}
buf := ""
for i := 0; i < l.progressLines; i++ {
buf = buf + "\033[1A\033[2K\r"
}
fmt.Printf(buf)
l.progressLines = 0
}
func (l *Logger) log(prefix, format string, a ...interface{}) {
l.lock.Lock()
stamp := time.Now().Format("15:04:05.000")
defer l.lock.Unlock()
l.clearProgress()
l.goLogger.Printf("[%s] %s%s", stamp, prefix, fmt.Sprintf(format, a...))
}
func (l *Logger) logToBufferOnly(prefix, format string, a ...interface{}) {
l.lock.Lock()
stamp := time.Now().Format("15:04:05.000")
defer l.lock.Unlock()
l.goBufferLogger.Printf("[%s] %s%s", stamp, prefix, fmt.Sprintf(format, a...))
}
func (l *Logger) Logf(loglevel LogLevel, format string, a ...interface{}) {
switch loglevel {
case InfoLevel:
l.Infof(format, a...)
case DebugLevel:
l.Debugf(format, a...)
case TraceLevel:
l.Tracef(format, a...)
case WarningLevel:
l.Warningf(format, a...)
case ErrorLevel:
l.Errorf(format, a...)
default:
panic(fmt.Sprintf("Undefined loglevel: %v, log message: %s", loglevel, fmt.Sprintf(format, a...)))
}
}
func (l *Logger) Infof(format string, a ...interface{}) {
if l.LoggerLevel >= InfoLevel {
l.log("", format, a...)
} else {
l.logToBufferOnly("", format, a...)
}
}
func (l *Logger) Debugf(format string, a ...interface{}) {
if l.LoggerLevel >= DebugLevel {
l.log(l.color.Cyan("DEBUG: "), format, a...)
} else {
l.logToBufferOnly(l.color.Cyan("DEBUG: "), format, a...)
}
}
func (l *Logger) Tracef(format string, a ...interface{}) {
if l.LoggerLevel >= TraceLevel {
l.log(l.color.Blue("TRACE: "), format, a...)
} else {
l.logToBufferOnly(l.color.Blue("TRACE: "), format, a...)
}
}
func (l *Logger) Warningf(format string, a ...interface{}) {
if l.LoggerLevel >= WarningLevel {
l.log(l.color.Yellow("WARN: "), format, a...)
} else {
l.logToBufferOnly(l.color.Yellow("WARN: "), format, a...)
}
}
func (l *Logger) Errorf(format string, a ...interface{}) {
if l.LoggerLevel >= ErrorLevel {
l.lock.Lock()
defer l.lock.Unlock()
l.clearProgress()
l.goErrorLogger.Printf("%s%s", l.color.Red("ERROR: "), fmt.Sprintf(format, a...))
} else {
l.logToBufferOnly(l.color.Red("ERROR: "), format, a...)
}
}
// WriteLogToFile writes current logs into file.
func (l *Logger) WriteLogToFile(filename string) error {
return os.WriteFile(filename, l.logBuffer.Bytes(), 0644)
}
// GetLogBuffer returns current buffer for the logger.
func (l *Logger) GetLogBuffer() *bytes.Buffer {
return l.logBuffer
}