You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
396 lines
8.4 KiB
396 lines
8.4 KiB
package logx |
|
|
|
import ( |
|
"fmt" |
|
"git.diulo.com/mogfee/kit/core/sysx" |
|
"io" |
|
"log" |
|
"os" |
|
"path" |
|
"runtime/debug" |
|
"sync" |
|
"sync/atomic" |
|
"time" |
|
) |
|
|
|
const callerDepth = 4 |
|
|
|
var ( |
|
timeFormat = "2006-01-02T15:04:05.000Z07:00" |
|
logLevel uint32 |
|
encoding uint32 = jsonEncodingType |
|
maxContentLength uint32 |
|
disableLog uint32 |
|
disableStat uint32 |
|
options logOptions |
|
writer = new(atomicWriter) |
|
setupOnce sync.Once |
|
) |
|
|
|
type ( |
|
LogField struct { |
|
Key string |
|
Value any |
|
} |
|
|
|
LogOption func(opts *logOptions) |
|
|
|
logEntry map[string]any |
|
|
|
logOptions struct { |
|
gzipEnabled bool |
|
logStackCooldownMills int |
|
keepDays int |
|
maxBackups int |
|
maxSize int |
|
rotationRule string |
|
} |
|
) |
|
|
|
func Alert(v string) { |
|
getWriter().Alert(v) |
|
} |
|
func Close() error { |
|
if w := writer.Swap(nil); w != nil { |
|
return w.(io.Closer).Close() |
|
} |
|
return nil |
|
} |
|
func Disable() { |
|
atomic.StoreUint32(&disableLog, 1) |
|
writer.Store(nopWriter{}) |
|
} |
|
func DisableStat() { |
|
atomic.StoreUint32(&disableStat, 1) |
|
} |
|
func Debug(v ...any) { |
|
writeDebug(fmt.Sprint(v...)) |
|
} |
|
|
|
func Debugf(format string, v ...any) { |
|
writeDebug(fmt.Sprintf(format, v...)) |
|
} |
|
func Error(v ...any) { |
|
writeError(fmt.Sprint(v...)) |
|
} |
|
func Errorf(format string, v ...any) { |
|
writeError(fmt.Errorf(format, v...)) |
|
} |
|
func ErrorStack(v ...any) { |
|
writeStack(fmt.Sprint(v...)) |
|
} |
|
func ErrorStackf(format string, v ...any) { |
|
writeStack(fmt.Sprintf(format, v...)) |
|
} |
|
func Info(v ...any) { |
|
writeInfo(fmt.Sprint(v...)) |
|
} |
|
func Infof(format string, v ...any) { |
|
writeInfo(fmt.Sprintf(format, v...)) |
|
} |
|
func Must(err error) { |
|
if err == nil { |
|
return |
|
} |
|
msg := err.Error() |
|
log.Print(err) |
|
getWriter().Severe(msg) |
|
os.Exit(1) |
|
} |
|
func Rest() Writer { |
|
return writer.Swap(nil) |
|
} |
|
func SetLevel(level uint32) { |
|
atomic.StoreUint32(&logLevel, level) |
|
} |
|
func SetWriter(w Writer) { |
|
if atomic.LoadUint32(&disableLog) == 0 { |
|
writer.Store(w) |
|
} |
|
} |
|
func SetUp(c LogConf) (err error) { |
|
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 |
|
} |
|
|
|
func Severe(v ...any) { |
|
writeSevere(fmt.Sprint(v...)) |
|
} |
|
func Severef(format string, v ...any) { |
|
writeSevere(fmt.Sprintf(format, v...)) |
|
} |
|
|
|
func Slow(v ...any) { |
|
writeSlow(fmt.Sprint(v...)) |
|
} |
|
|
|
func Slowf(format string, v ...any) { |
|
writeSlow(fmt.Sprintf(format, v...)) |
|
} |
|
func Stat(v ...any) { |
|
writeStat(fmt.Sprint(v...)) |
|
} |
|
|
|
func Statf(format string, v ...any) { |
|
writeStat(fmt.Sprintf(format, v...)) |
|
} |
|
func WithCooldownMillis(mills int) LogOption { |
|
return func(opts *logOptions) { |
|
opts.logStackCooldownMills = mills |
|
} |
|
} |
|
func WithKeepDays(days int) LogOption { |
|
return func(opts *logOptions) { |
|
opts.keepDays = days |
|
} |
|
} |
|
func WithGzip() LogOption { |
|
return func(opts *logOptions) { |
|
opts.gzipEnabled = true |
|
} |
|
} |
|
func WithMaxBackups(count int) LogOption { |
|
return func(opts *logOptions) { |
|
opts.maxBackups = count |
|
} |
|
} |
|
func WithMaxSize(size int) LogOption { |
|
return func(opts *logOptions) { |
|
opts.maxSize = size |
|
} |
|
} |
|
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 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} |
|
} |
|
} |
|
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()...) |
|
} |
|
} |
|
func newFileWriter(c LogConf) (Writer, error) { |
|
var err error |
|
var opts []LogOption |
|
var infoLog io.WriteCloser |
|
var errorLog io.WriteCloser |
|
var severeLog io.WriteCloser |
|
var slowLog io.WriteCloser |
|
var statLog io.WriteCloser |
|
var stackLog io.Writer |
|
|
|
if len(c.Path) == 0 { |
|
return nil, ErrLogPathNotSet |
|
} |
|
|
|
opts = append(opts, WithCooldownMillis(c.StackCooldownMillis)) |
|
if c.Compress { |
|
opts = append(opts, WithGzip()) |
|
} |
|
if c.KeepDays > 0 { |
|
opts = append(opts, WithKeepDays(c.KeepDays)) |
|
} |
|
if c.MaxBackups > 0 { |
|
opts = append(opts, WithMaxBackups(c.MaxBackups)) |
|
} |
|
if c.MaxSize > 0 { |
|
opts = append(opts, WithMaxSize(c.MaxSize)) |
|
} |
|
|
|
opts = append(opts, WithRotation(c.Rotation)) |
|
|
|
accessFile := path.Join(c.Path, accessFilename) |
|
errorFile := path.Join(c.Path, errorFilename) |
|
severeFile := path.Join(c.Path, severeFilename) |
|
slowFile := path.Join(c.Path, slowFilename) |
|
statFile := path.Join(c.Path, statFilename) |
|
|
|
handleOptions(opts) |
|
setupLogLevel(c) |
|
|
|
if infoLog, err = createOutput(accessFile); err != nil { |
|
return nil, err |
|
} |
|
|
|
if errorLog, err = createOutput(errorFile); err != nil { |
|
return nil, err |
|
} |
|
|
|
if severeLog, err = createOutput(severeFile); err != nil { |
|
return nil, err |
|
} |
|
|
|
if slowLog, err = createOutput(slowFile); err != nil { |
|
return nil, err |
|
} |
|
|
|
if statLog, err = createOutput(statFile); err != nil { |
|
return nil, err |
|
} |
|
|
|
stackLog = newLessWriter(errorLog, options.logStackCooldownMills) |
|
|
|
return &concreteWriter{ |
|
infoLog: infoLog, |
|
errorLog: errorLog, |
|
severeLog: severeLog, |
|
slowLog: slowLog, |
|
statLog: statLog, |
|
stackLog: stackLog, |
|
}, nil |
|
} |
|
func handleOptions(opts []LogOption) { |
|
for _, opt := range opts { |
|
opt(&options) |
|
} |
|
}
|
|
|