github.com/shuguocloud/go-zero@v1.3.0/core/logx/logs.go (about)

     1  package logx
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"log"
    11  	"os"
    12  	"path"
    13  	"runtime"
    14  	"runtime/debug"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"sync/atomic"
    19  	"time"
    20  
    21  	"github.com/shuguocloud/go-zero/core/iox"
    22  	"github.com/shuguocloud/go-zero/core/sysx"
    23  	"github.com/shuguocloud/go-zero/core/timex"
    24  )
    25  
    26  const (
    27  	// InfoLevel logs everything
    28  	InfoLevel = iota
    29  	// ErrorLevel includes errors, slows, stacks
    30  	ErrorLevel
    31  	// SevereLevel only log severe messages
    32  	SevereLevel
    33  )
    34  
    35  const (
    36  	jsonEncodingType = iota
    37  	plainEncodingType
    38  
    39  	jsonEncoding     = "json"
    40  	plainEncoding    = "plain"
    41  	plainEncodingSep = '\t'
    42  )
    43  
    44  const (
    45  	accessFilename = "access.log"
    46  	errorFilename  = "error.log"
    47  	severeFilename = "severe.log"
    48  	slowFilename   = "slow.log"
    49  	statFilename   = "stat.log"
    50  
    51  	consoleMode = "console"
    52  	volumeMode  = "volume"
    53  
    54  	levelAlert  = "alert"
    55  	levelInfo   = "info"
    56  	levelError  = "error"
    57  	levelSevere = "severe"
    58  	levelFatal  = "fatal"
    59  	levelSlow   = "slow"
    60  	levelStat   = "stat"
    61  
    62  	backupFileDelimiter = "-"
    63  	callerInnerDepth    = 5
    64  	flags               = 0x0
    65  )
    66  
    67  var (
    68  	// ErrLogPathNotSet is an error that indicates the log path is not set.
    69  	ErrLogPathNotSet = errors.New("log path must be set")
    70  	// ErrLogNotInitialized is an error that log is not initialized.
    71  	ErrLogNotInitialized = errors.New("log not initialized")
    72  	// ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
    73  	ErrLogServiceNameNotSet = errors.New("log service name must be set")
    74  
    75  	timeFormat   = "2006-01-02T15:04:05.000Z07"
    76  	writeConsole bool
    77  	logLevel     uint32
    78  	encoding     = jsonEncodingType
    79  	// use uint32 for atomic operations
    80  	disableStat uint32
    81  	infoLog     io.WriteCloser
    82  	errorLog    io.WriteCloser
    83  	severeLog   io.WriteCloser
    84  	slowLog     io.WriteCloser
    85  	statLog     io.WriteCloser
    86  	stackLog    io.Writer
    87  
    88  	once        sync.Once
    89  	initialized uint32
    90  	options     logOptions
    91  )
    92  
    93  type (
    94  	logEntry struct {
    95  		Timestamp string      `json:"@timestamp"`
    96  		Level     string      `json:"level"`
    97  		Duration  string      `json:"duration,omitempty"`
    98  		Content   interface{} `json:"content"`
    99  	}
   100  
   101  	logOptions struct {
   102  		gzipEnabled           bool
   103  		logStackCooldownMills int
   104  		keepDays              int
   105  	}
   106  
   107  	// LogOption defines the method to customize the logging.
   108  	LogOption func(options *logOptions)
   109  
   110  	// A Logger represents a logger.
   111  	Logger interface {
   112  		Error(...interface{})
   113  		Errorf(string, ...interface{})
   114  		Errorv(interface{})
   115  		Info(...interface{})
   116  		Infof(string, ...interface{})
   117  		Infov(interface{})
   118  		Slow(...interface{})
   119  		Slowf(string, ...interface{})
   120  		Slowv(interface{})
   121  		WithDuration(time.Duration) Logger
   122  	}
   123  )
   124  
   125  // MustSetup sets up logging with given config c. It exits on error.
   126  func MustSetup(c LogConf) {
   127  	Must(SetUp(c))
   128  }
   129  
   130  // SetUp sets up the logx. If already set up, just return nil.
   131  // we allow SetUp to be called multiple times, because for example
   132  // we need to allow different service frameworks to initialize logx respectively.
   133  // the same logic for SetUp
   134  func SetUp(c LogConf) error {
   135  	if len(c.TimeFormat) > 0 {
   136  		timeFormat = c.TimeFormat
   137  	}
   138  	switch c.Encoding {
   139  	case plainEncoding:
   140  		encoding = plainEncodingType
   141  	default:
   142  		encoding = jsonEncodingType
   143  	}
   144  
   145  	switch c.Mode {
   146  	case consoleMode:
   147  		setupWithConsole(c)
   148  		return nil
   149  	case volumeMode:
   150  		return setupWithVolume(c)
   151  	default:
   152  		return setupWithFiles(c)
   153  	}
   154  }
   155  
   156  // Alert alerts v in alert level, and the message is written to error log.
   157  func Alert(v string) {
   158  	outputText(errorLog, levelAlert, v)
   159  }
   160  
   161  // Close closes the logging.
   162  func Close() error {
   163  	if writeConsole {
   164  		return nil
   165  	}
   166  
   167  	if atomic.LoadUint32(&initialized) == 0 {
   168  		return ErrLogNotInitialized
   169  	}
   170  
   171  	atomic.StoreUint32(&initialized, 0)
   172  
   173  	if infoLog != nil {
   174  		if err := infoLog.Close(); err != nil {
   175  			return err
   176  		}
   177  	}
   178  
   179  	if errorLog != nil {
   180  		if err := errorLog.Close(); err != nil {
   181  			return err
   182  		}
   183  	}
   184  
   185  	if severeLog != nil {
   186  		if err := severeLog.Close(); err != nil {
   187  			return err
   188  		}
   189  	}
   190  
   191  	if slowLog != nil {
   192  		if err := slowLog.Close(); err != nil {
   193  			return err
   194  		}
   195  	}
   196  
   197  	if statLog != nil {
   198  		if err := statLog.Close(); err != nil {
   199  			return err
   200  		}
   201  	}
   202  
   203  	return nil
   204  }
   205  
   206  // Disable disables the logging.
   207  func Disable() {
   208  	once.Do(func() {
   209  		atomic.StoreUint32(&initialized, 1)
   210  
   211  		infoLog = iox.NopCloser(ioutil.Discard)
   212  		errorLog = iox.NopCloser(ioutil.Discard)
   213  		severeLog = iox.NopCloser(ioutil.Discard)
   214  		slowLog = iox.NopCloser(ioutil.Discard)
   215  		statLog = iox.NopCloser(ioutil.Discard)
   216  		stackLog = ioutil.Discard
   217  	})
   218  }
   219  
   220  // DisableStat disables the stat logs.
   221  func DisableStat() {
   222  	atomic.StoreUint32(&disableStat, 1)
   223  }
   224  
   225  // Error writes v into error log.
   226  func Error(v ...interface{}) {
   227  	ErrorCaller(1, v...)
   228  }
   229  
   230  // ErrorCaller writes v with context into error log.
   231  func ErrorCaller(callDepth int, v ...interface{}) {
   232  	errorTextSync(fmt.Sprint(v...), callDepth+callerInnerDepth)
   233  }
   234  
   235  // ErrorCallerf writes v with context in format into error log.
   236  func ErrorCallerf(callDepth int, format string, v ...interface{}) {
   237  	errorTextSync(fmt.Errorf(format, v...).Error(), callDepth+callerInnerDepth)
   238  }
   239  
   240  // Errorf writes v with format into error log.
   241  func Errorf(format string, v ...interface{}) {
   242  	ErrorCallerf(1, format, v...)
   243  }
   244  
   245  // ErrorStack writes v along with call stack into error log.
   246  func ErrorStack(v ...interface{}) {
   247  	// there is newline in stack string
   248  	stackSync(fmt.Sprint(v...))
   249  }
   250  
   251  // ErrorStackf writes v along with call stack in format into error log.
   252  func ErrorStackf(format string, v ...interface{}) {
   253  	// there is newline in stack string
   254  	stackSync(fmt.Sprintf(format, v...))
   255  }
   256  
   257  // Errorv writes v into error log with json content.
   258  // No call stack attached, because not elegant to pack the messages.
   259  func Errorv(v interface{}) {
   260  	errorAnySync(v)
   261  }
   262  
   263  // Info writes v into access log.
   264  func Info(v ...interface{}) {
   265  	infoTextSync(fmt.Sprint(v...))
   266  }
   267  
   268  // Infof writes v with format into access log.
   269  func Infof(format string, v ...interface{}) {
   270  	infoTextSync(fmt.Sprintf(format, v...))
   271  }
   272  
   273  // Infov writes v into access log with json content.
   274  func Infov(v interface{}) {
   275  	infoAnySync(v)
   276  }
   277  
   278  // Must checks if err is nil, otherwise logs the err and exits.
   279  func Must(err error) {
   280  	if err != nil {
   281  		msg := formatWithCaller(err.Error(), 3)
   282  		log.Print(msg)
   283  		outputText(severeLog, levelFatal, msg)
   284  		os.Exit(1)
   285  	}
   286  }
   287  
   288  // SetLevel sets the logging level. It can be used to suppress some logs.
   289  func SetLevel(level uint32) {
   290  	atomic.StoreUint32(&logLevel, level)
   291  }
   292  
   293  // Severe writes v into severe log.
   294  func Severe(v ...interface{}) {
   295  	severeSync(fmt.Sprint(v...))
   296  }
   297  
   298  // Severef writes v with format into severe log.
   299  func Severef(format string, v ...interface{}) {
   300  	severeSync(fmt.Sprintf(format, v...))
   301  }
   302  
   303  // Slow writes v into slow log.
   304  func Slow(v ...interface{}) {
   305  	slowTextSync(fmt.Sprint(v...))
   306  }
   307  
   308  // Slowf writes v with format into slow log.
   309  func Slowf(format string, v ...interface{}) {
   310  	slowTextSync(fmt.Sprintf(format, v...))
   311  }
   312  
   313  // Slowv writes v into slow log with json content.
   314  func Slowv(v interface{}) {
   315  	slowAnySync(v)
   316  }
   317  
   318  // Stat writes v into stat log.
   319  func Stat(v ...interface{}) {
   320  	statSync(fmt.Sprint(v...))
   321  }
   322  
   323  // Statf writes v with format into stat log.
   324  func Statf(format string, v ...interface{}) {
   325  	statSync(fmt.Sprintf(format, v...))
   326  }
   327  
   328  // WithCooldownMillis customizes logging on writing call stack interval.
   329  func WithCooldownMillis(millis int) LogOption {
   330  	return func(opts *logOptions) {
   331  		opts.logStackCooldownMills = millis
   332  	}
   333  }
   334  
   335  // WithKeepDays customizes logging to keep logs with days.
   336  func WithKeepDays(days int) LogOption {
   337  	return func(opts *logOptions) {
   338  		opts.keepDays = days
   339  	}
   340  }
   341  
   342  // WithGzip customizes logging to automatically gzip the log files.
   343  func WithGzip() LogOption {
   344  	return func(opts *logOptions) {
   345  		opts.gzipEnabled = true
   346  	}
   347  }
   348  
   349  func createOutput(path string) (io.WriteCloser, error) {
   350  	if len(path) == 0 {
   351  		return nil, ErrLogPathNotSet
   352  	}
   353  
   354  	return NewLogger(path, DefaultRotateRule(path, backupFileDelimiter, options.keepDays,
   355  		options.gzipEnabled), options.gzipEnabled)
   356  }
   357  
   358  func errorAnySync(v interface{}) {
   359  	if shallLog(ErrorLevel) {
   360  		outputAny(errorLog, levelError, v)
   361  	}
   362  }
   363  
   364  func errorTextSync(msg string, callDepth int) {
   365  	if shallLog(ErrorLevel) {
   366  		outputError(errorLog, msg, callDepth)
   367  	}
   368  }
   369  
   370  func formatWithCaller(msg string, callDepth int) string {
   371  	var buf strings.Builder
   372  
   373  	caller := getCaller(callDepth)
   374  	if len(caller) > 0 {
   375  		buf.WriteString(caller)
   376  		buf.WriteByte(' ')
   377  	}
   378  
   379  	buf.WriteString(msg)
   380  
   381  	return buf.String()
   382  }
   383  
   384  func getCaller(callDepth int) string {
   385  	var buf strings.Builder
   386  
   387  	_, file, line, ok := runtime.Caller(callDepth)
   388  	if ok {
   389  		short := file
   390  		for i := len(file) - 1; i > 0; i-- {
   391  			if file[i] == '/' {
   392  				short = file[i+1:]
   393  				break
   394  			}
   395  		}
   396  		buf.WriteString(short)
   397  		buf.WriteByte(':')
   398  		buf.WriteString(strconv.Itoa(line))
   399  	}
   400  
   401  	return buf.String()
   402  }
   403  
   404  func getTimestamp() string {
   405  	return timex.Time().Format(timeFormat)
   406  }
   407  
   408  func handleOptions(opts []LogOption) {
   409  	for _, opt := range opts {
   410  		opt(&options)
   411  	}
   412  }
   413  
   414  func infoAnySync(val interface{}) {
   415  	if shallLog(InfoLevel) {
   416  		outputAny(infoLog, levelInfo, val)
   417  	}
   418  }
   419  
   420  func infoTextSync(msg string) {
   421  	if shallLog(InfoLevel) {
   422  		outputText(infoLog, levelInfo, msg)
   423  	}
   424  }
   425  
   426  func outputAny(writer io.Writer, level string, val interface{}) {
   427  	switch encoding {
   428  	case plainEncodingType:
   429  		writePlainAny(writer, level, val)
   430  	default:
   431  		info := logEntry{
   432  			Timestamp: getTimestamp(),
   433  			Level:     level,
   434  			Content:   val,
   435  		}
   436  		outputJson(writer, info)
   437  	}
   438  }
   439  
   440  func outputText(writer io.Writer, level, msg string) {
   441  	switch encoding {
   442  	case plainEncodingType:
   443  		writePlainText(writer, level, msg)
   444  	default:
   445  		info := logEntry{
   446  			Timestamp: getTimestamp(),
   447  			Level:     level,
   448  			Content:   msg,
   449  		}
   450  		outputJson(writer, info)
   451  	}
   452  }
   453  
   454  func outputError(writer io.Writer, msg string, callDepth int) {
   455  	content := formatWithCaller(msg, callDepth)
   456  	outputText(writer, levelError, content)
   457  }
   458  
   459  func outputJson(writer io.Writer, info interface{}) {
   460  	if content, err := json.Marshal(info); err != nil {
   461  		log.Println(err.Error())
   462  	} else if atomic.LoadUint32(&initialized) == 0 || writer == nil {
   463  		log.Println(string(content))
   464  	} else {
   465  		writer.Write(append(content, '\n'))
   466  	}
   467  }
   468  
   469  func setupLogLevel(c LogConf) {
   470  	switch c.Level {
   471  	case levelInfo:
   472  		SetLevel(InfoLevel)
   473  	case levelError:
   474  		SetLevel(ErrorLevel)
   475  	case levelSevere:
   476  		SetLevel(SevereLevel)
   477  	}
   478  }
   479  
   480  func setupWithConsole(c LogConf) {
   481  	once.Do(func() {
   482  		atomic.StoreUint32(&initialized, 1)
   483  		writeConsole = true
   484  		setupLogLevel(c)
   485  
   486  		infoLog = newLogWriter(log.New(os.Stdout, "", flags))
   487  		errorLog = newLogWriter(log.New(os.Stderr, "", flags))
   488  		severeLog = newLogWriter(log.New(os.Stderr, "", flags))
   489  		slowLog = newLogWriter(log.New(os.Stderr, "", flags))
   490  		stackLog = newLessWriter(errorLog, options.logStackCooldownMills)
   491  		statLog = infoLog
   492  	})
   493  }
   494  
   495  func setupWithFiles(c LogConf) error {
   496  	var opts []LogOption
   497  	var err error
   498  
   499  	if len(c.Path) == 0 {
   500  		return ErrLogPathNotSet
   501  	}
   502  
   503  	opts = append(opts, WithCooldownMillis(c.StackCooldownMillis))
   504  	if c.Compress {
   505  		opts = append(opts, WithGzip())
   506  	}
   507  	if c.KeepDays > 0 {
   508  		opts = append(opts, WithKeepDays(c.KeepDays))
   509  	}
   510  
   511  	accessFile := path.Join(c.Path, accessFilename)
   512  	errorFile := path.Join(c.Path, errorFilename)
   513  	severeFile := path.Join(c.Path, severeFilename)
   514  	slowFile := path.Join(c.Path, slowFilename)
   515  	statFile := path.Join(c.Path, statFilename)
   516  
   517  	once.Do(func() {
   518  		atomic.StoreUint32(&initialized, 1)
   519  		handleOptions(opts)
   520  		setupLogLevel(c)
   521  
   522  		if infoLog, err = createOutput(accessFile); err != nil {
   523  			return
   524  		}
   525  
   526  		if errorLog, err = createOutput(errorFile); err != nil {
   527  			return
   528  		}
   529  
   530  		if severeLog, err = createOutput(severeFile); err != nil {
   531  			return
   532  		}
   533  
   534  		if slowLog, err = createOutput(slowFile); err != nil {
   535  			return
   536  		}
   537  
   538  		if statLog, err = createOutput(statFile); err != nil {
   539  			return
   540  		}
   541  
   542  		stackLog = newLessWriter(errorLog, options.logStackCooldownMills)
   543  	})
   544  
   545  	return err
   546  }
   547  
   548  func setupWithVolume(c LogConf) error {
   549  	if len(c.ServiceName) == 0 {
   550  		return ErrLogServiceNameNotSet
   551  	}
   552  
   553  	c.Path = path.Join(c.Path, c.ServiceName, sysx.Hostname())
   554  	return setupWithFiles(c)
   555  }
   556  
   557  func severeSync(msg string) {
   558  	if shallLog(SevereLevel) {
   559  		outputText(severeLog, levelSevere, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
   560  	}
   561  }
   562  
   563  func shallLog(level uint32) bool {
   564  	return atomic.LoadUint32(&logLevel) <= level
   565  }
   566  
   567  func shallLogStat() bool {
   568  	return atomic.LoadUint32(&disableStat) == 0
   569  }
   570  
   571  func slowAnySync(v interface{}) {
   572  	if shallLog(ErrorLevel) {
   573  		outputAny(slowLog, levelSlow, v)
   574  	}
   575  }
   576  
   577  func slowTextSync(msg string) {
   578  	if shallLog(ErrorLevel) {
   579  		outputText(slowLog, levelSlow, msg)
   580  	}
   581  }
   582  
   583  func stackSync(msg string) {
   584  	if shallLog(ErrorLevel) {
   585  		outputText(stackLog, levelError, fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
   586  	}
   587  }
   588  
   589  func statSync(msg string) {
   590  	if shallLogStat() && shallLog(InfoLevel) {
   591  		outputText(statLog, levelStat, msg)
   592  	}
   593  }
   594  
   595  func writePlainAny(writer io.Writer, level string, val interface{}, fields ...string) {
   596  	switch v := val.(type) {
   597  	case string:
   598  		writePlainText(writer, level, v, fields...)
   599  	case error:
   600  		writePlainText(writer, level, v.Error(), fields...)
   601  	case fmt.Stringer:
   602  		writePlainText(writer, level, v.String(), fields...)
   603  	default:
   604  		var buf bytes.Buffer
   605  		buf.WriteString(getTimestamp())
   606  		buf.WriteByte(plainEncodingSep)
   607  		buf.WriteString(level)
   608  		for _, item := range fields {
   609  			buf.WriteByte(plainEncodingSep)
   610  			buf.WriteString(item)
   611  		}
   612  		buf.WriteByte(plainEncodingSep)
   613  		if err := json.NewEncoder(&buf).Encode(val); err != nil {
   614  			log.Println(err.Error())
   615  			return
   616  		}
   617  		buf.WriteByte('\n')
   618  		if atomic.LoadUint32(&initialized) == 0 || writer == nil {
   619  			log.Println(buf.String())
   620  			return
   621  		}
   622  
   623  		if _, err := writer.Write(buf.Bytes()); err != nil {
   624  			log.Println(err.Error())
   625  		}
   626  	}
   627  }
   628  
   629  func writePlainText(writer io.Writer, level, msg string, fields ...string) {
   630  	var buf bytes.Buffer
   631  	buf.WriteString(getTimestamp())
   632  	buf.WriteByte(plainEncodingSep)
   633  	buf.WriteString(level)
   634  	for _, item := range fields {
   635  		buf.WriteByte(plainEncodingSep)
   636  		buf.WriteString(item)
   637  	}
   638  	buf.WriteByte(plainEncodingSep)
   639  	buf.WriteString(msg)
   640  	buf.WriteByte('\n')
   641  	if atomic.LoadUint32(&initialized) == 0 || writer == nil {
   642  		log.Println(buf.String())
   643  		return
   644  	}
   645  
   646  	if _, err := writer.Write(buf.Bytes()); err != nil {
   647  		log.Println(err.Error())
   648  	}
   649  }
   650  
   651  type logWriter struct {
   652  	logger *log.Logger
   653  }
   654  
   655  func newLogWriter(logger *log.Logger) logWriter {
   656  	return logWriter{
   657  		logger: logger,
   658  	}
   659  }
   660  
   661  func (lw logWriter) Close() error {
   662  	return nil
   663  }
   664  
   665  func (lw logWriter) Write(data []byte) (int, error) {
   666  	lw.logger.Print(string(data))
   667  	return len(data), nil
   668  }