You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
crate/log/logsdk/jsonlog/processor.go

167 lines
3.9 KiB
Go

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