feat: optimize must log add stack

main
NoahLan 1 year ago
parent b26579531e
commit f7620d4eaa

@ -13,11 +13,11 @@ import (
"time" "time"
) )
const CallerDepth = 4 const callerDepth = 4
var ( var (
timeFormat = "2006-01-02T15:04:05.000Z07:00" timeFormat = "2006-01-02T15:04:05.000Z07:00"
logLevel uint32 = DebugLevel logLevel uint32
encoding uint32 = plainEncodingType encoding uint32 = plainEncodingType
// maxContentLength is used to truncate the log content, 0 for not truncating. // maxContentLength is used to truncate the log content, 0 for not truncating.
maxContentLength uint32 maxContentLength uint32
@ -51,172 +51,303 @@ 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. // Alert alerts v in alert level, and the message is written to error log.
func Alert(v string) { 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. // Debug writes v into access log.
func Debug(v ...any) { func Debug(v ...any) {
if shallLog(DebugLevel) {
writeDebug(fmt.Sprint(v...)) writeDebug(fmt.Sprint(v...))
}
} }
// Debugf writes v with format into access log. // Debugf writes v with format into access log.
func Debugf(format string, v ...any) { func Debugf(format string, v ...any) {
if shallLog(DebugLevel) {
writeDebug(fmt.Sprintf(format, v...)) writeDebug(fmt.Sprintf(format, v...))
}
} }
// Debugv writes v into access log with json content. // Debugv writes v into access log with json content.
func Debugv(v any) { func Debugv(v any) {
if shallLog(DebugLevel) {
writeDebug(v) writeDebug(v)
}
} }
// Debugw writes msg along with fields into access log. // Debugw writes msg along with fields into access log.
func Debugw(msg string, fields ...LogField) { func Debugw(msg string, fields ...LogField) {
if shallLog(DebugLevel) {
writeDebug(msg, fields...) 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. // Error writes v into error log.
func Error(v ...any) { func Error(v ...any) {
if shallLog(ErrorLevel) {
writeError(fmt.Sprint(v...)) writeError(fmt.Sprint(v...))
}
} }
// Errorf writes v with format into error log. // Errorf writes v with format into error log.
func Errorf(format string, v ...any) { func Errorf(format string, v ...any) {
if shallLog(ErrorLevel) {
writeError(fmt.Errorf(format, v...).Error()) writeError(fmt.Errorf(format, v...).Error())
}
} }
// ErrorStack writes v along with call stack into error log. // ErrorStack writes v along with call stack into error log.
func ErrorStack(v ...any) { func ErrorStack(v ...any) {
if shallLog(ErrorLevel) {
// there is newline in stack string // there is newline in stack string
writeStack(fmt.Sprint(v...)) writeStack(fmt.Sprint(v...))
}
} }
// ErrorStackf writes v along with call stack in format into error log. // ErrorStackf writes v along with call stack in format into error log.
func ErrorStackf(format string, v ...any) { func ErrorStackf(format string, v ...any) {
if shallLog(ErrorLevel) {
// there is newline in stack string // there is newline in stack string
writeStack(fmt.Sprintf(format, v...)) writeStack(fmt.Sprintf(format, v...))
}
} }
// Errorv writes v into error log with json content. // Errorv writes v into error log with json content.
// No call stack attached, because not elegant to pack the messages. // No call stack attached, because not elegant to pack the messages.
func Errorv(v any) { func Errorv(v any) {
if shallLog(ErrorLevel) {
writeError(v) writeError(v)
}
} }
// Errorw writes msg along with fields into error log. // Errorw writes msg along with fields into error log.
func Errorw(msg string, fields ...LogField) { func Errorw(msg string, fields ...LogField) {
if shallLog(ErrorLevel) {
writeError(msg, fields...) writeError(msg, fields...)
}
} }
// Must checks if err is nil, otherwise logs the error and exits. // Field returns a LogField for the given key and value.
func Must(err error) { func Field(key string, value any) LogField {
if err == nil { switch val := value.(type) {
return 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. // Info writes v into access log.
func Info(v ...any) { func Info(v ...any) {
if shallLog(InfoLevel) {
writeInfo(fmt.Sprint(v...)) writeInfo(fmt.Sprint(v...))
}
} }
// Infof writes v with format into access log. // Infof writes v with format into access log.
func Infof(format string, v ...any) { func Infof(format string, v ...any) {
if shallLog(InfoLevel) {
writeInfo(fmt.Sprintf(format, v...)) writeInfo(fmt.Sprintf(format, v...))
}
} }
// Infov writes v into access log with json content. // Infov writes v into access log with json content.
func Infov(v any) { func Infov(v any) {
if shallLog(InfoLevel) {
writeInfo(v) writeInfo(v)
}
} }
// Infow writes msg along with fields into access log. // Infow writes msg along with fields into access log.
func Infow(msg string, fields ...LogField) { func Infow(msg string, fields ...LogField) {
if shallLog(InfoLevel) {
writeInfo(msg, fields...) writeInfo(msg, fields...)
}
}
// Must checks if err is nil, otherwise logs the error and exits.
func Must(err error) {
if err == nil {
return
}
msg := fmt.Sprintf("%+v\n\n%s", err.Error(), debug.Stack())
log.Print(msg)
getWriter().Severe(msg)
if ExitOnFatal.True() {
os.Exit(1)
} else {
panic(msg)
}
}
// 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. // Severe writes v into severe log.
func Severe(v ...any) { func Severe(v ...any) {
if shallLog(SevereLevel) {
writeSevere(fmt.Sprint(v...)) writeSevere(fmt.Sprint(v...))
}
} }
// Severef writes v with format into severe log. // Severef writes v with format into severe log.
func Severef(format string, v ...any) { func Severef(format string, v ...any) {
if shallLog(SevereLevel) {
writeSevere(fmt.Sprintf(format, v...)) writeSevere(fmt.Sprintf(format, v...))
}
} }
// Slow writes v into slow log. // Slow writes v into slow log.
func Slow(v ...any) { func Slow(v ...any) {
if shallLog(ErrorLevel) {
writeSlow(fmt.Sprint(v...)) writeSlow(fmt.Sprint(v...))
}
} }
// Slowf writes v with format into slow log. // Slowf writes v with format into slow log.
func Slowf(format string, v ...any) { func Slowf(format string, v ...any) {
if shallLog(ErrorLevel) {
writeSlow(fmt.Sprintf(format, v...)) writeSlow(fmt.Sprintf(format, v...))
}
} }
// Slowv writes v into slow log with json content. // Slowv writes v into slow log with json content.
func Slowv(v any) { func Slowv(v any) {
if shallLog(ErrorLevel) {
writeSlow(v) writeSlow(v)
}
} }
// Sloww writes msg along with fields into slow log. // Sloww writes msg along with fields into slow log.
func Sloww(msg string, fields ...LogField) { func Sloww(msg string, fields ...LogField) {
if shallLog(ErrorLevel) {
writeSlow(msg, fields...) writeSlow(msg, fields...)
}
} }
// Stat writes v into stat log. // Stat writes v into stat log.
func Stat(v ...any) { func Stat(v ...any) {
if shallLogStat() && shallLog(InfoLevel) {
writeStat(fmt.Sprint(v...)) writeStat(fmt.Sprint(v...))
}
} }
// Statf writes v with format into stat log. // Statf writes v with format into stat log.
func Statf(format string, v ...any) { func Statf(format string, v ...any) {
if shallLogStat() && shallLog(InfoLevel) {
writeStat(fmt.Sprintf(format, v...)) writeStat(fmt.Sprintf(format, v...))
}
} }
// WithCooldownMillis customizes logging on writing call stack interval. // WithCooldownMillis customizes logging on writing call stack interval.
@ -261,90 +392,8 @@ func WithRotation(r string) LogOption {
} }
} }
// 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
}
// Close closes the logging.
func Close() error {
if w := writer.Swap(nil); w != nil {
return w.(io.Closer).Close()
}
return nil
}
// Disable disables the logging.
func Disable() {
atomic.StoreUint32(&disableLog, 1)
writer.Store(nopWriter{})
}
// DisableStat disables the stat logs.
func DisableStat() {
atomic.StoreUint32(&disableStat, 1)
}
func addCaller(fields ...LogField) []LogField { 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) { func createOutput(path string) (io.WriteCloser, error) {
@ -352,17 +401,19 @@ func createOutput(path string) (io.WriteCloser, error) {
return nil, ErrLogPathNotSet return nil, ErrLogPathNotSet
} }
var rule RotateRule
switch options.rotationRule { switch options.rotationRule {
case sizeRotationRule: case sizeRotationRule:
return NewLogger(path, NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays, rule = NewSizeLimitRotateRule(path, backupFileDelimiter, options.keepDays, options.maxSize,
options.maxSize, options.maxBackups, options.gzipEnabled), options.gzipEnabled) options.maxBackups, options.gzipEnabled)
default: default:
return NewLogger(path, DefaultRotateRule(path, backupFileDelimiter, options.keepDays, rule = DefaultRotateRule(path, backupFileDelimiter, options.keepDays, options.gzipEnabled)
options.gzipEnabled), options.gzipEnabled)
} }
return NewLogger(path, rule, options.gzipEnabled)
} }
func GetWriter() Writer { func getWriter() Writer {
w := writer.Load() w := writer.Load()
if w == nil { if w == nil {
w = writer.StoreIfNil(newConsoleWriter()) w = writer.StoreIfNil(newConsoleWriter())
@ -421,44 +472,58 @@ func shallLogStat() bool {
return atomic.LoadUint32(&disableStat) == 0 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) { 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) { 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) { 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) { 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) { 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) { 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) { func writeStat(msg string) {
if shallLogStat() && shallLog(InfoLevel) { getWriter().Stat(msg, addCaller()...)
GetWriter().Stat(msg, addCaller()...)
}
} }

@ -1,6 +1,9 @@
package nlog package nlog
import "errors" import (
"errors"
"git.noahlan.cn/noahlan/ntool/nsys/atomic"
)
const ( const (
// DebugLevel logs everything // DebugLevel logs everything
@ -61,6 +64,8 @@ var (
ErrLogPathNotSet = errors.New("log path must be set") ErrLogPathNotSet = errors.New("log path must be set")
// ErrLogServiceNameNotSet is an error that indicates that the service name is not set. // ErrLogServiceNameNotSet is an error that indicates that the service name is not set.
ErrLogServiceNameNotSet = errors.New("log service name must be 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) truncatedField = Field(truncatedKey, true)
) )

@ -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
}
Loading…
Cancel
Save