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  }