APEX 的改进点
- Simplified handlers, no formatter/hook distinction
- log.Interface shares the Logger and Entry method set
- Built-in handlers (text, json, cli, discard, logfmt, memory, kinesis, multi)
- Tracing support
问题
什么是 Handler/Formmater 和 Hook
- A handler is the mechanism which processes log entries, without one apex/log isn’t very useful.
[root@liqiang.io]# cat handler.go
type Handler interface {
HandleLog(*Entry) error
}
Traceing support 是怎么做的,做到什么程度,有必要?
看 demo 是只做了一个执行时间的计算,没看到更多的演示和介绍,等下看代码试试。
log.Entry 的编码和解码是怎么做到的
Apex log 中吐槽了 Logrus 的日志 json 解析很 tricky,那么他又是怎么做到不 tricky 的呢,吐槽点:
This might not sound like a big deal, but if you want to serialize, ship, and deserialize JSON logs with the same schema, then this sort of normalization is not very Go-friendly. To produce the same *logrus.Entry on the consumer-side you’d have to unmarshal to a map, assign the three primary fields, loop the rest and assign those back to the entry. It’s not a huge problem, but it’s not ideal.
它吐槽的是这样的格式:
[root@liqiang.io]# cat logrus.json
{
"time": ...,
"msg": ...,
"level": ...,
<field>: ...
}
槽点在于 field 是动态的字段,前面有 3 个固定的字段,然后动态的 field 字段需要构造一个 map 来解析;那么 apex/log 的解决方式虽然我直觉上觉得也没有很完美,但是,似乎也找不到什么反驳点来吐槽,至少,我能看到的一个优点是,在解析的时候,你确实不需要比较 map 里面的 key 是不是 3 个固定字段中的一个,而是一股脑地直接利用它的 key 和 value 就可以了:
[root@liqiang.io]# cat apexlog.go
{
"fields":{
"file":"something.png",
"type":"image/png",
"user":"tobi"
},
"level":"info",
"timestamp":"2021-07-26T11:59:04.639931+08:00",
"message":"upload"
}
代码
Logger / Handler / Entry 的关系
[root@liqiang.io]# cat logger.go
type Logger struct {
Handler Handler
Level Level
}
type Handler interface {
HandleLog(*Entry) error
}
// WithFields returns a new entry with `fields` set.
func (l *Logger) WithFields(fields Fielder) *Entry {
return NewEntry(l).WithFields(fields.Fields())
}
// Info level message.
func (l *Logger) Info(msg string) {
NewEntry(l).Info(msg)
}
// log the message, invoking the handler. We clone the entry here
// to bypass the overhead in Entry methods when the level is not
// met.
func (l *Logger) log(level Level, e *Entry, msg string) {
if level < l.Level {
return
}
if err := l.Handler.HandleLog(e.finalize(level, msg)); err != nil {
stdlog.Printf("error logging: %s", err)
}
}
// Entry represents a single log entry.
type Entry struct {
Logger *Logger `json:"-"`
Fields Fields `json:"fields"`
Level Level `json:"level"`
Timestamp time.Time `json:"timestamp"`
Message string `json:"message"`
start time.Time
fields []Fields
}
// Info level message.
func (e *Entry) Info(msg string) {
e.Logger.log(InfoLevel, e, msg)
}
Multi Handler 实现
[root@liqiang.io]# cat handlers/multi/multi.go
// New handler.
func New(h ...log.Handler) *Handler {
return &Handler{
Handlers: h,
}
}
// HandleLog implements log.Handler.
func (h *Handler) HandleLog(e *log.Entry) error {
for _, handler := range h.Handlers {
// TODO(tj): maybe just write to stderr here, definitely not ideal
// to miss out logging to a more critical handler if something
// goes wrong
if err := handler.HandleLog(e); err != nil {
return err
}
}
return nil
}