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  }