英文:
Uber Zap Logger: how to prepend every log entry with a string
问题
我正在使用我的应用作为 SystemD 服务,并且需要在每条消息前面添加一个 JournalD 的条目级别 <LEVEL>
,例如:
<6> 这是信息
<7> 这是调试
<4> 这是警告
否则,JournalD 将把所有条目视为相同级别,而我想要利用其高级功能仅显示特定级别的日志。
如何使用 uber-zap 库在每个日志条目前面添加正确的级别标签(例如对于 Info 级别,它将是 <6>
)?
编辑:这是我的日志记录器配置的相关部分:
var config zap.Config
if production {
config = zap.NewProductionConfig()
config.Encoding = `console`
config.EncoderConfig.TimeKey = "" // 不需要时间戳,因为 SystemD 会添加时间戳
} else {
config = zap.NewDevelopmentConfig()
}
config.DisableStacktrace = true
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // 使用颜色
config.OutputPaths = []string{"stdout"}
英文:
I am using my app as a SystemD service and need to prepend every message with an entry level <LEVEL>
for JournalD like:
<6> this is info
<7> this is debug
<4> this is warning
Otherwise, JournalD treats all the entries the same level and I want to use its advanced capabilities for displaying logs only of certain level.
How can I prepend every log entry with the correct level label (like for Info it would be <6>
) with uber-zap library?
EDIT: This is the relevant part of my logger configuration:
var config zap.Config
if production {
config = zap.NewProductionConfig()
config.Encoding = `console`
config.EncoderConfig.TimeKey = "" // no time as SystemD adds timestamp
} else {
config = zap.NewDevelopmentConfig()
}
config.DisableStacktrace = true
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder // colors
config.OutputPaths = []string{"stdout"}
答案1
得分: 2
你可以使用一个自定义编码器来嵌入zapcore.Encoder
。
嵌入编码器可以让你使用相同的配置实现所有方法,然后你可以只实现EncodeEntry
方法并添加你需要的额外逻辑。
注意: 如果你计划使用结构化日志记录(例如logger.With()
),你仍然需要实现Clone()
方法。更多信息请参考:https://stackoverflow.com/questions/70502026
回到你的主要问题,这是一个可工作的示例;请查看代码中的注释以获取额外的解释:
type prependEncoder struct {
// 嵌入一个zapcore编码器
// 这样prependEncoder就可以在不额外工作的情况下实现接口
zapcore.Encoder
// zap缓冲池
pool buffer.Pool
}
// 只实现EncodeEntry方法
func (e *prependEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
// 新的日志缓冲区
buf := e.pool.Get()
// 根据日志级别添加JournalD前缀
buf.AppendString(e.toJournaldPrefix(entry.Level))
buf.AppendString(" ")
// 调用嵌入的编码器的EncodeEntry方法以保持原始的编码格式
consolebuf, err := e.Encoder.EncodeEntry(entry, fields)
if err != nil {
return nil, err
}
// 将输出写入自己的缓冲区
_, err = buf.Write(consolebuf.Bytes())
if err != nil {
return nil, err
}
return buf, nil
}
// 一些映射函数
func (e *prependEncoder) toJournaldPrefix(lvl zapcore.Level) string {
switch lvl {
case zapcore.DebugLevel:
return "<7>"
case zapcore.InfoLevel:
return "<6>"
case zapcore.WarnLevel:
return "<4>"
}
return ""
}
然后,使用使用自定义编码器构建一个使用该自定义编码器的自定义核心的记录器。你可以使用与当前选项相似的选项来初始化嵌入字段。
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
"os"
)
func getConfig() zap.Config {
// 你当前的配置选项
return config
}
func main() {
cfg := getConfig()
// 使用ConsoleEncoder和原始配置构建prependEncoder
enc := &prependEncoder{
Encoder: zapcore.NewConsoleEncoder(cfg.EncoderConfig),
pool: buffer.NewPool(),
}
logger := zap.New(
zapcore.NewCore(
enc,
os.Stdout,
zapcore.DebugLevel,
),
// 这里模拟NewProductionConfig.Build的行为
zap.ErrorOutput(os.Stderr),
)
logger.Info("this is info")
logger.Debug("this is debug")
logger.Warn("this is warn")
}
测试运行输出(INFO以蓝色打印,DEBUG以粉色打印,WARN以黄色打印,与你的zapcore.CapitalColorLevelEncoder
相符):
<6> INFO this is info
<7> DEBUG this is debug
<4> WARN this is warn
英文:
You can use a custom encoder that embeds a zapcore.Encoder
.
Embedding the encoder gives you the implementation of all methods "for free" with the same configuration you have now. Then you can implement only EncodeEntry
with the additional logic you require.
NOTE: You still have to implement Clone()
if you plan to use structured logging, e.g. logger.With()
. More info: https://stackoverflow.com/questions/70502026
Back to your main question, this is a working example; see the comments in code for additional explanation:
type prependEncoder struct {
// embed a zapcore encoder
// this makes prependEncoder implement the interface without extra work
zapcore.Encoder
// zap buffer pool
pool buffer.Pool
}
// implementing only EncodeEntry
func (e *prependEncoder) EncodeEntry(entry zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
// new log buffer
buf := e.pool.Get()
// prepend the JournalD prefix based on the entry level
buf.AppendString(e.toJournaldPrefix(entry.Level))
buf.AppendString(" ")
// calling the embedded encoder's EncodeEntry to keep the original encoding format
consolebuf, err := e.Encoder.EncodeEntry(entry, fields)
if err != nil {
return nil, err
}
// just write the output into your own buffer
_, err = buf.Write(consolebuf.Bytes())
if err != nil {
return nil, err
}
return buf, nil
}
// some mapper function
func (e *prependEncoder) toJournaldPrefix(lvl zapcore.Level) string {
switch lvl {
case zapcore.DebugLevel:
return "<7>"
case zapcore.InfoLevel:
return "<6>"
case zapcore.WarnLevel:
return "<4>"
}
return ""
}
Later you construct a logger with a custom core that uses the custom encoder. You initialize the embedded field with the same encoder you are using now. The options you see below mimic the options you currently have.
package main
import (
"go.uber.org/zap"
"go.uber.org/zap/buffer"
"go.uber.org/zap/zapcore"
"os"
)
func getConfig() zap.Config {
// your current config options
return config
}
func main() {
cfg := getConfig()
// constructing our prependEncoder with a ConsoleEncoder using your original configs
enc := &prependEncoder{
Encoder: zapcore.NewConsoleEncoder(cfg.EncoderConfig),
pool: buffer.NewPool(),
}
logger := zap.New(
zapcore.NewCore(
enc,
os.Stdout,
zapcore.DebugLevel,
),
// this mimics the behavior of NewProductionConfig.Build
zap.ErrorOutput(os.Stderr),
)
logger.Info("this is info")
logger.Debug("this is debug")
logger.Warn("this is warn")
}
Test run output (INFO is printed in blue, DEBUG in pink and WARN in yellow as per your zapcore.CapitalColorLevelEncoder
):
<6> INFO this is info
<7> DEBUG this is debug
<4> WARN this is warn
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论