logger.go raw

   1  /*
   2   * Copyright 2017 Baidu, Inc.
   3   *
   4   * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
   5   * except in compliance with the License. You may obtain a copy of the License at
   6   *
   7   * http://www.apache.org/licenses/LICENSE-2.0
   8   *
   9   * Unless required by applicable law or agreed to in writing, software distributed under the
  10   * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  11   * either express or implied. See the License for the specific language governing permissions
  12   * and limitations under the License.
  13   */
  14  
  15  // logger.go - defines the logger structure and methods
  16  
  17  // Package log implements the log facilities for BCE. It supports log to stderr, stdout as well as
  18  // log to file with rotating. It is safe to be called by multiple goroutines.
  19  // By using the package level function to use the default logger:
  20  //     log.SetLogHandler(log.STDOUT | log.FILE) // default is log to stdout
  21  //     log.SetLogDir("/tmp")                    // default is /tmp
  22  //     log.SetRotateType(log.ROTATE_DAY)        // default is log.HOUR
  23  //     log.SetRotateSize(1 << 30)               // default is 1GB
  24  //     log.SetLogLevel(log.INFO)                // default is log.DEBUG
  25  //     log.Debug(1, 1.2, "a")
  26  //     log.Debugln(1, 1.2, "a")
  27  //     log.Debugf(1, 1.2, "a")
  28  // User can also create new logger without using the default logger:
  29  //     customLogger := log.NewLogger()
  30  //     customLogger.SetLogHandler(log.FILE)
  31  //     customLogger.Debug(1, 1.2, "a")
  32  // The log format can also support custom setting by using the following interface:
  33  //     log.SetLogFormat([]string{log.FMT_LEVEL, log.FMT_TIME, log.FMT_MSG})
  34  // Most of the cases just use the default format is enough:
  35  //     []string{FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG}
  36  package log
  37  
  38  import (
  39  	"fmt"
  40  	"io"
  41  	"os"
  42  	"path/filepath"
  43  	"runtime"
  44  	"strings"
  45  	"time"
  46  )
  47  
  48  type Handler uint8
  49  
  50  // Constants for log handler flags, default is STDOUT
  51  const (
  52  	NONE   Handler = 0
  53  	STDOUT Handler = 1
  54  	STDERR Handler = 1 << 1
  55  	FILE   Handler = 1 << 2
  56  )
  57  
  58  type RotateStrategy uint8
  59  
  60  // Constants for log rotating strategy when logging to file, default is by hour
  61  const (
  62  	ROTATE_NONE RotateStrategy = iota
  63  	ROTATE_DAY
  64  	ROTATE_HOUR
  65  	ROTATE_MINUTE
  66  	ROTATE_SIZE
  67  
  68  	DEFAULT_ROTATE_TYPE           = ROTATE_HOUR
  69  	DEFAULT_ROTATE_SIZE     int64 = 1 << 30
  70  	DEFAULT_LOG_DIR               = "/tmp"
  71  	ROTATE_SIZE_FILE_PREFIX       = "rotating"
  72  )
  73  
  74  type Level uint8
  75  
  76  // Constants for log levels, default is DEBUG
  77  const (
  78  	DEBUG Level = iota
  79  	INFO
  80  	WARN
  81  	ERROR
  82  	FATAL
  83  	PANIC
  84  )
  85  
  86  var gLevelString = [...]string{"DEBUG", "INFO", "WARN", "ERROR", "FATAL", "PANIC"}
  87  
  88  // Constants of the log format components to support user custom specification
  89  const (
  90  	FMT_LEVEL    = "level"
  91  	FMT_LTIME    = "ltime"    // long time with microsecond
  92  	FMT_TIME     = "time"     // just with second
  93  	FMT_LOCATION = "location" // caller's location with file, line, function
  94  	FMT_MSG      = "msg"
  95  )
  96  
  97  var (
  98  	LOG_FMT_STR = map[string]string{
  99  		FMT_LEVEL:    "[%s]",
 100  		FMT_LTIME:    "2006-01-02 15:04:05.000000",
 101  		FMT_TIME:     "2006-01-02 15:04:05",
 102  		FMT_LOCATION: "%s:%d:%s:",
 103  		FMT_MSG:      "%s",
 104  	}
 105  	gDefaultLogFormat = []string{FMT_LEVEL, FMT_LTIME, FMT_LOCATION, FMT_MSG}
 106  )
 107  
 108  type writerArgs struct {
 109  	record     string
 110  	rotateArgs interface{} // used for rotating: the size of the record or the logging time
 111  }
 112  
 113  // Logger defines the internal implementation of the log facility
 114  type logger struct {
 115  	writers        map[Handler]io.WriteCloser // the destination writer to log message
 116  	writerChan     chan *writerArgs           // the writer channal to pass each record and time or size
 117  	logFormat      []string
 118  	levelThreshold Level
 119  	handler        Handler
 120  
 121  	// Fields that used when logging to file
 122  	logDir     string
 123  	logFile    string
 124  	rotateType RotateStrategy
 125  	rotateSize int64
 126  	done       chan bool
 127  }
 128  
 129  func (l *logger) logging(level Level, format string, args ...interface{}) {
 130  	// Only log message that set the handler and is greater than or equal to the threshold
 131  	if l.handler == NONE || level < l.levelThreshold {
 132  		return
 133  	}
 134  
 135  	// Generate the log record string and pass it to the writer channel
 136  	now := time.Now()
 137  	pc, file, line, ok, funcname := uintptr(0), "???", 0, true, "???"
 138  	pc, file, line, ok = runtime.Caller(2)
 139  	if ok {
 140  		funcname = runtime.FuncForPC(pc).Name()
 141  		funcname = filepath.Ext(funcname)
 142  		funcname = strings.TrimPrefix(funcname, ".")
 143  		file = filepath.Base(file)
 144  	}
 145  	buf := make([]string, 0, len(l.logFormat))
 146  	msg := fmt.Sprintf(format, args...)
 147  	for _, f := range l.logFormat {
 148  		if _, exists := LOG_FMT_STR[f]; !exists { // skip not supported part
 149  			continue
 150  		}
 151  		fmtStr := LOG_FMT_STR[f]
 152  		switch f {
 153  		case FMT_LEVEL:
 154  			buf = append(buf, fmt.Sprintf(fmtStr, gLevelString[level]))
 155  		case FMT_LTIME:
 156  			buf = append(buf, now.Format(fmtStr))
 157  		case FMT_TIME:
 158  			buf = append(buf, now.Format(fmtStr))
 159  		case FMT_LOCATION:
 160  			buf = append(buf, fmt.Sprintf(fmtStr, file, line, funcname))
 161  		case FMT_MSG:
 162  			buf = append(buf, fmt.Sprintf(fmtStr, msg))
 163  		}
 164  	}
 165  	record := strings.Join(buf, " ")
 166  	if l.rotateType == ROTATE_SIZE {
 167  		l.writerChan <- &writerArgs{record, int64(len(record))}
 168  	} else {
 169  		l.writerChan <- &writerArgs{record, now}
 170  	}
 171  
 172  	// wait for current record done logging
 173  }
 174  
 175  func (l *logger) buildWriter(args interface{}) {
 176  	if l.handler&STDOUT == STDOUT {
 177  		l.writers[STDOUT] = os.Stdout
 178  	} else {
 179  		delete(l.writers, STDOUT)
 180  	}
 181  	if l.handler&STDERR == STDERR {
 182  		l.writers[STDERR] = os.Stderr
 183  	} else {
 184  		delete(l.writers, STDERR)
 185  	}
 186  	if l.handler&FILE == FILE {
 187  		l.writers[FILE] = l.buildFileWriter(args)
 188  	} else {
 189  		delete(l.writers, FILE)
 190  	}
 191  }
 192  
 193  func (l *logger) buildFileWriter(args interface{}) io.WriteCloser {
 194  	if l.handler&FILE != FILE {
 195  		return os.Stderr
 196  	}
 197  
 198  	if len(l.logDir) == 0 {
 199  		l.logDir = DEFAULT_LOG_DIR
 200  	}
 201  	if l.rotateType < ROTATE_NONE || l.rotateType > ROTATE_SIZE {
 202  		l.rotateType = DEFAULT_ROTATE_TYPE
 203  	}
 204  	if l.rotateType == ROTATE_SIZE && l.rotateSize == 0 {
 205  		l.rotateSize = DEFAULT_ROTATE_SIZE
 206  	}
 207  
 208  	logFile, needCreateFile := "", false
 209  	if l.rotateType == ROTATE_SIZE {
 210  		recordSize, _ := args.(int64)
 211  		logFile, needCreateFile = l.buildFileWriterBySize(recordSize)
 212  	} else {
 213  		recordTime, _ := args.(time.Time)
 214  		switch l.rotateType {
 215  		case ROTATE_NONE:
 216  			logFile = "default.log"
 217  		case ROTATE_DAY:
 218  			logFile = recordTime.Format("2006-01-02.log")
 219  		case ROTATE_HOUR:
 220  			logFile = recordTime.Format("2006-01-02_15.log")
 221  		case ROTATE_MINUTE:
 222  			logFile = recordTime.Format("2006-01-02_15-04.log")
 223  		}
 224  		if _, exist := getFileInfo(filepath.Join(l.logDir, logFile)); !exist {
 225  			needCreateFile = true
 226  		}
 227  	}
 228  	l.logFile = logFile
 229  	logFile = filepath.Join(l.logDir, l.logFile)
 230  
 231  	// Should create new file
 232  	if needCreateFile {
 233  		if w, ok := l.writers[FILE]; ok {
 234  			w.Close()
 235  		}
 236  		if writer, err := os.Create(logFile); err == nil {
 237  			return writer
 238  		} else {
 239  			return os.Stderr
 240  		}
 241  	}
 242  
 243  	// Already open the file
 244  	if w, ok := l.writers[FILE]; ok {
 245  		return w
 246  	}
 247  
 248  	// Newly open the file
 249  	if writer, err := os.OpenFile(logFile, os.O_WRONLY|os.O_APPEND, 0666); err == nil {
 250  		return writer
 251  	} else {
 252  		return os.Stderr
 253  	}
 254  }
 255  
 256  func (l *logger) buildFileWriterBySize(recordSize int64) (string, bool) {
 257  	logFile, needCreateFile := "", false
 258  	// First running the program and need to get filename by checking the existed files
 259  	if len(l.logFile) == 0 {
 260  		fname := fmt.Sprintf("%s-%s.0.log", ROTATE_SIZE_FILE_PREFIX, getSizeString(l.rotateSize))
 261  		for {
 262  			size, exist := getFileInfo(filepath.Join(l.logDir, fname))
 263  			if !exist {
 264  				logFile, needCreateFile = fname, true
 265  				break
 266  			}
 267  			if exist && size+recordSize <= l.rotateSize {
 268  				logFile, needCreateFile = fname, false
 269  				break
 270  			}
 271  			fname = getNextFileName(fname)
 272  		}
 273  	} else { // check the file size to append to the existed file or create a new file
 274  		currentFile := filepath.Join(l.logDir, l.logFile)
 275  		size, exist := getFileInfo(currentFile)
 276  		if !exist {
 277  			logFile, needCreateFile = l.logFile, true
 278  		} else {
 279  			if size+recordSize > l.rotateSize { // size exceeded
 280  				logFile, needCreateFile = getNextFileName(l.logFile), true
 281  			} else {
 282  				logFile, needCreateFile = l.logFile, false
 283  			}
 284  		}
 285  	}
 286  	return logFile, needCreateFile
 287  }
 288  
 289  func (l *logger) SetHandler(h Handler) { l.handler = h }
 290  
 291  func (l *logger) SetLogDir(dir string) { l.logDir = dir }
 292  
 293  func (l *logger) SetLogLevel(level Level) { l.levelThreshold = level }
 294  
 295  func (l *logger) SetLogFormat(format []string) { l.logFormat = format }
 296  
 297  func (l *logger) SetRotateType(rotate RotateStrategy) { l.rotateType = rotate }
 298  
 299  func (l *logger) SetRotateSize(size int64) { l.rotateSize = size }
 300  
 301  func (l *logger) Debug(msg ...interface{}) { l.logging(DEBUG, "%s\n", concat(msg...)) }
 302  
 303  func (l *logger) Debugln(msg ...interface{}) { l.logging(DEBUG, "%s\n", concat(msg...)) }
 304  
 305  func (l *logger) Debugf(f string, msg ...interface{}) { l.logging(DEBUG, f+"\n", msg...) }
 306  
 307  func (l *logger) Info(msg ...interface{}) { l.logging(INFO, "%s\n", concat(msg...)) }
 308  
 309  func (l *logger) Infoln(msg ...interface{}) { l.logging(INFO, "%s\n", concat(msg...)) }
 310  
 311  func (l *logger) Infof(f string, msg ...interface{}) { l.logging(INFO, f+"\n", msg...) }
 312  
 313  func (l *logger) Warn(msg ...interface{}) { l.logging(WARN, "%s\n", concat(msg...)) }
 314  
 315  func (l *logger) Warnln(msg ...interface{}) { l.logging(WARN, "%s\n", concat(msg...)) }
 316  
 317  func (l *logger) Warnf(f string, msg ...interface{}) { l.logging(WARN, f+"\n", msg...) }
 318  
 319  func (l *logger) Error(msg ...interface{}) { l.logging(ERROR, "%s\n", concat(msg...)) }
 320  
 321  func (l *logger) Errorln(msg ...interface{}) { l.logging(ERROR, "%s\n", concat(msg...)) }
 322  
 323  func (l *logger) Errorf(f string, msg ...interface{}) { l.logging(ERROR, f+"\n", msg...) }
 324  
 325  func (l *logger) Fatal(msg ...interface{}) { l.logging(FATAL, "%s\n", concat(msg...)) }
 326  
 327  func (l *logger) Fatalln(msg ...interface{}) { l.logging(FATAL, "%s\n", concat(msg...)) }
 328  
 329  func (l *logger) Fatalf(f string, msg ...interface{}) { l.logging(FATAL, f+"\n", msg...) }
 330  
 331  func (l *logger) Panic(msg ...interface{}) {
 332  	record := concat(msg...)
 333  	l.logging(PANIC, "%s\n", record)
 334  	panic(record)
 335  }
 336  
 337  func (l *logger) Panicln(msg ...interface{}) {
 338  	record := concat(msg...)
 339  	l.logging(PANIC, "%s\n", record)
 340  	panic(record)
 341  }
 342  
 343  func (l *logger) Panicf(format string, msg ...interface{}) {
 344  	record := fmt.Sprintf(format, msg...)
 345  	l.logging(PANIC, format+"\n", msg...)
 346  	panic(record)
 347  }
 348  
 349  func (l *logger) Close() {
 350  	select {
 351  	case <-l.done:
 352  		return
 353  	default:
 354  	}
 355  	l.writerChan <- nil
 356  }
 357  
 358  func NewLogger() *logger {
 359  	obj := &logger{
 360  		writers:        make(map[Handler]io.WriteCloser, 3), // now only support 3 kinds of handler
 361  		writerChan:     make(chan *writerArgs, 100),
 362  		logFormat:      gDefaultLogFormat,
 363  		levelThreshold: DEBUG,
 364  		handler:        NONE,
 365  		done:           make(chan bool),
 366  	}
 367  	// The backend writer goroutine to write each log record
 368  	go func() {
 369  		defer func() {
 370  			if e := recover(); e != nil {
 371  				fmt.Println(e)
 372  			}
 373  		}()
 374  		for {
 375  			select {
 376  			case <-obj.done:
 377  				return
 378  			case args := <-obj.writerChan: // wait until a record comes to log
 379  				if args == nil {
 380  					close(obj.done)
 381  					close(obj.writerChan)
 382  					return
 383  				}
 384  				obj.buildWriter(args.rotateArgs)
 385  				for _, w := range obj.writers {
 386  					fmt.Fprint(w, args.record)
 387  				}
 388  			}
 389  		}
 390  	}()
 391  
 392  	return obj
 393  }
 394