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

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)
}
}