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