diff --git a/log/go.mod b/log/go.mod new file mode 100644 index 0000000..eb0a315 --- /dev/null +++ b/log/go.mod @@ -0,0 +1,8 @@ +module git.blauwelle.com/go/crate/log + +go 1.20 + +require ( + git.blauwelle.com/go/crate/runtimehelper v0.1.0 + git.blauwelle.com/go/crate/synchelper v0.1.0 +) diff --git a/log/go.sum b/log/go.sum new file mode 100644 index 0000000..bb0b349 --- /dev/null +++ b/log/go.sum @@ -0,0 +1,4 @@ +git.blauwelle.com/go/crate/runtimehelper v0.1.0 h1:qNhtnt9YmHXNHKsGRbwD3AZ3pezpOwrbmX1o9Bz532I= +git.blauwelle.com/go/crate/runtimehelper v0.1.0/go.mod h1:yVMA0GkO9AS7iuPmalHKeWyv9en0JWj25rY1vpTuHhk= +git.blauwelle.com/go/crate/synchelper v0.1.0 h1:4yEXpshkklaws/57P94xN5bA3NmyyKGcZqYmzd6QIK4= +git.blauwelle.com/go/crate/synchelper v0.1.0/go.mod h1:2JkfH+7sF0Q0wiIaDOqG42ZLO5JxpcMfSoyy7db4Y2g= diff --git a/log/log.go b/log/log.go new file mode 100644 index 0000000..4504345 --- /dev/null +++ b/log/log.go @@ -0,0 +1,150 @@ +// log 实现了全局的日志处理 +// +// 这个包里定义的函数都是对全局的 [logsdk.Logger] 对象的封装. +// 全局的 Logger 不能重新赋值, +// Logger 的配置方法和日志方法可以并发调用. + +package log + +import ( + "context" + "fmt" + "time" + + "git.blauwelle.com/go/crate/log/logsdk" +) + +type ( + Level = logsdk.Level + Field = logsdk.Field +) + +const ( + LevelPanic = logsdk.LevelPanic + LevelFatal = logsdk.LevelFatal + LevelError = logsdk.LevelError + LevelWarn = logsdk.LevelWarn + LevelInfo = logsdk.LevelInfo + LevelDebug = logsdk.LevelDebug + LevelTrace = logsdk.LevelTrace +) + +// AddCallerSkip 增加调用 [runtime.Callers] 时的 skip 参数, +// 当通过装饰器等方式封装 Entry 导致增加调用 Entry 方法的深度时使用 AddCallerSkip 调整 skip, +// 直接在需要日志的地方调用 Entry 的方法时不需要 AddCallerSkip. +func AddCallerSkip(n int) logsdk.Entry { + return globalLogger.AddCallerSkip(n) +} + +// WithField 增加1组键值对 +func WithField(key string, value any) logsdk.Entry { + return globalLogger.WithField(key, value) +} + +// WithFields 增加键值对 +func WithFields(fields ...logsdk.Field) logsdk.Entry { + return globalLogger.WithFields(fields...) +} + +// WithTime 设置日志的时间, +// Entry 默认使用调用 Log 等最终方法的时间作为日志的时间. +func WithTime(t time.Time) logsdk.Entry { + return globalLogger.WithTime(t) +} + +// WithReportCaller 设置 [logsdk.Entry] 是否收集调用记录 +func WithReportCaller(reportCaller bool) logsdk.Entry { + return globalLogger.WithReportCaller(reportCaller) +} + +// WithReportStack 设置 [logsdk.Entry] 是否收集调用栈 +func WithReportStack(reportStack bool) logsdk.Entry { + return globalLogger.WithReportStack(reportStack) +} + +// Log 输出日志 +func Log(ctx context.Context, level Level, args ...any) { + globalLogger.AddCallerSkip(1).Log(ctx, level, fmt.Sprint(args...)) +} + +// Trace 输出 LevelTrace 等级的日志 +func Trace(ctx context.Context, args ...any) { + globalLogger.AddCallerSkip(1).Trace(ctx, args...) +} + +// Debug 输出 LevelDebug 等级的日志 +func Debug(ctx context.Context, args ...any) { + globalLogger.AddCallerSkip(1).Debug(ctx, args...) +} + +// Info 输出 LevelInfo 等级的日志 +func Info(ctx context.Context, args ...any) { + globalLogger.AddCallerSkip(1).Info(ctx, args...) +} + +// Warn 输出 LevelWarn 等级的日志 +func Warn(ctx context.Context, args ...any) { + globalLogger.AddCallerSkip(1).Warn(ctx, args...) +} + +// Error 输出 LevelError 等级的日志 +func Error(ctx context.Context, args ...any) { + globalLogger.AddCallerSkip(1).Error(ctx, args...) +} + +// Fatal 输出 LevelFatal 等级的日志并调用 Logger.Exit +func Fatal(ctx context.Context, args ...any) { + globalLogger.AddCallerSkip(1).Fatal(ctx, args...) +} + +// Panic 输出 LevelPanic 等级的日志后执行 panic, +// 即使 Logger 日志等级高于 LevelPanic 也会 panic. +func Panic(ctx context.Context, args ...any) { + globalLogger.AddCallerSkip(1).Panic(ctx, args...) +} + +// Logf 格式化输出日志 +func Logf(ctx context.Context, level Level, format string, args ...any) { + globalLogger.AddCallerSkip(1).Logf(ctx, level, format, args...) +} + +// Tracef 格式化输出 LevelTrace 等级的日志 +func Tracef(ctx context.Context, format string, args ...any) { + globalLogger.AddCallerSkip(1).Tracef(ctx, format, args...) +} + +// Debugf 格式化输出 LevelDebug 等级的日志 +func Debugf(ctx context.Context, format string, args ...any) { + globalLogger.AddCallerSkip(1).Debugf(ctx, format, args...) +} + +// Infof 格式化输出 LevelInfo 等级的日志 +func Infof(ctx context.Context, format string, args ...any) { + globalLogger.AddCallerSkip(1).Infof(ctx, format, args...) +} + +// Warnf 格式化输出 LevelWarn 等级的日志 +func Warnf(ctx context.Context, format string, args ...any) { + globalLogger.AddCallerSkip(1).Warnf(ctx, format, args...) +} + +// Errorf 格式化输出 LevelError 等级的日志 +func Errorf(ctx context.Context, format string, args ...any) { + globalLogger.AddCallerSkip(1).Errorf(ctx, format, args...) +} + +// Fatalf 格式化输出 LevelFatal 等级的日志并调用 Logger.Exit +func Fatalf(ctx context.Context, format string, args ...any) { + globalLogger.AddCallerSkip(1).Fatalf(ctx, format, args...) +} + +// Panicf 格式化输出 LevelPanic 等级的日志 +func Panicf(ctx context.Context, format string, args ...any) { + globalLogger.AddCallerSkip(1).Panicf(ctx, format, args...) +} + +// Logger 返回全局 logger, 通常用来在程序启动时对全局 logger 进行配置, +// 业务代码处理日志时直接使用这个包里定义的日志函数. +func Logger() *logsdk.Logger { + return globalLogger +} diff --git a/log/logsdk/entry.go b/log/logsdk/entry.go new file mode 100644 index 0000000..c4c3caf --- /dev/null +++ b/log/logsdk/entry.go @@ -0,0 +1,220 @@ +package logsdk + +import ( + "context" + "fmt" + "time" + + "git.blauwelle.com/go/crate/runtimehelper" +) + +// Entry 包含日志所需的全部中间信息并负责输出日志 +type Entry struct { + logger *Logger + time time.Time + fields []Field + callerSkip int + reportCaller bool + isReportCallerSet bool + reportStack bool + isReportStackSet bool + initialized bool +} + +func (entry Entry) copy() Entry { + if entry.initialized { + return entry + } + return entry.logger.newEntry() +} + +// AddCallerSkip 增加调用 [runtime.Callers] 时的 skip 参数, +// 当通过装饰器等方式封装 Entry 导致增加调用 Entry 方法的深度时使用 AddCallerSkip 调整 skip, +// 直接在需要日志的地方调用 Entry 的方法时不需要 AddCallerSkip. +func (entry Entry) AddCallerSkip(n int) Entry { + newEntry := entry.copy() + newEntry.callerSkip += n + return newEntry +} + +// WithField 增加1组键值对 +func (entry Entry) WithField(key string, value any) Entry { + return entry.WithFields(Field{Key: key, Value: value}) +} + +// WithFields 增加键值对 +func (entry Entry) WithFields(fields ...Field) Entry { + newEntry := entry.copy() + newEntry.fields = make([]Field, len(entry.fields)+len(fields)) + copy(newEntry.fields, entry.fields) + copy(newEntry.fields, fields) + return newEntry +} + +// WithTime 设置日志的时间, +// Entry 默认使用调用 Log 等最终方法的时间作为日志的时间. +func (entry Entry) WithTime(t time.Time) Entry { + newEntry := entry.copy() + newEntry.time = t + return newEntry +} + +// GetReportCaller 获取是否收集调用记录, +// 默认使用 Logger 上的对应配置. +func (entry Entry) GetReportCaller() bool { + if entry.isReportCallerSet { + return entry.reportCaller + } + return entry.logger.GetReportCaller() +} + +// WithReportCaller 设置是否收集调用记录 +func (entry Entry) WithReportCaller(reportCaller bool) Entry { + newEntry := entry.copy() + newEntry.reportCaller = reportCaller + newEntry.isReportCallerSet = true + return newEntry +} + +// GetReportStack 获取是否收集调用栈, +// 默认使用 Logger 上的对应配置. +func (entry Entry) GetReportStack() bool { + if entry.isReportStackSet { + return entry.reportStack + } + return entry.logger.GetReportStack() +} + +// WithReportStack 设置是否收集调用栈 +func (entry Entry) WithReportStack(reportStack bool) Entry { + newEntry := entry.copy() + newEntry.reportStack = reportStack + newEntry.isReportStackSet = true + return newEntry +} + +// Log 输出日志 +func (entry Entry) Log(ctx context.Context, level Level, args ...any) { + entry.log(ctx, level, fmt.Sprint(args...)) +} + +// Trace 输出 LevelTrace 等级的日志 +func (entry Entry) Trace(ctx context.Context, args ...any) { + entry.log(ctx, LevelTrace, fmt.Sprint(args...)) +} + +// Debug 输出 LevelDebug 等级的日志 +func (entry Entry) Debug(ctx context.Context, args ...any) { + entry.log(ctx, LevelDebug, fmt.Sprint(args...)) +} + +// Info 输出 LevelInfo 等级的日志 +func (entry Entry) Info(ctx context.Context, args ...any) { + entry.log(ctx, LevelInfo, fmt.Sprint(args...)) +} + +// Warn 输出 LevelWarn 等级的日志 +func (entry Entry) Warn(ctx context.Context, args ...any) { + entry.log(ctx, LevelWarn, fmt.Sprint(args...)) +} + +// Error 输出 LevelError 等级的日志 +func (entry Entry) Error(ctx context.Context, args ...any) { + entry.log(ctx, LevelError, fmt.Sprint(args...)) +} + +// Fatal 输出 LevelFatal 等级的日志并调用 Logger.Exit +func (entry Entry) Fatal(ctx context.Context, args ...any) { + entry.log(ctx, LevelFatal, fmt.Sprint(args...)) + entry.logger.Exit(1) +} + +// Panic 输出 LevelPanic 等级的日志后执行 panic, +// 即使 Logger 日志等级高于 LevelPanic 也会 panic. +func (entry Entry) Panic(ctx context.Context, args ...any) { + entry.log(ctx, LevelPanic, fmt.Sprint(args...)) +} + +// Logf 格式化输出日志 +func (entry Entry) Logf(ctx context.Context, level Level, format string, args ...any) { + entry.log(ctx, level, fmt.Sprintf(format, args...)) +} + +// Tracef 格式化输出 LevelTrace 等级的日志 +func (entry Entry) Tracef(ctx context.Context, format string, args ...any) { + entry.log(ctx, LevelTrace, fmt.Sprintf(format, args...)) +} + +// Debugf 格式化输出 LevelDebug 等级的日志 +func (entry Entry) Debugf(ctx context.Context, format string, args ...any) { + entry.log(ctx, LevelDebug, fmt.Sprintf(format, args...)) +} + +// Infof 格式化输出 LevelInfo 等级的日志 +func (entry Entry) Infof(ctx context.Context, format string, args ...any) { + entry.log(ctx, LevelInfo, fmt.Sprintf(format, args...)) +} + +// Warnf 格式化输出 LevelWarn 等级的日志 +func (entry Entry) Warnf(ctx context.Context, format string, args ...any) { + entry.log(ctx, LevelWarn, fmt.Sprintf(format, args...)) +} + +// Errorf 格式化输出 LevelError 等级的日志 +func (entry Entry) Errorf(ctx context.Context, format string, args ...any) { + entry.log(ctx, LevelError, fmt.Sprintf(format, args...)) +} + +// Fatalf 格式化输出 LevelFatal 等级的日志并调用 Logger.Exit +func (entry Entry) Fatalf(ctx context.Context, format string, args ...any) { + entry.log(ctx, LevelFatal, fmt.Sprintf(format, args...)) + entry.logger.Exit(1) +} + +// Panicf 格式化输出 LevelPanic 等级的日志 +// 即使 Logger 日志等级高于 LevelPanic 也会 panic. +func (entry Entry) Panicf(ctx context.Context, format string, args ...any) { + entry.log(ctx, LevelPanic, fmt.Sprintf(format, args...)) +} + +func (entry Entry) log(ctx context.Context, level Level, message string) { + newEntry := entry.copy() + defer func() { + if level == LevelPanic { + panic(message) + } + }() + if newEntry.logger.GetLevel() < level { + return + } + readonlyEntry := ReadonlyEntry{ + Context: ctx, + Fields: newEntry.fields, + Message: message, + Time: newEntry.time, + Level: level, + } + if readonlyEntry.Time.IsZero() { + readonlyEntry.Time = time.Now() + } + if newEntry.GetReportCaller() { + readonlyEntry.Caller = runtimehelper.Caller(newEntry.callerSkip) + } + if newEntry.GetReportStack() { + readonlyEntry.Stack = runtimehelper.Stack(newEntry.callerSkip, 32) + } + for _, processor := range newEntry.logger.getLevelProcessors(level) { + processor.Process(readonlyEntry) + } +} + +// ReadonlyEntry 是日志系统收集到1条日志记录 +type ReadonlyEntry struct { + Context context.Context `json:"-"` + Caller runtimehelper.Frame + Stack []runtimehelper.Frame + Time time.Time + Message string + Fields []Field + Level Level +} diff --git a/log/logsdk/field.go b/log/logsdk/field.go new file mode 100644 index 0000000..fe379ff --- /dev/null +++ b/log/logsdk/field.go @@ -0,0 +1,7 @@ +package logsdk + +// Field 是日志记录中的键值对 +type Field struct { + Value any `json:"v"` + Key string `json:"k"` +} diff --git a/log/logsdk/jsonlog/processor.go b/log/logsdk/jsonlog/processor.go new file mode 100644 index 0000000..3da6e57 --- /dev/null +++ b/log/logsdk/jsonlog/processor.go @@ -0,0 +1,166 @@ +package jsonlog + +import ( + "encoding/json" + "fmt" + "io" + "os" + "time" + + "git.blauwelle.com/go/crate/log/logsdk" + "git.blauwelle.com/go/crate/runtimehelper" + "git.blauwelle.com/go/crate/synchelper" +) + +var _ logsdk.EntryProcessor = &Processor{} + +// New 返回日志处理对象, +// 返回的对象是 [logsdk.EntryProcessor]. +func New(opts ...Option) *Processor { + cfg := defaultConfig() + for _, opt := range opts { + opt.apply(cfg) + } + return &Processor{ + bufferPool: cfg.pool, + output: cfg.output, + timeFormat: cfg.timestampFormat, + disableTime: cfg.disableTime, + disableHTMLEscape: cfg.disableHTMLEscape, + prettyPrint: cfg.prettyPrint, + } +} + +// Processor 日志处理对象, 把日志处理成 JSON. +type Processor struct { + bufferPool synchelper.BytesBufferPool + output io.Writer + timeFormat string + disableTime bool + disableHTMLEscape bool + prettyPrint bool +} + +// Process 处理日志 +func (processor *Processor) Process(entry logsdk.ReadonlyEntry) { + m := Entry{ + Stack: entry.Stack, + Fields: entry.Fields, + Level: entry.Level, + Message: entry.Message, + } + if !processor.disableTime { + m.Time = entry.Time.Format(processor.timeFormat) // 1次分配 + } + if entry.Caller.IsValid() { + // 1次分配 + // 直接取 &entry.Caller 会增加堆内存分配 + m.Caller = &runtimehelper.Frame{ + Function: entry.Caller.Function, + File: entry.Caller.File, + Line: entry.Caller.Line, + } + } + + buf := processor.bufferPool.Get() + buf.Reset() + defer processor.bufferPool.Put(buf) + + encoder := json.NewEncoder(buf) + if processor.prettyPrint { + encoder.SetIndent("", " ") + } + encoder.SetEscapeHTML(!processor.disableHTMLEscape) + + // Encode 2次分配 + if err := encoder.Encode(m); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "JSON processor cannot encode log %#v: %s\n", m, err.Error()) + } + + if _, err := buf.WriteTo(processor.output); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "JSON processor cannot write log: %s\n", err.Error()) + } +} + +// Option 配置日志处理对象 +type Option interface { + apply(cfg *config) +} + +// WithBufferPool 配置缓冲池 +func WithBufferPool(pool synchelper.BytesBufferPool) Option { + return optionFunc(func(cfg *config) { + cfg.pool = pool + }) +} + +// WithOutput 配置输出 +func WithOutput(w io.Writer) Option { + return optionFunc(func(cfg *config) { + cfg.output = w + }) +} + +// WithTimeFormat 配置时间格式 +func WithTimeFormat(format string) Option { + return optionFunc(func(cfg *config) { + cfg.timestampFormat = format + }) +} + +// WithDisableTime 配置仅用时间输出 +func WithDisableTime(disable bool) Option { + return optionFunc(func(cfg *config) { + cfg.disableTime = disable + }) +} + +// WithDisableHTMLEscape 配置禁止 HTML 转义 +func WithDisableHTMLEscape(disable bool) Option { + return optionFunc(func(cfg *config) { + cfg.disableHTMLEscape = disable + }) +} + +// WithPrettyPrint 配置 JSON 多行缩进输出 +func WithPrettyPrint(pretty bool) Option { + return optionFunc(func(cfg *config) { + cfg.prettyPrint = pretty + }) +} + +// Entry 被用来 JSON 序列化 +type Entry struct { + Message string `json:"msg"` + Time string `json:"time,omitempty"` + Caller *runtimehelper.Frame `json:"caller,omitempty"` + Stack []runtimehelper.Frame `json:"stack,omitempty"` + Fields []logsdk.Field `json:"fields,omitempty"` + Level logsdk.Level `json:"level"` +} + +func defaultConfig() *config { + return &config{ + pool: synchelper.NewBytesBufferPool(512, 2048), + output: synchelper.NewSyncWriter(os.Stderr), + timestampFormat: time.RFC3339Nano, + disableTime: false, + disableHTMLEscape: false, + prettyPrint: false, + } +} + +type config struct { + pool synchelper.BytesBufferPool + output io.Writer + timestampFormat string + disableTime bool + disableHTMLEscape bool + prettyPrint bool +} + +type optionFunc func(cfg *config) + +func (fn optionFunc) apply(cfg *config) { + fn(cfg) +} diff --git a/log/logsdk/level.go b/log/logsdk/level.go new file mode 100644 index 0000000..514fb67 --- /dev/null +++ b/log/logsdk/level.go @@ -0,0 +1,74 @@ +package logsdk + +import "fmt" + +// Level 日志等级 +type Level int + +func (level Level) String() string { + if b, err := level.MarshalText(); err == nil { + return string(b) + } + return "unknown" +} + +func (level Level) MarshalText() ([]byte, error) { + switch level { + case LevelPanic: + return []byte(LevelPanicValue), nil + case LevelFatal: + return []byte(LevelFatalValue), nil + case LevelError: + return []byte(LevelErrorValue), nil + case LevelWarn: + return []byte(LevelWarnValue), nil + case LevelInfo: + return []byte(LevelInfoValue), nil + case LevelDebug: + return []byte(LevelDebugValue), nil + case LevelTrace: + return []byte(LevelTraceValue), nil + } + return nil, fmt.Errorf("not a valid log level %d", level) +} + +const ( + // LevelCount 日志等级的个数 + LevelCount = 7 + + // LevelOffset 是把 LevelInfo 等级的日志值偏移到零值的偏移量 + LevelOffset = 4 +) + +const ( + // LevelDisabled 表示不处理任何等级的日志, + // 其他日志等级的值越小表示日志严重程度越高. + LevelDisabled Level = iota - LevelOffset - 1 + LevelPanic + LevelFatal + LevelError + LevelWarn + LevelInfo + LevelDebug + LevelTrace +) + +const ( + LevelPanicValue = "panic" + LevelFatalValue = "fatal" + LevelErrorValue = "error" + LevelWarnValue = "warn" + LevelInfoValue = "info" + LevelDebugValue = "debug" + LevelTraceValue = "trace" +) + +var AllLevels = []Level{ + LevelPanic, + LevelFatal, + LevelError, + LevelWarn, + LevelInfo, + LevelDebug, + LevelTrace, +} diff --git a/log/logsdk/logger.go b/log/logsdk/logger.go new file mode 100644 index 0000000..9ed981a --- /dev/null +++ b/log/logsdk/logger.go @@ -0,0 +1,129 @@ +package logsdk + +import ( + "os" + "sync" + "sync/atomic" +) + +// New 返回初始未经过参数调整的 Logger +func New() *Logger { + logger := &Logger{} + logger.entry.logger = logger + return logger +} + +// entry 仅被用来嵌入 Logger +type entry = Entry + +// Logger 中保存了日志所需的全局配置, +// 使用 Logger 处理日志. +type Logger struct { + exit func(code int) // protected by lock + levelProcessors levelProcessors // protected by lock + entry + lock sync.RWMutex + level atomic.Int32 + callerSkip atomic.Int32 + reportCaller atomic.Bool + reportStack atomic.Bool +} + +// AddProcessor 把日志处理器增加到 Logger +func (logger *Logger) AddProcessor(levels []Level, processor EntryProcessor) { + logger.lock.Lock() + for _, level := range levels { + logger.levelProcessors[level+LevelOffset] = append(logger.levelProcessors[level+LevelOffset], processor) + } + logger.lock.Unlock() +} + +// GetLevel 返回日志系统的等级, 严重程度低于返回等级的日志不会被处理. +func (logger *Logger) GetLevel() Level { + return Level(logger.level.Load()) +} + +// SetLevel 设置日志系统的等级 +func (logger *Logger) SetLevel(level Level) { + logger.level.Store(int32(level)) +} + +// GetCallerSkip 获取调用 [runtime.Callers] 时的 skip 参数, +// skip 已经被偏移到从调用 Logger 相关方法处获取调用信息. +// 0 表示从调用 Logger 相关方法处获取调用信息. +func (logger *Logger) GetCallerSkip() int { + return int(logger.callerSkip.Load()) +} + +// SetCallerSkip 设置调用 [runtime.Callers] 时的 skip 参数, +// skip 已经被偏移到从调用 Logger 相关方法处获取调用信息. +// 0 表示从调用 Logger 相关方法处获取调用信息. +func (logger *Logger) SetCallerSkip(callerSkip int) { + logger.callerSkip.Store(int32(callerSkip)) +} + +// GetReportCaller 返回是否收集调用信息 +func (logger *Logger) GetReportCaller() bool { + return logger.reportCaller.Load() +} + +// SetReportCaller 设置是否收集调用信息 +func (logger *Logger) SetReportCaller(reportCaller bool) { + logger.reportCaller.Store(reportCaller) +} + +// GetReportStack 返回是否收集调用栈信息 +func (logger *Logger) GetReportStack() bool { + return logger.reportStack.Load() +} + +// SetReportStack 设置是否收集调用栈信息 +func (logger *Logger) SetReportStack(reportStack bool) { + logger.reportStack.Store(reportStack) +} + +// Reset 把 Logger 重置到初始状态 +func (logger *Logger) Reset() { + logger.lock.Lock() + logger.exit = nil + logger.levelProcessors = levelProcessors{} + logger.lock.Unlock() + + logger.SetLevel(LevelInfo) + logger.SetCallerSkip(0) + logger.SetReportCaller(false) + logger.SetReportStack(false) +} + +// Exit 退出程序, 执行的具体过程可以通过 SetExit 指定 +func (logger *Logger) Exit(code int) { + logger.lock.RLock() + defer logger.lock.RUnlock() + if logger.exit == nil { + os.Exit(code) + } + logger.exit(code) +} + +// SetExit 指定退出程序时执行的函数 +func (logger *Logger) SetExit(fn func(code int)) { + logger.lock.Lock() + logger.exit = fn + logger.lock.Unlock() +} + +func (logger *Logger) getLevelProcessors(level Level) []EntryProcessor { + logger.lock.RLock() + defer logger.lock.RUnlock() + return logger.levelProcessors[level+LevelOffset] +} + +func (logger *Logger) newEntry() Entry { + return Entry{ + logger: logger, + callerSkip: logger.GetCallerSkip() + 2, + reportCaller: logger.GetReportCaller(), + reportStack: logger.GetReportStack(), + initialized: true, + } +} diff --git a/log/logsdk/processor.go b/log/logsdk/processor.go new file mode 100644 index 0000000..4b85d85 --- /dev/null +++ b/log/logsdk/processor.go @@ -0,0 +1,8 @@ +package logsdk + +// EntryProcessor 处理日志记录 +type EntryProcessor interface { + Process(entry ReadonlyEntry) +} + +type levelProcessors [LevelCount][]EntryProcessor diff --git a/log/state.go b/log/state.go new file mode 100644 index 0000000..0f4cd0e --- /dev/null +++ b/log/state.go @@ -0,0 +1,8 @@ +package log + +import ( + "git.blauwelle.com/go/crate/log/logsdk" +) + +// globalLogger 不能重新赋值 +var globalLogger = logsdk.New()