Zap
Zap es un componente de registros rápido, estructurado y nivelado construido con Go.
Repositorio oficial: uber-go/zap: Blazing fast, structured, leveled logging in Go. (github.com)
Documentación oficial: zap package - go.uber.org/zap - Go Packages
Instalación
go get -u go.uber.org/zapInicio rápido
Oficial proporciona dos ejemplos de inicio rápido, ambos son registros de nivel de producción, el primero es un Sugar que soporta estilo printf pero con rendimiento relativamente bajo.
logger, _ := zap.NewProduction()
defer logger.Sync() // Sincronizar el búfer al archivo al final del programa
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)El segundo es de mejor rendimiento, pero solo soporta salida fuertemente tipificada logger
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("failed to fetch URL",
// Contexto estructurado como valores de campo fuertemente tipificados.
zap.String("url", url),
zap.Int("attempt", 3),
zap.Duration("backoff", time.Second),
)TIP
El uso de Zap es muy simple, el punto problemático es configurar un registro adecuado para tu proyecto, los ejemplos oficiales son pocos, hay que leer más los comentarios del código fuente.
Configuración
Generalmente la configuración de registros se escribe en archivos de configuración, la configuración de Zap también soporta deserialización a través de archivos de configuración, pero solo soporta configuración básica, incluso la configuración avanzada que los ejemplos oficiales proporcionan es muy simple, no es suficiente para poner en uso, así que hay que explicar los detalles de la configuración.
Primero veamos la estructura del cuerpo de configuración total, necesitamos entender el significado de cada campo
type Config struct {
// Nivel mínimo de registros
Level AtomicLevel `json:"level" yaml:"level"`
// Modo desarrollo, afecta principalmente el seguimiento de pila
Development bool `json:"development" yaml:"development"`
// Seguimiento de llamador
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
// Seguimiento de pila
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
// Muestreo, registrar solo algunos registros más representativos bajo la condición de limitar el uso de rendimiento de registros, igual a registro selectivo
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
// Codificación, dividida en modo json y console
Encoding string `json:"encoding" yaml:"encoding"`
// Configuración de codificación, principalmente configuración de formateo de salida
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
// Ruta de salida de archivo de registros
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
// Ruta de salida de archivo de errores
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
// Agregar algunos contenidos de salida predeterminados a los registros
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}A continuación están los detalles sobre la configuración de codificación
type EncoderConfig struct {
// Clave de valor, si la clave está vacía, la propiedad correspondiente no se escribirá
MessageKey string `json:"messageKey" yaml:"messageKey"`
LevelKey string `json:"levelKey" yaml:"levelKey"`
TimeKey string `json:"timeKey" yaml:"timeKey"`
NameKey string `json:"nameKey" yaml:"nameKey"`
CallerKey string `json:"callerKey" yaml:"callerKey"`
FunctionKey string `json:"functionKey" yaml:"functionKey"`
StacktraceKey string `json:"stacktraceKey" yaml:"stacktraceKey"`
SkipLineEnding bool `json:"skipLineEnding" yaml:"skipLineEnding"`
LineEnding string `json:"lineEnding" yaml:"lineEnding"`
// Algunos codificadores personalizados
EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"`
EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`
EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"`
// Codificador de nombre de registrador
EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
// Codificador de reflexión, principalmente para tipo interface{}, si no hay jsonencoder predeterminado
NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
// Cadena de separación de salida de consola
ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}Option es sobre interruptores y aplicaciones de algunas configuraciones, hay muchas implementaciones.
type Option interface {
apply(*Logger)
}
// Implementación de Option
type optionFunc func(*Logger)
func (f optionFunc) apply(log *Logger) {
f(log)
}
// Aplicación
func Development() Option {
return optionFunc(func(log *Logger) {
log.development = true
})
}Este es el núcleo de registro más utilizado, sus campos internos básicamente representan nuestros pasos de configuración, también se puede referir a los pasos de la configuración oficial al deserializar, son aproximadamente los mismos.
type ioCore struct {
// Nivel de registros
LevelEnabler
// Codificación de registros
enc Encoder
// Escritura de registros
out WriteSyncer
}zap.Encoder es responsable de la formateo y codificación de registros
zap.WriteSyncer es responsable de la salida de registros, principalmente salida a archivo y consola
zap.LevelEnabler nivel mínimo de registros, los registros por debajo de este nivel ya no se escriben a través de syncer.
Codificación de registros
La codificación de registros implica principalmente el formateo de algunos detalles de registros, primero veamos la salida del registro original directamente.
func TestQuickStart(t *testing.T) {
rawJSON := []byte(`{
"level": "debug",
"encoding": "json",
"outputPaths": ["stdout"],
"errorOutputPaths": ["stderr"],
"initialFields": {"foo": "bar"},
"encoderConfig": {
"messageKey": "message",
"levelKey": "level",
"levelEncoder": "lowercase"
}
}`)
var cfg zap.Config
if err := json.Unmarshal(rawJSON, &cfg); err != nil {
panic(err)
}
logger := zap.Must(cfg.Build())
defer logger.Sync()
logger.Info("logger construction succeeded")
}{"level":"info","message":"logger construction succeeded","foo":"bar"}Se encontrará que este registro tiene varios problemas:
- No tiene tiempo
- No tiene situación del llamador, no se sabe de dónde se escribió este registro, de lo contrario no se puede investigar cuando ocurre un error
- No tiene situación de pila
A continuación se resuelve el problema paso a paso, principalmente modificar zapcore.EncoderConfig, primero hay que escribir el archivo de configuración propio, no usar la deserialización directa oficial. Primero crear un archivo de configuración config.yml
# Configuración de registros Zap
zap:
prefix: ZapLogTest
timeFormat: 2006/01/02 - 15:04:05.00000
level: debug
caller: true
stackTrace: false
encode: console
# Dónde escribir los registros file | console | both
writer: both
logFile:
maxSize: 20
backups: 5
compress: true
output:
- "./log/output.log"Estructura mapeada
// ZapConfig
// @Date: 2023-01-09 16:37:05
// @Description: Estructura de configuración de registros zap
type ZapConfig struct {
Prefix string `yaml:"prefix" mapstructure:"prefix"`
TimeFormat string `yaml:"timeFormat" mapstructure:"timeFormat"`
Level string `yaml:"level" mapstructure:"level"`
Caller bool `yaml:"caller" mapstructure:"caller"`
StackTrace bool `yaml:"stackTrace" mapstructure:"stackTrace"`
Writer string `yaml:"writer" mapstructure:"writer"`
Encode string `yaml:"encode" mapstructure:"encode"`
LogFile *LogFileConfig `yaml:"logFile" mapstructure:"logFile"`
}
// LogFileConfig
// @Date: 2023-01-09 16:38:45
// @Description: Estructura de configuración de archivo de registros
type LogFileConfig struct {
MaxSize int `yaml:"maxSize" mapstructure:"maxSize"`
BackUps int `yaml:"backups" mapstructure:"backups"`
Compress bool `yaml:"compress" mapstructure:"compress"`
Output []string `yaml:"output" mapstructure:"output"`
Errput []string `yaml:"errput" mapstructure:"errput"`
}TIP
Leer configuración usando Viper, el código específico se omite.
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)TimerEncoder en realidad es una función, podemos usar otros codificadores de tiempo proporcionados oficialmente, o escribir los propios.
func CustomTimeFormatEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(global.Config.ZapConfig.Prefix + "\t" + t.Format(global.Config.ZapConfig.TimeFormat))
}La parte general es la siguiente
func zapEncoder(config *ZapConfig) zapcore.Encoder {
// Crear una configuración
encoderConfig := zapcore.EncoderConfig{
TimeKey: "Time",
LevelKey: "Level",
NameKey: "Logger",
CallerKey: "Caller",
MessageKey: "Message",
StacktraceKey: "StackTrace",
LineEnding: zapcore.DefaultLineEnding,
FunctionKey: zapcore.OmitKey,
}
// Formato de tiempo personalizado
encoderConfig.EncodeTime = CustomTimeFormatEncoder
// Nivel de registros en mayúsculas
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
// Intervalo de tiempo en segundos
encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
// Salida abreviada del llamador
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
// Serialización completa del nombre del registrador
encoderConfig.EncodeName = zapcore.FullNameEncoder
// Codificación final de registros json o console
switch config.Encode {
case "json":
{
return zapcore.NewJSONEncoder(encoderConfig)
}
case "console":
{
return zapcore.NewConsoleEncoder(encoderConfig)
}
}
// console predeterminado
return zapcore.NewConsoleEncoder(encoderConfig)
}Salida de registros
La salida de registros se divide en salida de consola y salida de archivo, podemos configurar dinámicamente según el archivo de configuración, y si se desea realizar corte de archivos de registros también hay que usar otra dependencia de terceros.
go get -u github.com/natefinch/lumberjackFinalmente el código es el siguiente
func zapWriteSyncer(cfg *ZapConfig) zapcore.WriteSyncer {
syncers := make([]zapcore.WriteSyncer, 0, 2)
// Si se habilita la salida de consola de registros, agregar escritor de consola
if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteConsole {
syncers = append(syncers, zapcore.AddSync(os.Stdout))
}
// Si se habilita el almacenamiento de archivos de registros, agregar escritores según la ruta de archivo
if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteFile {
// Agregar escritor de registros
for _, path := range cfg.LogFile.Output {
logger := &lumberjack.Logger{
Filename: path, // Ruta de archivo
MaxSize: cfg.LogFile.MaxSize, // Tamaño de archivo dividido
MaxBackups: cfg.LogFile.BackUps, // Número de copias de seguridad
Compress: cfg.LogFile.Compress, // Si comprimir
LocalTime: true, // Usar hora local
}
syncers = append(syncers, zapcore.Lock(zapcore.AddSync(logger)))
}
}
return zap.CombineWriteSyncers(syncers...)
}Nivel de registros
Oficial tiene elementos de enumeración sobre niveles de registros, se pueden usar directamente.
func zapLevelEnabler(cfg *ZapConfig) zapcore.LevelEnabler {
switch cfg.Level {
case config.DebugLevel:
return zap.DebugLevel
case config.InfoLevel:
return zap.InfoLevel
case config.ErrorLevel:
return zap.ErrorLevel
case config.PanicLevel:
return zap.PanicLevel
case config.FatalLevel:
return zap.FatalLevel
}
// Nivel Debug predeterminado
return zap.DebugLevel
}Construcción final
func InitZap(config *ZapConfig) *zap.Logger {
// Construir codificador
encoder := zapEncoder(config)
// Construir nivel de registros
levelEnabler := zapLevelEnabler(config)
// Finalmente obtener Core y Options
subCore, options := tee(config, encoder, levelEnabler)
// Crear Logger
return zap.New(subCore, options...)
}
// Combinar todo
func tee(cfg *ZapConfig, encoder zapcore.Encoder, levelEnabler zapcore.LevelEnabler) (core zapcore.Core, options []zap.Option) {
sink := zapWriteSyncer(cfg)
return zapcore.NewCore(encoder, sink, levelEnabler), buildOptions(cfg, levelEnabler)
}
// Construir Option
func buildOptions(cfg *ZapConfig, levelEnabler zapcore.LevelEnabler) (options []zap.Option) {
if cfg.Caller {
options = append(options, zap.AddCaller())
}
if cfg.StackTrace {
options = append(options, zap.AddStacktrace(levelEnabler))
}
return
}Efecto final
ZapLogTest 2023/01/09 - 19:44:00.91076 INFO demo/zap.go:49 Registro inicializado correctamente