develop
Ge Song 2 years ago
parent e484aaa1f9
commit 179e00910d

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

@ -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=

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

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

@ -0,0 +1,7 @@
package logsdk
// Field 是日志记录中的键值对
type Field struct {
Value any `json:"v"`
Key string `json:"k"`
}

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

@ -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,
}

@ -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,
}
}

@ -0,0 +1,8 @@
package logsdk
// EntryProcessor 处理日志记录
type EntryProcessor interface {
Process(entry ReadonlyEntry)
}
type levelProcessors [LevelCount][]EntryProcessor

@ -0,0 +1,8 @@
package log
import (
"git.blauwelle.com/go/crate/log/logsdk"
)
// globalLogger 不能重新赋值
var globalLogger = logsdk.New()
Loading…
Cancel
Save