diff --git a/nlog/logs.go b/nlog/logs.go index 02609e3..15d86d2 100644 --- a/nlog/logs.go +++ b/nlog/logs.go @@ -13,11 +13,11 @@ import ( "time" ) -const CallerDepth = 4 +const callerDepth = 4 var ( - timeFormat = "2006-01-02T15:04:05.000Z07:00" - logLevel uint32 = DebugLevel + timeFormat = "2006-01-02T15:04:05.000Z07:00" + logLevel uint32 encoding uint32 = plainEncodingType // maxContentLength is used to truncate the log content, 0 for not truncating. maxContentLength uint32 @@ -51,213 +51,184 @@ type ( } ) -// 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} - } -} - // Alert alerts v in alert level, and the message is written to error log. func Alert(v string) { - GetWriter().Alert(v) + 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...)) + if shallLog(DebugLevel) { + writeDebug(fmt.Sprint(v...)) + } } // Debugf writes v with format into access log. func Debugf(format string, v ...any) { - writeDebug(fmt.Sprintf(format, v...)) + if shallLog(DebugLevel) { + writeDebug(fmt.Sprintf(format, v...)) + } } // Debugv writes v into access log with json content. func Debugv(v any) { - writeDebug(v) + if shallLog(DebugLevel) { + writeDebug(v) + } } // Debugw writes msg along with fields into access log. func Debugw(msg string, fields ...LogField) { - writeDebug(msg, fields...) + if shallLog(DebugLevel) { + 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...)) + if shallLog(ErrorLevel) { + writeError(fmt.Sprint(v...)) + } } // Errorf writes v with format into error log. func Errorf(format string, v ...any) { - writeError(fmt.Errorf(format, v...).Error()) + if shallLog(ErrorLevel) { + 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...)) + if shallLog(ErrorLevel) { + // 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...)) + if shallLog(ErrorLevel) { + // 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) + if shallLog(ErrorLevel) { + writeError(v) + } } // Errorw writes msg along with fields into error log. func Errorw(msg string, fields ...LogField) { - writeError(msg, fields...) + if shallLog(ErrorLevel) { + writeError(msg, fields...) + } } -// Must checks if err is nil, otherwise logs the error and exits. -func Must(err error) { - if err == nil { - return +// 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} } - - msg := err.Error() - log.Print(msg) - GetWriter().Severe(msg) - os.Exit(1) } // Info writes v into access log. func Info(v ...any) { - writeInfo(fmt.Sprint(v...)) + if shallLog(InfoLevel) { + writeInfo(fmt.Sprint(v...)) + } } // Infof writes v with format into access log. func Infof(format string, v ...any) { - writeInfo(fmt.Sprintf(format, v...)) + if shallLog(InfoLevel) { + writeInfo(fmt.Sprintf(format, v...)) + } } // Infov writes v into access log with json content. func Infov(v any) { - writeInfo(v) + if shallLog(InfoLevel) { + writeInfo(v) + } } // Infow writes msg along with fields into access log. func Infow(msg string, fields ...LogField) { - writeInfo(msg, fields...) -} - -// 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 + if shallLog(InfoLevel) { + writeInfo(msg, fields...) } } -// WithMaxBackups customizes how many log files backups will be kept. -func WithMaxBackups(count int) LogOption { - return func(opts *logOptions) { - opts.maxBackups = count +// Must checks if err is nil, otherwise logs the error and exits. +func Must(err error) { + if err == nil { + return } -} -// WithMaxSize customizes how much space the writing log file can take up. -func WithMaxSize(size int) LogOption { - return func(opts *logOptions) { - opts.maxSize = size - } -} + msg := fmt.Sprintf("%+v\n\n%s", err.Error(), debug.Stack()) + log.Print(msg) + getWriter().Severe(msg) -// WithRotation customizes which log rotation rule to use. -func WithRotation(r string) LogOption { - return func(opts *logOptions) { - opts.rotationRule = r + if ExitOnFatal.True() { + os.Exit(1) + } else { + panic(msg) } } @@ -323,28 +294,106 @@ func SetUp(c LogConf) (err error) { return } -// Close closes the logging. -func Close() error { - if w := writer.Swap(nil); w != nil { - return w.(io.Closer).Close() +// Severe writes v into severe log. +func Severe(v ...any) { + if shallLog(SevereLevel) { + writeSevere(fmt.Sprint(v...)) } +} - return nil +// Severef writes v with format into severe log. +func Severef(format string, v ...any) { + if shallLog(SevereLevel) { + writeSevere(fmt.Sprintf(format, v...)) + } } -// Disable disables the logging. -func Disable() { - atomic.StoreUint32(&disableLog, 1) - writer.Store(nopWriter{}) +// Slow writes v into slow log. +func Slow(v ...any) { + if shallLog(ErrorLevel) { + writeSlow(fmt.Sprint(v...)) + } } -// DisableStat disables the stat logs. -func DisableStat() { - atomic.StoreUint32(&disableStat, 1) +// Slowf writes v with format into slow log. +func Slowf(format string, v ...any) { + if shallLog(ErrorLevel) { + writeSlow(fmt.Sprintf(format, v...)) + } +} + +// Slowv writes v into slow log with json content. +func Slowv(v any) { + if shallLog(ErrorLevel) { + writeSlow(v) + } +} + +// Sloww writes msg along with fields into slow log. +func Sloww(msg string, fields ...LogField) { + if shallLog(ErrorLevel) { + writeSlow(msg, fields...) + } +} + +// Stat writes v into stat log. +func Stat(v ...any) { + if shallLogStat() && shallLog(InfoLevel) { + writeStat(fmt.Sprint(v...)) + } +} + +// Statf writes v with format into stat log. +func Statf(format string, v ...any) { + if shallLogStat() && shallLog(InfoLevel) { + 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))) + return append(fields, Field(callerKey, getCaller(callerDepth))) } func createOutput(path string) (io.WriteCloser, error) { @@ -352,17 +401,19 @@ func createOutput(path string) (io.WriteCloser, error) { return nil, ErrLogPathNotSet } + var rule RotateRule switch options.rotationRule { case sizeRotationRule: - return NewLogger(path, NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays, - options.maxSize, options.maxBackups, options.gzipEnabled), options.gzipEnabled) + rule = NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays, options.maxSize, + options.maxBackups, options.gzipEnabled) default: - return NewLogger(path, DefaultRotateRule(path, backupFileDelimiter, options.keepDays, - options.gzipEnabled), options.gzipEnabled) + rule = DefaultRotateRule(path, backupFileDelimiter, options.keepDays, options.gzipEnabled) } + + return NewLogger(path, rule, options.gzipEnabled) } -func GetWriter() Writer { +func getWriter() Writer { w := writer.Load() if w == nil { w = writer.StoreIfNil(newConsoleWriter()) @@ -421,44 +472,58 @@ func shallLogStat() bool { return atomic.LoadUint32(&disableStat) == 0 } +// writeDebug writes v into debug log. +// Not checking shallLog here is for performance consideration. +// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled. +// The caller should check shallLog before calling this function. func writeDebug(val any, fields ...LogField) { - if shallLog(DebugLevel) { - GetWriter().Debug(val, addCaller(fields...)...) - } + getWriter().Debug(val, addCaller(fields...)...) } +// writeError writes v into error log. +// Not checking shallLog here is for performance consideration. +// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled. +// The caller should check shallLog before calling this function. func writeError(val any, fields ...LogField) { - if shallLog(ErrorLevel) { - GetWriter().Error(val, addCaller(fields...)...) - } + getWriter().Error(val, addCaller(fields...)...) } +// writeInfo writes v into info log. +// Not checking shallLog here is for performance consideration. +// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled. +// The caller should check shallLog before calling this function. func writeInfo(val any, fields ...LogField) { - if shallLog(InfoLevel) { - GetWriter().Info(val, addCaller(fields...)...) - } + getWriter().Info(val, addCaller(fields...)...) } +// writeSevere writes v into severe log. +// Not checking shallLog here is for performance consideration. +// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled. +// The caller should check shallLog before calling this function. func writeSevere(msg string) { - if shallLog(SevereLevel) { - GetWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack()))) - } + getWriter().Severe(fmt.Sprintf("%s\n%s", msg, string(debug.Stack()))) } +// writeSlow writes v into slow log. +// Not checking shallLog here is for performance consideration. +// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled. +// The caller should check shallLog before calling this function. func writeSlow(val any, fields ...LogField) { - if shallLog(ErrorLevel) { - GetWriter().Slow(val, addCaller(fields...)...) - } + getWriter().Slow(val, addCaller(fields...)...) } +// writeStack writes v into stack log. +// Not checking shallLog here is for performance consideration. +// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled. +// The caller should check shallLog before calling this function. func writeStack(msg string) { - if shallLog(ErrorLevel) { - GetWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack()))) - } + getWriter().Stack(fmt.Sprintf("%s\n%s", msg, string(debug.Stack()))) } +// writeStat writes v into stat log. +// Not checking shallLog here is for performance consideration. +// If we check shallLog here, the fmt.Sprint might be called even if the log level is not enabled. +// The caller should check shallLog before calling this function. func writeStat(msg string) { - if shallLogStat() && shallLog(InfoLevel) { - GetWriter().Stat(msg, addCaller()...) - } + getWriter().Stat(msg, addCaller()...) } diff --git a/nlog/vars.go b/nlog/vars.go index 50bc536..7051f5a 100644 --- a/nlog/vars.go +++ b/nlog/vars.go @@ -1,6 +1,9 @@ package nlog -import "errors" +import ( + "errors" + "git.noahlan.cn/noahlan/ntool/nsys/atomic" +) const ( // DebugLevel logs everything @@ -61,6 +64,8 @@ var ( ErrLogPathNotSet = errors.New("log path must be set") // ErrLogServiceNameNotSet is an error that indicates that the service name is not set. ErrLogServiceNameNotSet = errors.New("log service name must be set") + // ExitOnFatal defines whether to exit on fatal errors, defined here to make it easier to test. + ExitOnFatal = atomic.ForAtomicBool(true) truncatedField = Field(truncatedKey, true) ) diff --git a/nsys/atomic/atomic_bool.go b/nsys/atomic/atomic_bool.go new file mode 100644 index 0000000..01a8110 --- /dev/null +++ b/nsys/atomic/atomic_bool.go @@ -0,0 +1,46 @@ +package atomic + +import "sync/atomic" + +// An AtomicBool is an atomic implementation for boolean values. +type AtomicBool uint32 + +// NewAtomicBool returns an AtomicBool. +func NewAtomicBool() *AtomicBool { + return new(AtomicBool) +} + +// ForAtomicBool returns an AtomicBool with given val. +func ForAtomicBool(val bool) *AtomicBool { + b := NewAtomicBool() + b.Set(val) + return b +} + +// CompareAndSwap compares current value with given old, if equals, set to given val. +func (b *AtomicBool) CompareAndSwap(old, val bool) bool { + var ov, nv uint32 + + if old { + ov = 1 + } + if val { + nv = 1 + } + + return atomic.CompareAndSwapUint32((*uint32)(b), ov, nv) +} + +// Set sets the value to v. +func (b *AtomicBool) Set(v bool) { + if v { + atomic.StoreUint32((*uint32)(b), 1) + } else { + atomic.StoreUint32((*uint32)(b), 0) + } +} + +// True returns true if current value is true. +func (b *AtomicBool) True() bool { + return atomic.LoadUint32((*uint32)(b)) == 1 +}