123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465 |
- package logx
- import (
- "fmt"
- "io"
- "log"
- "os"
- "path"
- "runtime/debug"
- "sync"
- "sync/atomic"
- "time"
- "github.com/zeromicro/go-zero/core/sysx"
- )
- const callerDepth = 4
- var (
- timeFormat = "2006-01-02T15:04:05.000Z07:00"
- logLevel uint32
- encoding uint32 = jsonEncodingType
- // maxContentLength is used to truncate the log content, 0 for not truncating.
- maxContentLength uint32
- // use uint32 for atomic operations
- disableLog uint32
- disableStat uint32
- options logOptions
- writer = new(atomicWriter)
- setupOnce sync.Once
- )
- type (
- // LogField is a key-value pair that will be added to the log entry.
- LogField struct {
- Key string
- Value any
- }
- // LogOption defines the method to customize the logging.
- LogOption func(options *logOptions)
- logEntry map[string]any
- logOptions struct {
- gzipEnabled bool
- logStackCooldownMills int
- keepDays int
- maxBackups int
- maxSize int
- rotationRule string
- }
- )
- // Alert alerts v in alert level, and the message is written to error log.
- func Alert(v string) {
- getWriter().Alert(v)
- }
- // Close closes the logging.
- func Close() error {
- if w := writer.Swap(nil); w != nil {
- return w.(io.Closer).Close()
- }
- return nil
- }
- // Debug writes v into access log.
- func Debug(v ...any) {
- writeDebug(fmt.Sprint(v...))
- }
- // Debugf writes v with format into access log.
- func Debugf(format string, v ...any) {
- writeDebug(fmt.Sprintf(format, v...))
- }
- // Debugv writes v into access log with json content.
- func Debugv(v any) {
- writeDebug(v)
- }
- // Debugw writes msg along with fields into access log.
- func Debugw(msg string, fields ...LogField) {
- writeDebug(msg, fields...)
- }
- // Disable disables the logging.
- func Disable() {
- atomic.StoreUint32(&disableLog, 1)
- writer.Store(nopWriter{})
- }
- // DisableStat disables the stat logs.
- func DisableStat() {
- atomic.StoreUint32(&disableStat, 1)
- }
- // Error writes v into error log.
- func Error(v ...any) {
- writeError(fmt.Sprint(v...))
- }
- // Errorf writes v with format into error log.
- func Errorf(format string, v ...any) {
- writeError(fmt.Errorf(format, v...).Error())
- }
- // ErrorStack writes v along with call stack into error log.
- func ErrorStack(v ...any) {
- // there is newline in stack string
- writeStack(fmt.Sprint(v...))
- }
- // ErrorStackf writes v along with call stack in format into error log.
- func ErrorStackf(format string, v ...any) {
- // there is newline in stack string
- writeStack(fmt.Sprintf(format, v...))
- }
- // Errorv writes v into error log with json content.
- // No call stack attached, because not elegant to pack the messages.
- func Errorv(v any) {
- writeError(v)
- }
- // Errorw writes msg along with fields into error log.
- func Errorw(msg string, fields ...LogField) {
- writeError(msg, fields...)
- }
- // Field returns a LogField for the given key and value.
- func Field(key string, value any) LogField {
- switch val := value.(type) {
- case error:
- return LogField{Key: key, Value: val.Error()}
- case []error:
- var errs []string
- for _, err := range val {
- errs = append(errs, err.Error())
- }
- return LogField{Key: key, Value: errs}
- case time.Duration:
- return LogField{Key: key, Value: fmt.Sprint(val)}
- case []time.Duration:
- var durs []string
- for _, dur := range val {
- durs = append(durs, fmt.Sprint(dur))
- }
- return LogField{Key: key, Value: durs}
- case []time.Time:
- var times []string
- for _, t := range val {
- times = append(times, fmt.Sprint(t))
- }
- return LogField{Key: key, Value: times}
- case fmt.Stringer:
- return LogField{Key: key, Value: val.String()}
- case []fmt.Stringer:
- var strs []string
- for _, str := range val {
- strs = append(strs, str.String())
- }
- return LogField{Key: key, Value: strs}
- default:
- return LogField{Key: key, Value: val}
- }
- }
- // Info writes v into access log.
- func Info(v ...any) {
- writeInfo(fmt.Sprint(v...))
- }
- // Infof writes v with format into access log.
- func Infof(format string, v ...any) {
- writeInfo(fmt.Sprintf(format, v...))
- }
- // Infov writes v into access log with json content.
- func Infov(v any) {
- writeInfo(v)
- }
- // Infow writes msg along with fields into access log.
- func Infow(msg string, fields ...LogField) {
- writeInfo(msg, fields...)
- }
- // Must checks if err is nil, otherwise logs the error and exits.
- func Must(err error) {
- if err == nil {
- return
- }
- msg := err.Error()
- log.Print(msg)
- getWriter().Severe(msg)
- os.Exit(1)
- }
- // MustSetup sets up logging with given config c. It exits on error.
- func MustSetup(c LogConf) {
- Must(SetUp(c))
- }
- // Reset clears the writer and resets the log level.
- func Reset() Writer {
- return writer.Swap(nil)
- }
- // SetLevel sets the logging level. It can be used to suppress some logs.
- func SetLevel(level uint32) {
- atomic.StoreUint32(&logLevel, level)
- }
- // SetWriter sets the logging writer. It can be used to customize the logging.
- func SetWriter(w Writer) {
- if atomic.LoadUint32(&disableLog) == 0 {
- writer.Store(w)
- }
- }
- // SetUp sets up the logx. If already set up, just return nil.
- // we allow SetUp to be called multiple times, because for example
- // we need to allow different service frameworks to initialize logx respectively.
- func SetUp(c LogConf) (err error) {
- // Just ignore the subsequent SetUp calls.
- // Because multiple services in one process might call SetUp respectively.
- // Need to wait for the first caller to complete the execution.
- setupOnce.Do(func() {
- setupLogLevel(c)
- if !c.Stat {
- DisableStat()
- }
- if len(c.TimeFormat) > 0 {
- timeFormat = c.TimeFormat
- }
- atomic.StoreUint32(&maxContentLength, c.MaxContentLength)
- switch c.Encoding {
- case plainEncoding:
- atomic.StoreUint32(&encoding, plainEncodingType)
- default:
- atomic.StoreUint32(&encoding, jsonEncodingType)
- }
- switch c.Mode {
- case fileMode:
- err = setupWithFiles(c)
- case volumeMode:
- err = setupWithVolume(c)
- default:
- setupWithConsole()
- }
- })
- return
- }
- // Severe writes v into severe log.
- func Severe(v ...any) {
- writeSevere(fmt.Sprint(v...))
- }
- // Severef writes v with format into severe log.
- func Severef(format string, v ...any) {
- writeSevere(fmt.Sprintf(format, v...))
- }
- // Slow writes v into slow log.
- func Slow(v ...any) {
- writeSlow(fmt.Sprint(v...))
- }
- // Slowf writes v with format into slow log.
- func Slowf(format string, v ...any) {
- writeSlow(fmt.Sprintf(format, v...))
- }
- // Slowv writes v into slow log with json content.
- func Slowv(v any) {
- writeSlow(v)
- }
- // Sloww writes msg along with fields into slow log.
- func Sloww(msg string, fields ...LogField) {
- writeSlow(msg, fields...)
- }
- // Stat writes v into stat log.
- func Stat(v ...any) {
- writeStat(fmt.Sprint(v...))
- }
- // Statf writes v with format into stat log.
- func Statf(format string, v ...any) {
- writeStat(fmt.Sprintf(format, v...))
- }
- // WithCooldownMillis customizes logging on writing call stack interval.
- func WithCooldownMillis(millis int) LogOption {
- return func(opts *logOptions) {
- opts.logStackCooldownMills = millis
- }
- }
- // WithKeepDays customizes logging to keep logs with days.
- func WithKeepDays(days int) LogOption {
- return func(opts *logOptions) {
- opts.keepDays = days
- }
- }
- // WithGzip customizes logging to automatically gzip the log files.
- func WithGzip() LogOption {
- return func(opts *logOptions) {
- opts.gzipEnabled = true
- }
- }
- // WithMaxBackups customizes how many log files backups will be kept.
- func WithMaxBackups(count int) LogOption {
- return func(opts *logOptions) {
- opts.maxBackups = count
- }
- }
- // WithMaxSize customizes how much space the writing log file can take up.
- func WithMaxSize(size int) LogOption {
- return func(opts *logOptions) {
- opts.maxSize = size
- }
- }
- // WithRotation customizes which log rotation rule to use.
- func WithRotation(r string) LogOption {
- return func(opts *logOptions) {
- opts.rotationRule = r
- }
- }
- func addCaller(fields ...LogField) []LogField {
- return append(fields, Field(callerKey, getCaller(callerDepth)))
- }
- func createOutput(path string) (io.WriteCloser, error) {
- if len(path) == 0 {
- return nil, ErrLogPathNotSet
- }
- switch options.rotationRule {
- case sizeRotationRule:
- return NewLogger(path, NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays,
- options.maxSize, options.maxBackups, options.gzipEnabled), options.gzipEnabled)
- default:
- return NewLogger(path, DefaultRotateRule(path, backupFileDelimiter, options.keepDays,
- options.gzipEnabled), options.gzipEnabled)
- }
- }
- func getWriter() Writer {
- w := writer.Load()
- if w == nil {
- w = writer.StoreIfNil(newConsoleWriter())
- }
- return w
- }
- func handleOptions(opts []LogOption) {
- for _, opt := range opts {
- opt(&options)
- }
- }
- func setupLogLevel(c LogConf) {
- switch c.Level {
- case levelDebug:
- SetLevel(DebugLevel)
- case levelInfo:
- SetLevel(InfoLevel)
- case levelError:
- SetLevel(ErrorLevel)
- case levelSevere:
- SetLevel(SevereLevel)
- }
- }
- func setupWithConsole() {
- SetWriter(newConsoleWriter())
- }
- func setupWithFiles(c LogConf) error {
- w, err := newFileWriter(c)
- if err != nil {
- return err
- }
- SetWriter(w)
- return nil
- }
- func setupWithVolume(c LogConf) error {
- if len(c.ServiceName) == 0 {
- return ErrLogServiceNameNotSet
- }
- c.Path = path.Join(c.Path, c.ServiceName, sysx.Hostname())
- return setupWithFiles(c)
- }
- func shallLog(level uint32) bool {
- return atomic.LoadUint32(&logLevel) <= level
- }
- func shallLogStat() bool {
- return atomic.LoadUint32(&disableStat) == 0
- }
- func writeDebug(val any, fields ...LogField) {
- if shallLog(DebugLevel) {
- getWriter().Debug(val, addCaller(fields...)...)
- }
- }
- func writeError(val any, fields ...LogField) {
- if shallLog(ErrorLevel) {
- getWriter().Error(val, addCaller(fields...)...)
- }
- }
- func writeInfo(val any, fields ...LogField) {
- if shallLog(InfoLevel) {
- getWriter().Info(val, addCaller(fields...)...)
- }
- }
- func writeSevere(msg string) {
- if shallLog(SevereLevel) {
- getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
- }
- }
- func writeSlow(val any, fields ...LogField) {
- if shallLog(ErrorLevel) {
- getWriter().Slow(val, addCaller(fields...)...)
- }
- }
- func writeStack(msg string) {
- if shallLog(ErrorLevel) {
- getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack())))
- }
- }
- func writeStat(msg string) {
- if shallLogStat() && shallLog(InfoLevel) {
- getWriter().Stat(msg, addCaller()...)
- }
- }
|