Zap
Zap هو مكوّن سجلات سريع، منظم، ومتعدد المستويات مبني بلغة Go.
المستودع الرسمي: uber-go/zap: Blazing fast, structured, leveled logging in Go. (github.com)
الوثائق الرسمية: zap package - go.uber.org/zap - Go Packages
التثبيت
go get -u go.uber.org/zapالبدء السريع
قدم المسؤول مثالين للبدء السريع، كلاهما سجلات على مستوى الإنتاج، الأول هو Sugar الذي يدعم نمط printf لكن بأداء أقل نسبيًا.
logger, _ := zap.NewProduction()
defer logger.Sync() // مزامنة المخزن المؤقت إلى الملف عند انتهاء البرنامج
sugar := logger.Sugar()
sugar.Infow("failed to fetch URL",
"url", url,
"attempt", 3,
"backoff", time.Second,
)
sugar.Infof("Failed to fetch URL: %s", url)الثاني هو logger ذو الأداء الأفضل، لكنه يدعم فقط إخراج الأنواع القوية
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),
)TIP
استخدام Zap بسيط جدًا، لكن التعقيد يكمن في تكوين سجلات مناسبة لمشروعك، والأمثلة الرسمية قليلة، لذا يجب قراءة تعليقات الكود المصدري بعناية.
التكوين
عمومًا يتم كتابة تكوين السجلات في ملف التكوين، ويدعم تكوين Zap أيضًا إلغاء التسلسل من ملف التكوين، لكنه يدعم فقط التكوينات الأساسية، وحتى التكوينات المتقدمة التي يقدمها المسؤول بسيطة جدًا ولا تكفي للاستخدام الفعلي، لذا سنشرح تفاصيل التكوين بالتفصيل.
أولاً لنلقِ نظرة على بنية التكوين العامة، يجب فهم معنى كل حقل
type Config struct {
// أدنى مستوى للسجلات
Level AtomicLevel `json:"level" yaml:"level"`
// وضع التطوير، يؤثر بشكل رئيسي على تتبع المكدس
Development bool `json:"development" yaml:"development"`
// تتبع المستدعي
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
// تتبع المكدس
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
// العينات، في حالة تقييد تأثير السجلات على الأداء يتم تسجيل جزء فقط من السجلات التمثيلية
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
// الترميز، ينقسم إلى وضعي json و console
Encoding string `json:"encoding" yaml:"encoding"`
// تكوين الترميز، بشكل رئيسي بعض تكوينات تنسيق الإخراج
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
// مسار إخراج ملف السجلات
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
// مسار إخراج ملف الأخطاء
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
// إضافة بعض المحتوى الافتراضي للسجلات
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
}فيما يلي تفاصيل تكوين الترميز
type EncoderConfig struct {
// المفتاح، إذا كان المفتاح فارغًا، لن يتم إخراج الخاصية المقابلة
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"`
// بعض المشفرات المخصصة
EncodeLevel LevelEncoder `json:"levelEncoder" yaml:"levelEncoder"`
EncodeTime TimeEncoder `json:"timeEncoder" yaml:"timeEncoder"`
EncodeDuration DurationEncoder `json:"durationEncoder" yaml:"durationEncoder"`
EncodeCaller CallerEncoder `json:"callerEncoder" yaml:"callerEncoder"`
// مشفر اسم المسجل
EncodeName NameEncoder `json:"nameEncoder" yaml:"nameEncoder"`
// مشفر الانعكاس، بشكل رئيسي للنوع interface{}، إذا لم يكن موجودًا يكون الافتراضي jsonencoder
NewReflectedEncoder func(io.Writer) ReflectedEncoder `json:"-" yaml:"-"`
// سلسلة فصل إخراج وحدة التحكم
ConsoleSeparator string `json:"consoleSeparator" yaml:"consoleSeparator"`
}Option يتعلق ببعض مفاتيح التكوين وتطبيقها، ولديه العديد من التطبيقات.
type Option interface {
apply(*Logger)
}
// تطبيق Option
type optionFunc func(*Logger)
func (f optionFunc) apply(log *Logger) {
f(log)
}
// التطبيق
func Development() Option {
return optionFunc(func(log *Logger) {
log.development = true
})
}هذا هو الجوهر الأساسي للسجلات الأكثر استخدامًا، وحقوله الداخلية تمثل بشكل أساسي خطوات تكويننا، ويمكن أيضًا الرجوع إلى خطوات المسؤول عند إلغاء التسلسل، وهي تقريبًا متشابهة.
type ioCore struct {
// مستوى السجلات
LevelEnabler
// ترميز السجلات
enc Encoder
// كتابة السجلات
out WriteSyncer
}zap.Encoder مسؤول عن تنسيق السجلات وترميزها
zap.WriteSyncer مسؤول عن إخراج السجلات، بشكل رئيسي الإخراج إلى الملف ووحدة التحكم
zap.LevelEnabler أدنى مستوى للسجلات، السجلات دون هذا المستوى لن تُخرج عبر syncer.
ترميز السجلات
يتعلق ترميز السجلات بشكل رئيسي ببعض تنسيقات التفاصيل للسجلات، أولاً لنلقِ نظرة على الإخراج باستخدام السجلات الأصلية مباشرة.
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"}ستجد أن هذا السطر به عدة مشاكل:
- لا يوجد وقت
- لا يوجد موضع المستدعي، لا نعرف من أين أُخرج هذا السطر، وبالتالي عند حدوث خطأ لا يمكن تتبعه
- لا يوجد حالة المكدس
الآن سنحل المشاكل خطوة بخطوة، بشكل رئيسي بتعديل zapcore.EncoderConfig، أولاً يجب علينا كتابة ملف التكوين بأنفسنا، دون استخدام إلغاء التسلسل المباشر من المسؤول. أولاً ننشئ ملف تكوين config.yml
# تكوين سجلات Zap
zap:
prefix: ZapLogTest
timeFormat: 2006/01/02 - 15:04:05.00000
level: debug
caller: true
stackTrace: false
encode: console
# أين تُخرج السجلات file | console | both
writer: both
logFile:
maxSize: 20
backups: 5
compress: true
output:
- "./log/output.log"البنية المُرتبطة
// ZapConfig
// @Date: 2023-01-09 16:37:05
// @Description: بنية تكوين سجلات 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: بنية تكوين ملف السجلات
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
استخدم Viper لقراءة التكوين، الكود المحدد محذوف.
type TimeEncoder func(time.Time, PrimitiveArrayEncoder)TimerEncoder في جوهره هو دالة، يمكننا استخدام مشفرات الوقت الأخرى التي يوفرها المسؤول، أو يمكننا كتابته بأنفسنا.
func CustomTimeFormatEncoder(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(global.Config.ZapConfig.Prefix + "\t" + t.Format(global.Config.ZapConfig.TimeFormat))
}الجزء الكلي كالتالي
func zapEncoder(config *ZapConfig) zapcore.Encoder {
// إنشاء تكوين جديد
encoderConfig := zapcore.EncoderConfig{
TimeKey: "Time",
LevelKey: "Level",
NameKey: "Logger",
CallerKey: "Caller",
MessageKey: "Message",
StacktraceKey: "StackTrace",
LineEnding: zapcore.DefaultLineEnding,
FunctionKey: zapcore.OmitKey,
}
// تنسيق وقت مخصص
encoderConfig.EncodeTime = CustomTimeFormatEncoder
// مستوى السجلات بأحرف كبيرة
encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
// فاصل زمني بالثواني
encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
// إخراج مستدعي مختصر
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
// تسلسل كامل لاسم المسجل
encoderConfig.EncodeName = zapcore.FullNameEncoder
// ترميز السجلات النهائي json أو console
switch config.Encode {
case "json":
{
return zapcore.NewJSONEncoder(encoderConfig)
}
case "console":
{
return zapcore.NewConsoleEncoder(encoderConfig)
}
}
// الافتراضي console
return zapcore.NewConsoleEncoder(encoderConfig)
}إخراج السجلات
ينقسم إخراج السجلات إلى إخراج وحدة التحكم وإخراج الملف، يمكننا التكوين ديناميكيًا وفقًا لملف التكوين، وإذا أردنا تقسيم ملفات السجلات نحتاج إلى استخدام اعتماد طرف ثالث آخر.
go get -u github.com/natefinch/lumberjackالكود النهائي كالتالي
func zapWriteSyncer(cfg *ZapConfig) zapcore.WriteSyncer {
syncers := make([]zapcore.WriteSyncer, 0, 2)
// إذا تم تفعيل إخراج وحدة التحكم للسجلات، أضف كاتب وحدة التحكم
if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteConsole {
syncers = append(syncers, zapcore.AddSync(os.Stdout))
}
// إذا تم تفعيل تخزين ملف السجلات، أضف الكاتب وفقًا لشريحة مسار الملف
if cfg.Writer == config.WriteBoth || cfg.Writer == config.WriteFile {
// إضافة مُخرج السجلات
for _, path := range cfg.LogFile.Output {
logger := &lumberjack.Logger{
Filename: path, // مسار الملف
MaxSize: cfg.LogFile.MaxSize, // حجم ملف التقسيم
MaxBackups: cfg.LogFile.BackUps, // عدد النسخ الاحتياطية
Compress: cfg.LogFile.Compress, // هل يتم الضغط
LocalTime: true, // استخدام الوقت المحلي
}
syncers = append(syncers, zapcore.Lock(zapcore.AddSync(logger)))
}
}
return zap.CombineWriteSyncers(syncers...)
}مستوى السجلات
لدى المسؤول عناصر تعداد لمستوى السجلات، استخدمها مباشرة.
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
}
// المستوى الافتراضي Debug
return zap.DebugLevel
}البناء النهائي
func InitZap(config *ZapConfig) *zap.Logger {
// بناء المشفر
encoder := zapEncoder(config)
// بناء مستوى السجلات
levelEnabler := zapLevelEnabler(config)
// الحصول على Core و Options
subCore, options := tee(config, encoder, levelEnabler)
// إنشاء Logger
return zap.New(subCore, options...)
}
// دمج الكل
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)
}
// بناء 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
}التأثير النهائي
ZapLogTest 2023/01/09 - 19:44:00.91076 INFO demo/zap.go:49 اكتملت تهيئة السجلات