github.com/xwi88/log4go@v0.0.6/log.go (about)

     1  package log4go
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"log"
     7  	"path"
     8  	"runtime"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  )
    13  
    14  // LevelFlag log level flags
    15  const (
    16  	LevelFlagEmergency = "EMERGENCY"
    17  	LevelFlagAlert     = "ALERT"
    18  	LevelFlagCritical  = "CRITICAL"
    19  	LevelFlagError     = "ERROR"
    20  	LevelFlagWarning   = "WARNING" // compatible WARN
    21  	LevelFlagWarn      = "WARN"
    22  	LevelFlagNotice    = "NOTICE"
    23  	LevelFlagInfo      = "INFO"
    24  	LevelFlagDebug     = "DEBUG"
    25  )
    26  
    27  // RFC5424 log message levels.
    28  // ref: https://tools.ietf.org/html/draft-ietf-syslog-protocol-23
    29  const (
    30  	EMERGENCY = iota // Emergency: system is unusable
    31  	ALERT            // Alert: action must be taken immediately
    32  	CRITICAL         // Critical: critical conditions
    33  	ERROR            // Error: error conditions
    34  	WARNING          // Warning: warning conditions
    35  	NOTICE           // Notice: normal but significant condition
    36  	INFO             // Informational: informational messages
    37  	DEBUG            // Debug: debug-level messages
    38  )
    39  
    40  const (
    41  	// default size or min size for record channel
    42  	recordChannelSizeDefault = uint(4096)
    43  	// default time layout
    44  	defaultLayout = "2006/01/02 15:04:05"
    45  	// timestamp with zone info
    46  	timestampLayout = "2006-01-02T15:04:05.000+0800"
    47  )
    48  
    49  // LevelFlags level Flags set
    50  var (
    51  	LevelFlags = []string{
    52  		LevelFlagEmergency,
    53  		LevelFlagAlert,
    54  		LevelFlagCritical,
    55  		LevelFlagError,
    56  		LevelFlagWarning,
    57  		LevelFlagNotice,
    58  		LevelFlagInfo,
    59  		LevelFlagDebug,
    60  	}
    61  	DefaultLayout = defaultLayout
    62  )
    63  
    64  // default logger
    65  var (
    66  	loggerDefault     *Logger
    67  	recordPool        *sync.Pool
    68  	recordChannelSize = recordChannelSizeDefault // log chan size
    69  )
    70  
    71  // Record log record
    72  type Record struct {
    73  	level int
    74  	time  string
    75  	file  string
    76  	msg   string
    77  }
    78  
    79  func (r *Record) String() string {
    80  	return fmt.Sprintf("%s [%s] <%s> %s\n", r.time, LevelFlags[r.level], r.file, r.msg)
    81  }
    82  
    83  // Writer record writer
    84  type Writer interface {
    85  	Init() error
    86  	Write(*Record) error
    87  }
    88  
    89  // Flusher record flusher
    90  type Flusher interface {
    91  	Flush() error
    92  }
    93  
    94  // Rotater record rotater
    95  type Rotater interface {
    96  	Rotate() error
    97  	SetPathPattern(string) error
    98  }
    99  
   100  // Logger logger define
   101  type Logger struct {
   102  	writers         []Writer
   103  	records         chan *Record
   104  	recordsChanSize uint
   105  	lastTime        int64
   106  	lastTimeStr     string
   107  
   108  	flushTimer  time.Duration // timer to flush logger record to chan
   109  	rotateTimer time.Duration // timer to rotate logger record for writer
   110  
   111  	c chan bool
   112  
   113  	layout       string
   114  	level        int
   115  	fullPath     bool // show full path, default only show file:line_number
   116  	withFuncName bool // show caller func name
   117  	lock         sync.RWMutex
   118  }
   119  
   120  // NewLogger create the logger
   121  func NewLogger() *Logger {
   122  	if loggerDefault != nil {
   123  		return loggerDefault
   124  	}
   125  	records := make(chan *Record, recordChannelSize)
   126  
   127  	return newLoggerWithRecords(records)
   128  }
   129  
   130  // newLoggerWithRecords is useful for go test
   131  func newLoggerWithRecords(records chan *Record) *Logger {
   132  	l := new(Logger)
   133  	l.writers = make([]Writer, 0, 1) // normal least has console writer
   134  	if l.recordsChanSize == 0 {
   135  		recordChannelSize = recordChannelSizeDefault
   136  	}
   137  
   138  	l.records = records
   139  	l.c = make(chan bool, 1)
   140  	l.level = DEBUG
   141  	l.layout = DefaultLayout
   142  
   143  	go bootstrapLogWriter(l)
   144  
   145  	return l
   146  }
   147  
   148  // Register register writer
   149  // the writer should be register once for writers by kind
   150  func (l *Logger) Register(w Writer) {
   151  	if err := w.Init(); err != nil {
   152  		panic(err)
   153  	}
   154  
   155  	l.writers = append(l.writers, w)
   156  }
   157  
   158  // Close close logger
   159  func (l *Logger) Close() {
   160  	close(l.records)
   161  	<-l.c
   162  
   163  	for _, w := range l.writers {
   164  		if f, ok := w.(Flusher); ok {
   165  			if err := f.Flush(); err != nil {
   166  				log.Println(err)
   167  			}
   168  		}
   169  	}
   170  }
   171  
   172  // SetLayout set the logger time layout
   173  func (l *Logger) SetLayout(layout string) {
   174  	l.layout = layout
   175  }
   176  
   177  // SetLevel set the logger level
   178  func (l *Logger) SetLevel(lvl int) {
   179  	l.level = lvl
   180  }
   181  
   182  // WithFullPath set the logger with full path
   183  func (l *Logger) WithFullPath(show bool) {
   184  	l.fullPath = show
   185  }
   186  
   187  // WithFuncName set the logger with func name
   188  func (l *Logger) WithFuncName(show bool) {
   189  	l.withFuncName = show
   190  }
   191  
   192  // Debug level debug
   193  func (l *Logger) Debug(fmt string, args ...interface{}) {
   194  	l.deliverRecordToWriter(DEBUG, fmt, args...)
   195  }
   196  
   197  // Info level info
   198  func (l *Logger) Info(fmt string, args ...interface{}) {
   199  	l.deliverRecordToWriter(INFO, fmt, args...)
   200  }
   201  
   202  // Notice level notice
   203  func (l *Logger) Notice(fmt string, args ...interface{}) {
   204  	l.deliverRecordToWriter(NOTICE, fmt, args...)
   205  }
   206  
   207  // Warn level warn
   208  func (l *Logger) Warn(fmt string, args ...interface{}) {
   209  	l.deliverRecordToWriter(WARNING, fmt, args...)
   210  }
   211  
   212  // Error level error
   213  func (l *Logger) Error(fmt string, args ...interface{}) {
   214  	l.deliverRecordToWriter(ERROR, fmt, args...)
   215  }
   216  
   217  // Critical level critical
   218  func (l *Logger) Critical(fmt string, args ...interface{}) {
   219  	l.deliverRecordToWriter(CRITICAL, fmt, args...)
   220  }
   221  
   222  // Alert level alert
   223  func (l *Logger) Alert(fmt string, args ...interface{}) {
   224  	l.deliverRecordToWriter(ALERT, fmt, args...)
   225  }
   226  
   227  // Emergency level emergency
   228  func (l *Logger) Emergency(fmt string, args ...interface{}) {
   229  	l.deliverRecordToWriter(EMERGENCY, fmt, args...)
   230  }
   231  
   232  func (l *Logger) deliverRecordToWriter(level int, f string, args ...interface{}) {
   233  	var msg string
   234  	var fi bytes.Buffer
   235  
   236  	if level > l.level {
   237  		return
   238  	}
   239  
   240  	msg = f
   241  	sz := len(args)
   242  	if sz != 0 {
   243  		if strings.Contains(msg, "%") && !strings.Contains(msg, "%%") {
   244  		} else {
   245  			msg += strings.Repeat("%v", len(args))
   246  		}
   247  	}
   248  	msg = fmt.Sprintf(msg, args...)
   249  
   250  	// source code, file and line num
   251  	pc, file, line, ok := runtime.Caller(2)
   252  	if ok {
   253  		fileName := path.Base(file)
   254  		if l.fullPath {
   255  			fileName = file
   256  		}
   257  		fi.WriteString(fmt.Sprintf("%s:%d", fileName, line))
   258  
   259  		if l.withFuncName {
   260  			funcName := runtime.FuncForPC(pc).Name()
   261  			funcName = path.Base(funcName)
   262  			fi.WriteString(fmt.Sprintf(" %s", funcName))
   263  		}
   264  	}
   265  
   266  	// format time
   267  	now := time.Now()
   268  	l.lock.Lock() // avoid data race
   269  	if now.Unix() != l.lastTime {
   270  		l.lastTime = now.Unix()
   271  		l.lastTimeStr = now.Format(l.layout)
   272  	}
   273  	lastTimeStr := l.lastTimeStr
   274  	l.lock.Unlock()
   275  
   276  	r := recordPool.Get().(*Record)
   277  	r.msg = msg
   278  	r.file = fi.String()
   279  	r.time = lastTimeStr
   280  	r.level = level
   281  
   282  	l.records <- r
   283  }
   284  
   285  func bootstrapLogWriter(logger *Logger) {
   286  	var (
   287  		r  *Record
   288  		ok bool
   289  	)
   290  
   291  	if r, ok = <-logger.records; !ok {
   292  		logger.c <- true
   293  		return
   294  	}
   295  
   296  	for _, w := range logger.writers {
   297  		if err := w.Write(r); err != nil {
   298  			log.Printf("%v\n", err)
   299  		}
   300  	}
   301  
   302  	flushTimer := time.NewTimer(logger.flushTimer)
   303  	rotateTimer := time.NewTimer(logger.rotateTimer)
   304  
   305  	for {
   306  		select {
   307  		case r, ok = <-logger.records:
   308  			if !ok {
   309  				logger.c <- true
   310  				return
   311  			}
   312  
   313  			for _, w := range logger.writers {
   314  				if err := w.Write(r); err != nil {
   315  					log.Printf("%v\n", err)
   316  				}
   317  			}
   318  
   319  			recordPool.Put(r)
   320  
   321  		case <-flushTimer.C:
   322  			for _, w := range logger.writers {
   323  				if f, ok := w.(Flusher); ok {
   324  					if err := f.Flush(); err != nil {
   325  						log.Printf("%v\n", err)
   326  					}
   327  				}
   328  			}
   329  			flushTimer.Reset(logger.flushTimer)
   330  
   331  		case <-rotateTimer.C:
   332  			for _, w := range logger.writers {
   333  				if r, ok := w.(Rotater); ok {
   334  					if err := r.Rotate(); err != nil {
   335  						log.Printf("%v\n", err)
   336  					}
   337  				}
   338  			}
   339  			rotateTimer.Reset(logger.rotateTimer)
   340  		}
   341  	}
   342  }
   343  
   344  func init() {
   345  	loggerDefault = NewLogger()
   346  	loggerDefault.flushTimer = time.Millisecond * 500
   347  	loggerDefault.rotateTimer = time.Second * 10
   348  	recordPool = &sync.Pool{New: func() interface{} {
   349  		return &Record{}
   350  	}}
   351  }
   352  
   353  // Register register writer
   354  func Register(w Writer) {
   355  	loggerDefault.Register(w)
   356  }
   357  
   358  // Close close logger
   359  func Close() {
   360  	loggerDefault.Close()
   361  }
   362  
   363  // SetLayout set the logger time layout, should call before logger real use
   364  func SetLayout(layout string) {
   365  	loggerDefault.layout = layout
   366  }
   367  
   368  // SetLevel set the logger level, should call before logger real use
   369  func SetLevel(lvl int) {
   370  	loggerDefault.level = lvl
   371  }
   372  
   373  // WithFullPath set the logger with full path, should call before logger real use
   374  func WithFullPath(show bool) {
   375  	loggerDefault.fullPath = show
   376  }
   377  
   378  // WithFuncName set the logger with func name, should call before logger real use
   379  func WithFuncName(show bool) {
   380  	loggerDefault.withFuncName = show
   381  }
   382  
   383  // Debug level debug
   384  func Debug(fmt string, args ...interface{}) {
   385  	loggerDefault.deliverRecordToWriter(DEBUG, fmt, args...)
   386  }
   387  
   388  // Info level info
   389  func Info(fmt string, args ...interface{}) {
   390  	loggerDefault.deliverRecordToWriter(INFO, fmt, args...)
   391  }
   392  
   393  // Notice level notice
   394  func Notice(fmt string, args ...interface{}) {
   395  	loggerDefault.deliverRecordToWriter(NOTICE, fmt, args...)
   396  }
   397  
   398  // Warn level warn
   399  func Warn(fmt string, args ...interface{}) {
   400  	loggerDefault.deliverRecordToWriter(WARNING, fmt, args...)
   401  }
   402  
   403  // Error level error
   404  func Error(fmt string, args ...interface{}) {
   405  	loggerDefault.deliverRecordToWriter(ERROR, fmt, args...)
   406  }
   407  
   408  // Critical level critical
   409  func Critical(fmt string, args ...interface{}) {
   410  	loggerDefault.deliverRecordToWriter(CRITICAL, fmt, args...)
   411  }
   412  
   413  // Alert level alert
   414  func Alert(fmt string, args ...interface{}) {
   415  	loggerDefault.deliverRecordToWriter(ALERT, fmt, args...)
   416  }
   417  
   418  // Emergency level emergency
   419  func Emergency(fmt string, args ...interface{}) {
   420  	loggerDefault.deliverRecordToWriter(EMERGENCY, fmt, args...)
   421  }
   422  
   423  // The method is put here, so it's easy to test
   424  func getLevelDefault(flag string, defaultFlag int, writer string) int {
   425  	// level WARN == WARNING
   426  	if strings.EqualFold(flag, LevelFlagWarn) {
   427  		flag = LevelFlagWarning
   428  	}
   429  
   430  	for i, f := range LevelFlags {
   431  		if strings.TrimSpace(strings.ToUpper(flag)) == f {
   432  			return i
   433  		}
   434  	}
   435  	log.Printf("[log4go] no matching level for writer(%v, flag:%v), use default level(%d, flag:%v)", writer, flag, defaultFlag, LevelFlags[defaultFlag])
   436  	return defaultFlag
   437  }