与原生log对比
基于反射的序列化和字符串格式昂贵得令人望而却步——它们是CPU密集型的,并进行许多小的分配。换句话说,使用encoding/json和fmt.Fprintf来记录大量的interface{}s会使您的应用程序变慢。
Zap采取了不同的方法。它包括一个无反射的零分配JSON编码器,基本Logger努力尽可能避免序列化开销和分配。通过在此基础上构建高级SugaredLogger,zap允许用户选择何时需要计算每个分配,以及何时他们更喜欢更熟悉、类型松散的API。
正如其自身的基准套件所衡量的那样,zap不仅比可比的结构化日志包性能更高,而且比标准库更快。像所有基准一样,对它们持保留态度。
用法
在性能良好但不关键的情况下,请使用SugaredLogger。它比其他结构化日志包快4-10倍,包括结构化和printf风格的API。
logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
// Structured context as loosely typed key-value pairs.
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)
当性能和类型安全至关重要时,请使用Logger。它甚至比SugaredLogger更快,分配也少得多,但它只支持结构化日志记录。
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
// Structured context as strongly typed Field values.
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)
zap配置
config
type Config struct {
// 级别是启用的最低日志记录级别。注意,这是一个动态级别,因此调用Config.level.SetLevel将自动更改日志,所有记录器的级别都从该配置下降。
Level AtomicLevel `json:"level" yaml:"level"`
// 开发使记录器处于开发模式,DPanicLevel的行为,并更自由地进行堆叠。
Development bool `json:"development" yaml:"development"`
//停止使用调用函数的文件注释日志,名称和行号。默认情况下,所有日志都有注释。
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
// DisableStacktrace完全禁用自动堆栈跟踪捕获。通过,默认情况下,为WarnLevel及更高级别的日志捕获堆栈,开发和ErrorLevel及以上。
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
// 采样设置采样策略。nil SamplingConfig禁用采样。
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
// 编码设置记录器的编码。有效值为“json”和,“控制台”,以及通过注册的任何第三方编码,寄存器编码器。
Encoding string `json:"encoding" yaml:"encoding"`
// EncoderConfig为所选编码器设置选项。
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
// OutputPaths是要将日志输出写入的URL或文件路径的列表。
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
// ErrorOutputPaths是将内部记录器错误写入的URL列表。默认值为标准错误。请注意,此设置仅影响内部错误;
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
// InitialFields是要添加到根记录器的字段集合。
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}
func NewProductionConfig() Config {
return Config{
Level: NewAtomicLevelAt(InfoLevel), // 日志级别
Development: false, // 开发模式,堆栈跟踪
Sampling: &SamplingConfig{
Initial: 100,
Thereafter: 100,
},
Encoding: "json", // 输出格式 console 或 json
EncoderConfig: NewProductionEncoderConfig(), // 编码器配置
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
}
// 编码器配置
func NewProductionEncoderConfig() zapcore.EncoderConfig {
return zapcore.EncoderConfig{
TimeKey: "ts",
LevelKey: "level",
NameKey: "logger",
CallerKey: "caller",
FunctionKey: zapcore.OmitKey,
MessageKey: "msg",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder, // 小写编码器
EncodeTime: zapcore.EpochTimeEncoder, // ISO8601 UTC 时间格式
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder, // 全路径编码器
}
}
zap日志输出,确定类型的输出确实很快,那么不确定类型会怎么处理呢?自定义类型呢?
源码的任意类型方案, switch case:
func Any(key string, value interface{}) Field {
switch val := value.(type) {
case zapcore.ObjectMarshaler:
return Object(key, val)
case zapcore.ArrayMarshaler:
return Array(key, val)
case bool:
return Bool(key, val)
case *bool:
return Boolp(key, val)
case []bool:
return Bools(key, val)
case complex128:
return Complex128(key, val)
case *complex128:
return Complex128p(key, val)
case []complex128:
return Complex128s(key, val)
case complex64:
return Complex64(key, val)
case *complex64:
return Complex64p(key, val)
case []complex64:
return Complex64s(key, val)
case float64:
return Float64(key, val)
case *float64:
return Float64p(key, val)
case []float64:
return Float64s(key, val)
case float32:
return Float32(key, val)
case *float32:
return Float32p(key, val)
case []float32:
return Float32s(key, val)
case int:
return Int(key, val)
case *int:
return Intp(key, val)
case []int:
return Ints(key, val)
case int64:
return Int64(key, val)
case *int64:
return Int64p(key, val)
case []int64:
return Int64s(key, val)
case int32:
return Int32(key, val)
case *int32:
return Int32p(key, val)
case []int32:
return Int32s(key, val)
case int16:
return Int16(key, val)
case *int16:
return Int16p(key, val)
case []int16:
return Int16s(key, val)
case int8:
return Int8(key, val)
case *int8:
return Int8p(key, val)
case []int8:
return Int8s(key, val)
case string:
return String(key, val)
case *string:
return Stringp(key, val)
case []string:
return Strings(key, val)
case uint:
return Uint(key, val)
case *uint:
return Uintp(key, val)
case []uint:
return Uints(key, val)
case uint64:
return Uint64(key, val)
case *uint64:
return Uint64p(key, val)
case []uint64:
return Uint64s(key, val)
case uint32:
return Uint32(key, val)
case *uint32:
return Uint32p(key, val)
case []uint32:
return Uint32s(key, val)
case uint16:
return Uint16(key, val)
case *uint16:
return Uint16p(key, val)
case []uint16:
return Uint16s(key, val)
case uint8:
return Uint8(key, val)
case *uint8:
return Uint8p(key, val)
case []byte:
return Binary(key, val)
case uintptr:
return Uintptr(key, val)
case *uintptr:
return Uintptrp(key, val)
case []uintptr:
return Uintptrs(key, val)
case time.Time:
return Time(key, val)
case *time.Time:
return Timep(key, val)
case []time.Time:
return Times(key, val)
case time.Duration:
return Duration(key, val)
case *time.Duration:
return Durationp(key, val)
case []time.Duration:
return Durations(key, val)
case error:
return NamedError(key, val)
case []error:
return Errors(key, val)
case fmt.Stringer:
return Stringer(key, val)
default:
return Reflect(key, val)
}
}
在zapcore/field.go中有个接口:type ObjectEncoder interface {}
type ObjectEncoder interface {
// Logging-specific marshalers.
AddArray(key string, marshaler ArrayMarshaler) error
AddObject(key string, marshaler ObjectMarshaler) error
***
}
实现提供的接口,加载自定义结构
ObjectMarshaller, ArrayMarshaler
type ObjectMarshaler interface {
MarshalLogObject(ObjectEncoder) error
}
type ArrayMarshaler interface {
MarshalLogArray(ArrayEncoder) error
}
demo
type User struct {
Name string
Email string
CreatedAt time.Time
}
func (u *User) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddString("name", u.Name)
enc.AddString("email", u.Email)
enc.AddInt64("created_at", u.CreatedAt.UnixNano())
return nil
}
type Users []*User
func (uu Users) MarshalLogArray(arr zapcore.ArrayEncoder) error {
var err error
for i := range uu {
err = multierr.Append(err, arr.AppendObject(uu[i]))
}
return err
}
func marshalerDemo() {
logger, err := zap.NewProduction()
defer logger.Sync()
if err != nil {
panic(err)
}
var user = &User{
Name: "hello1",
Email: "[email protected]",
CreatedAt: time.Date(2020, 12, 19, 8, 0, 0, 0, time.UTC),
}
var users Users
users = append(users, &User{
Name: "hello2",
Email: "[email protected]",
CreatedAt: time.Date(2020, 12, 19, 9, 0, 0, 0, time.UTC),
}, &User{
Name: "hello3",
Email: "[email protected]",
CreatedAt: time.Date(2020, 12, 20, 10, 0, 0, 0, time.UTC),
})
logger.Info("marshaler", zap.Object("user", user))
logger.Info("marshaler", zap.Array("users", users))
}
异步日志
相关实现博客
目前zap原生不提供异步输出日志
源码同步写:
type WriteSyncer interface {
io.Writer
Sync() error
}
type lockedWriteSyncer struct {
sync.Mutex
ws WriteSyncer
}
type multiWriteSyncer []WriteSyncer
func (ws multiWriteSyncer) Write(p []byte) (int, error) {
var writeErr error
nWritten := 0
for _, w := range ws {
n, err := w.Write(p)
writeErr = multierr.Append(writeErr, err)
if nWritten == 0 && n != 0 {
nWritten = n
} else if n < nWritten {
nWritten = n
}
}
return nWritten, writeErr
}
添加计数型日志
package test
import(
"time"
"testing"
"code/log"
"code/timer"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func TestMain(m *testing.M){
log.Init("maomi")
f := &Frame{}
t1 := timer.NewTicker(2 * time.Second, func() {
log.Info("record", zap.Object("frame", f))
})
for i := 0; i< 100; i++ {
f.Frame_0 = f.Frame_0 + 1
f.Frame_3 = f.Frame_3 + 1
time.Sleep(time.Second/2)
}
time.Sleep(10 * time.Second)
t1.Stop()
}
type Frame struct{
Frame_0 int32
Frame_1 int32
Frame_2 int32
Frame_3 int32
}
func (f *Frame) MarshalLogObject(enc zapcore.ObjectEncoder) error {
enc.AddInt32("frame_0", f.Frame_0)
enc.AddInt32("frame_1", f.Frame_1)
enc.AddInt32("frame_2", f.Frame_2)
enc.AddInt32("frame_3", f.Frame_3)
return nil
}
output:
{"level":"info","time":"2023-02-13T22:49:08.743+0800","msg":"record","servername":"maomi","caller":"/go/src/gin/test/log_test.go:50","frame":{"frame_0":4,"frame_1":0,"frame_2":0,"frame_3":4}}
{"level":"info","time":"2023-02-13T22:49:10.743+0800","msg":"record","servername":"maomi","caller":"/go/src/gin/test/log_test.go:50","frame":{"frame_0":8,"frame_1":0,"frame_2":0,"frame_3":8}}