Anophel-آنوفل آشنایی با Logging در Go: بررسی عمیق کتابخانه Zap

آشنایی با Logging در Go: بررسی عمیق کتابخانه Zap

انتشار:
1

در دنیای توسعه اپلیکیشن، لاگ (logging) نقش مهمی ایفا می کند که اغلب مورد توجه قرار نمی گیرد. مانند این است که یک دستیار سخت کوش داشته باشید که به طور مداوم تمام جزئیات عملکرد برنامه و تعاملات کاربر را یادداشت می کند. در این مقاله از آنوفل خواهیم دید که چرا logging بسیار مهم است و چگونه با استفاده از کتابخانه قدرتمند Zap یک سیستم لاگ را در برنامه Go خود به راحتی پیاده سازی کنید.

چرا باید یک سیستم logging را پیاده سازی کنم؟

پیاده‌سازی یک سیستم logging ممکن است یک کار اضافی در فرآیند توسعه به نظر برسد، اما اهمیت آن را نمی‌توان دست کم گرفت. در حالی که توسعه‌دهندگان تمام تلاش خود را می‌کنند تا هر مشکل احتمالی را در طول توسعه پیش‌بینی کنند، واقعیت این است که برخی از آن‌ها شناسایی نشده می‌گذرند. اینجاست که logging وارد عمل می شود، و یک رکورد دقیق از رویدادهایی که در برنامه اتفاق افتاده است ارائه می دهد.


گزارش‌ها به‌عنوان یک دنباله خرده نان عمل می‌کنند، توسعه‌دهندگان را به سمت علت اصلی هرگونه ناهنجاری که ممکن است به وجود بیاید راهنمایی می‌کند و بینش ارزشمندی را برای دیباگ و حل مشکلات ارائه می‌دهد.


علاوه بر نقشی که در دیباگ دارد، لاگ به عنوان یک متحد قوی برای تقویت امنیت برنامه عمل می کند. با ثبت هر تعامل، از ورودی‌های کاربر گرفته تا پاسخ‌های سیستم، توسعه‌دهندگان می‌توانند به راحتی وضعیت برنامه خود را کنترل کنند و با مشکلات احتمالی مقابله کنند.

چرا Zap و چرا کتابخانه استاندارد نه؟

در حالی که کتابخانه استاندارد ممکن است برای کاربردهای ساده‌تر کافی باشد، سناریوهای پرترافیک نیاز به راه‌حل قوی‌تری دارند و اینجاست که کتابخانه Zap می‌تواند به کمک بیاید. Zap با در نظر گرفتن عملکرد طراحی شده است، به یک دارایی ارزشمند در این محیط ها تبدیل می شود. Zap که به طیف گسترده ای از عملکردها مجهز شده است، فرآیندهای ضبط را ساده و بهبود می بخشد و به انتخاب ترجیحی برای توسعه دهندگانی تبدیل می شود که به دنبال کارایی و مقیاس پذیری هستند. یکی از ویژگی های برجسته Zap سیستم از پیش تعریف شده در سطح گزارش آن است که فرآیند مدیریت و اولویت بندی ورودی های گزارش را ساده می کند.

علاوه بر این، پشتیبانی آن از ورودی‌های ساختاریافته و قالب‌بندی JSON تضمین می‌کند که گزارش‌ها نه تنها جامع هستند، بلکه به راحتی برای تجزیه و تحلیل بیشتر قابل تجزیه هستند. علاوه بر این، انعطاف‌پذیری Zap به توانایی آن در تغییر مسیر گزارش‌ها به چندین خروجی به طور همزمان گسترش می‌یابد و به توسعه‌دهندگان انعطاف‌پذیری بیشتری در نظارت و مدیریت رفتار برنامه‌ها ارائه می‌دهد.


در بخش های بعدی، نحوه ادغام Zap را در پروژه خود و همچنین برخی از مهم ترین ویژگی ها را بررسی خواهیم کرد.

نصب و راه اندازی Zap در Go

برای نصب کتابخانه Zap، به سادگی این دستور را در دایرکتوری اصلی پروژه اجرا کنید:

go get -u go.uber.org/zap

سپس کتابخانه را می توان به عنوان go.uber.org/zap وارد کرد.


پیکربندی اولیه

برای پیکربندی اولیه لاگر، Zap از قبل دو لاگر پیکربندی شده، یکی برای توسعه و دیگری برای تولید ارائه می‌کند. پس از تنظیم گلوبال ثبت‌کننده، تابع مربوطه را می‌توان با توجه به محیط و نیازهای خاص شما فراخوانی کرد. همین!


پیکربندی پیش فرض را می توان در کمترین زمان یکپارچه کرد و قابلیت ضبط برنامه را بهبود بخشید. با این حال، امکان ایجاد تنظیمات سفارشی برای رفع نیازهای خود نیز وجود دارد.

logger, err := zap.NewDevelopment() // or zap.NewProduction()
if err != nil {
  log.Fatal("Failed to initialize logger")
}
zap.ReplaceGlobals(logger)

از ()zap.ReplaceGlobals برای تنظیم logger گلوبال استفاده کنید و سپس از طریق ()zap.S برای نسخه Sugar یا ()zap.L برای Logger استاندارد به آن دسترسی پیدا کنید.


تفاوت های این دو نسخه را در قسمت بعدی به تفصیل خواهیم دید. در حال حاضر، در نظر بگیرید که نسخه Sugar ساده تر از نسخه Logger است، اما هنوز هم برای استفاده قدرتمند است.

zap.L().Info("This is an info message")
zap.S().Error("This is an error message")

یک مثال ساده:

package main

import (
 "log"

 "go.uber.org/zap"
)

func main() {
 logger, err := zap.NewDevelopment() // or zap.NewProduction()
 if err != nil {
  log.Fatal("Failed to initialize logger")
 }

 zap.ReplaceGlobals(logger)

 zap.S().Info("This is an info message")
 zap.S().Errorf("This is an %s!", "error message")
 zap.S().Warnln("This is", "a warn", "message")

 zap.L().Warn("This is an info message")
 zap.L().Info("message", zap.Bool("flag", true), zap.Int("number", 42))
}

قالب پیش‌فرض Logger 

به طور پیش فرض، Zap شامل چهار بخش اصلی در هر ورودی گزارش است:


زمان: نشان دهنده timestamp است که ورودی گزارش ثبت شده است.
سطح گزارش: میزان شدت یا اهمیت پیام گزارش را مشخص می کند، مانند DEBUG، INFO، WARN یا ERROR.
Caller: منبع یا مبدأ پیام گزارش را مشخص می کند، معمولاً تابع یا ماژولی را که دستور گزارش در آن اجرا شده است نشان می دهد.
Log message: حاوی محتوای واقعی ورودی گزارش است که رویداد یا اطلاعات ثبت شده را توصیف می کند. فیلدهای اختیاری ممکن است پیام گزارش را دنبال کنند و زمینه یا جزئیات بیشتری در مورد رویداد ارائه دهند.


Logger در مقابل Sugar 

کتابخانه Zap دو نوع مختلف از لاگر را ارائه می دهد: یکی برای استفاده ساده تر و هنوز هم بسیار سریع (Sugar) و دیگری که ایمنی نوع و ساختار را به حداکثر کارایی می رساند (Logger).


توسعه دهندگان Zap پیشنهاد می کنند که با Sugar Logger شروع کنید زیرا هنوز هم بسیار سریعتر از بسیاری از لاگرهای دیگر است اما در عین حال قدرتمند است. من نیز این را ترجیح می دهم و آن را کامل و مناسب برای نیازهایم یافتم.


در هر صورت، در نظر بگیرید که تغییر از یکی به دیگری چندان پیچیده نیست. بنابراین، با خیال راحت با Sugar one شروع کنید و زمانی که فکر کردید زمان آن فرا رسیده است به Logger بروید.

پیکربندی پیش فرض

پیکربندی پیش‌فرض موجود با Zap برای محیط توسعه و تولید می‌تواند برای اکثر سناریوهای ممکن کافی باشد. سعی کنید به دو پیکربندی موجود با Zap و مقادیر پیش‌فرض آنها نگاهی بیندازید تا ببینید آیا نیاز به شخصی‌سازی آنها دارید یا خیر.

در بخش بعدی نحوه ایجاد یک پیکربندی کاملا سفارشی را خواهیم دید.

پیکربندی توسعه:

// NewDevelopmentConfig builds a reasonable default development logging
// configuration.
// Logging is enabled at DebugLevel and above, and uses a console encoder.
// Logs are written to standard error.
// Stacktraces are included on logs of WarnLevel and above.
// DPanicLevel logs will panic.
//
// See [NewDevelopmentEncoderConfig] for information
// on the default encoder configuration.
func NewDevelopmentConfig() Config {
 return Config{
  Level:            NewAtomicLevelAt(DebugLevel),
  Development:      true,
  Encoding:         "console",
  EncoderConfig:    NewDevelopmentEncoderConfig(),
  OutputPaths:      []string{"stderr"},
  ErrorOutputPaths: []string{"stderr"},
 }
}

نسخه production :

// NewProductionConfig builds a reasonable default production logging
// configuration.
// Logging is enabled at InfoLevel and above, and uses a JSON encoder.
// Logs are written to standard error.
// Stacktraces are included on logs of ErrorLevel and above.
// DPanicLevel logs will not panic, but will write a stacktrace.
//
// Sampling is enabled at 100:100 by default,
// meaning that after the first 100 log entries
// with the same level and message in the same second,
// it will log every 100th entry
// with the same level and message in the same second.
// You may disable this behavior by setting Sampling to nil.
//
// See [NewProductionEncoderConfig] for information
// on the default encoder configuration.
func NewProductionConfig() Config {
 return Config{
  Level:       NewAtomicLevelAt(InfoLevel),
  Development: false,
  Sampling: &SamplingConfig{
   Initial:    100,
   Thereafter: 100,
  },
  Encoding:         "json",
  EncoderConfig:    NewProductionEncoderConfig(),
  OutputPaths:      []string{"stderr"},
  ErrorOutputPaths: []string{"stderr"},
 }
}

پیکربندی سفارشی

همچنین می توان یک پیکربندی سفارشی ایجاد کرد و سپس یک لاگر جدید را با یک پیکربندی خاص جدید مقداردهی کرد. می توانید از ابتدا شروع کنید یا از پیکربندی توسعه یا تولید به عنوان پایه شروع استفاده کنید.

شروع از پیکربندی توسعه:

// Use default development config as starting base.
config := zap.NewDevelopmentConfig()

// Customize some fields
config.Encoding = "console"
config.EncoderConfig.TimeKey = "time"
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
config.OutputPaths = []string{"stdout"}
config.ErrorOutputPaths = []string{"stderr"}

// Build configuration and create a new logger
logger, err := config.Build()
if err != nil {
  log.Fatal(err)
}

// Set global logger
zap.ReplaceGlobals(logger)

شروع از صفر (برای تعریف هر فیلد در اینجا باید دقت شود):

// Start from an empty configuration
 config := zap.Config{
  Level:       zap.NewAtomicLevelAt(zap.DebugLevel),
  Development: true,
  Encoding:    "json",
  EncoderConfig: zapcore.EncoderConfig{
   MessageKey:     "message",
   LevelKey:       "level",
   TimeKey:        "time",
   EncodeLevel:    zapcore.CapitalLevelEncoder,
   EncodeTime:     zapcore.ISO8601TimeEncoder,
   EncodeDuration: zapcore.SecondsDurationEncoder,
   EncodeCaller:   zapcore.ShortCallerEncoder,
  },
  OutputPaths:      []string{"stdout"},
  ErrorOutputPaths: []string{"stderr"},
  // ...
 }

 // Build configuration and create a new logger
 logger, err := config.Build()
 if err != nil {
  log.Fatal(err)
 }

 // Set global logger
 zap.ReplaceGlobals(logger)

زمان encoder سفارشی

اگر می‌خواهید قالب زمان را سفارشی کنید، می‌توانید یک time encoder سفارشی ایجاد کنید و پارامتر EncodeTime را با آن تنظیم کنید. این می تواند برای تعریف ساختار زمانی واضح تر یا تنظیم یک تغییر زمانی متفاوت مفید باشد.

در زیر می توانید نمونه ای را مشاهده کنید که از string فرمت زمان معمول پیروی می کند:

// CustomTimeEncoder serializes a time.Time to a custom formatted string.
func CustomTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
 enc.AppendString(t.Format("2006-01-02 15:04:05.000 Z07"))
}

سپس، encoder پیش فرض را می توان با تنظیم تابع EncoderTime در پیکربندی جایگزین کرد:

// Use default development config as starting base.
config := zap.NewDevelopmentConfig()
config.EncoderConfig.EncodeTime = CustomTimeEncoder

سطوح Log در Zap

Zap هفت سطح گزارش از قبل تعریف شده را ارائه می دهد:


Debug اطلاعات دقیقی که در طول توسعه مفید هستند اما باید در محیط های تولید پنهان شوند. این به توسعه دهندگان کمک می کند تا مسائل را شناسایی کرده و جریان برنامه را درک کنند.


Info پیام‌های اطلاعاتی که به‌روزرسانی‌های کلی یا اطلاعات وضعیت برنامه را منتقل می‌کنند. این پیام ها معمولاً مربوط به کاربران یا مدیران سیستم هستند.


Warning اخطار نشان می دهد که شما چیزی غیرمنتظره را تأیید کرده اید یا مشکل بالقوه ای وجود دارد که اگر به آن رسیدگی نشود، می تواند مشکلاتی ایجاد کند. این به عنوان یک علامت هشدار برای مشکلات احتمالی آینده عمل می کند.


Error هر پیامی که خطای رخ داده در حین اجرای برنامه را توصیف می کند. این پیام‌ها مشکلاتی را نشان می‌دهند که نیاز به توجه دارند، اما لزوماً مانع از ادامه اجرای برنامه نمی‌شوند.


DPanic (Development Panic) این سطح مختص محیط های توسعه است. هنگامی که فعال می شود، لاگر پیام را چاپ می کند و سپس Panic می کند و باعث از کار افتادن برنامه می شود. با این حال، در محیط های تولید، فقط پیام را همراه با stack trace چاپ می کند. این رفتار توسط فلگ توسعه در پیکربندی لاگر کنترل می شود.


Panic یک پیام Panic نشان می دهد که اتفاقی غیرمنتظره رخ داده است و برنامه نمی تواند به طور ایمن ادامه دهد. ضروری است که Panic را با دقت کنترل کنید، احتمالاً با تنظیم یک عملکرد بازیابی برای مدیریت برازنده وضعیت.


Fatal این شدیدترین سطح است. این نشان می دهد که اتفاق فاجعه باری رخ داده است و برنامه دیگر نمی تواند اجرا شود. پس از چاپ پیام، برنامه بلافاصله خاتمه می یابد. خطاهای Fatal معمولاً نیاز به توجه فوری دارند و ممکن است منجر به اختلال در سرویس یا از دست دادن اطلاعات شوند.

const (
 // DebugLevel logs are typically voluminous, and are usually disabled in
 // production.
 DebugLevel Level = iota - 1
 // InfoLevel is the default logging priority.
 InfoLevel
 // WarnLevel logs are more important than Info, but don't need individual
 // human review.
 WarnLevel
 // ErrorLevel logs are high-priority. If an application is running smoothly,
 // it shouldn't generate any error-level logs.
 ErrorLevel
 // DPanicLevel logs are particularly important errors. In development the
 // logger panics after writing the message.
 DPanicLevel
 // PanicLevel logs a message, then panics.
 PanicLevel
 // FatalLevel logs a message, then calls os.Exit(1).
 FatalLevel

 _minLevel = DebugLevel
 _maxLevel = FatalLevel

 // InvalidLevel is an invalid value for Level.
 //
 // Core implementations may panic if they see messages of this level.
 InvalidLevel = _maxLevel + 1
)

از سطح هشدار به بعد، ردیابی استک به طور خودکار همراه با پیام چاپ می شود. این عملکرد را می توان در پیکربندی لاگر غیرفعال کرد.


همچنین امکان فعال کردن رنگ برای سطوح گزارش وجود دارد که در یک محیط توسعه مفید است.

zap.S().Debug("This is a debug message.")
zap.S().Info("This is an info message.")
zap.S().Warn("This is a warning message.")
zap.S().Error("This is an error message.")
zap.S().DPanic("This is a development panic message.")
zap.S().Panic("This is a panic message.")
zap.S().Fatal("This is a fatal message.")

کد نتیجه قبلی (فقط برای اهداف آموزشی، برای استفاده عملی چندان مفید نیست):

package main

import (
 "log"
 "time"

 "go.uber.org/zap"
 "go.uber.org/zap/zapcore"
)

// CustomTimeEncoder serializes a time.Time to a custom formatted string.
func CustomTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
 enc.AppendString("XX-YY-ZZ AA:BB:CC")
}

func recoverer() {
 if r := recover(); r != nil {
  return
 }
}

func main() {
 // Use default development config as starting base.
 config := zap.NewDevelopmentConfig()

 // Customize available fields
 config.Encoding = "console"
 config.EncoderConfig.TimeKey = "time"
 config.EncoderConfig.EncodeTime = CustomTimeEncoder
 config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
 config.OutputPaths = []string{"stdout"}
 config.ErrorOutputPaths = []string{"stderr"}
 config.DisableStacktrace = true
 config.DisableCaller = true

 // Build configuration and create a new logger
 logger, err := config.Build()
 if err != nil {
  log.Fatal(err)
 }

 zap.ReplaceGlobals(logger)

 zap.S().Debug("This is a debug message")
 zap.L().Info("This is an info message")
 zap.L().Warn("This is a warn message")
 zap.L().Error("This is an error message")

 // deferred function are executed in reverse order
 defer func() {
  zap.L().Fatal("This is a fatal message")
 }()

 defer func() {
  defer recoverer()
  zap.L().DPanic("This is a dev panic message")
 }()

 defer func() {
  defer recoverer()
  zap.L().Panic("This is a panic message")
 }()
}

ثبت پیام های جدید

با Sugar Logger، چهار عملکرد اصلی برای هر سطح log در دسترس است:


Debug، Info، Warn، Error، DPanic، Panic، Fatal آنها مانند fmt کار می کنند. چاپ چاپ تقریباً همان چیزی است که شما نیاز دارید. فاصله ها فقط زمانی بین آرگومان ها اضافه می شوند که هیچ کدام رشته ای نباشد.


Debugln، Infoln، Warnln، Errorln، DPanicln، Panicln، Fatalln آنها به طور یکسان با fmt.Println کار می کنند. فاصله بین هر رشته داده شده اضافه می شود و یک خط جدید در پایان اضافه می شود.


Debugf، Infof، Warnf، Errorf، DPanicf، Panicf، Fatalf آنها به طور یکسان با fmt.Printf کار می کنند. رشته اول می تواند شامل یک یا چند فعل برای تعریف رشته و به دنبال آن همان تعداد پارامتر باشد.


Debugw، Infow، Warnw، Errorw، DPanicw، Panicw، Fatalw متد خاص برای مدیریت پیام ها، مانند نسخه Logger Zap. پارامتر اول حاوی یک پیام کلی است و به دنبال آن یک یا چند جفت کلید-مقدار وجود دارد.

zap.S().Info("This is a standard info message.")
zap.S().Infof("This is an info message with %s %d", "formatting", 42)
zap.S().Infoln("String", "concatenation", "is", "done", "automatically", "like", "fmt.Println")
zap.S().Infow("message", "key1", "value1", "key2", "value2")

از سوی دیگر، نسخه Logger تنها اجازه ایجاد پیام های گزارش را با استفاده از نسخه استاندارد برای هر سطح گزارش و تعیین نوع و مقدار هر فیلد می دهد:

 zap.L().Info("message",
  zap.Bool("flag", true),
  zap.Int("number", 42),
  zap.String("str", "hello"),
  zap.Duration("dur", 42*time.Second),
  zap.Float32("flo", 42.42),
  zap.Time("tim", time.Now()))

همچنین :

package main

import (
 "log"
 "time"

 "go.uber.org/zap"
)

func main() {
 logger, err := zap.NewDevelopment() // or zap.NewProduction()
 if err != nil {
  log.Fatal("Failed to initialize logger")
 }

 zap.ReplaceGlobals(logger)

 zap.S().Info("This is a standard info message.")
 zap.S().Infof("This is an info message with %s %d", "formatting", 42)
 zap.S().Infoln("String", "concatenation", "is", "done", "automatically", "like", "fmt.Println")
 zap.S().Infow("message", "key1", "value1", "key2", "value2")

 zap.L().Info("message",
  zap.Bool("flag", true),
  zap.Int("number", 42),
  zap.String("str", "hello"),
  zap.Duration("dur", 42*time.Second),
  zap.Float32("flo", 42.42),
  zap.Time("tim", time.Now()))
}

نتیجه

Logging نقش مهمی در هر برنامه ای ایفا می کند و استفاده از کتابخانه هایی مانند Zap می تواند پیاده سازی را تا حد زیادی ساده کند.کتابخانه Zap را به دلیل طراحی عملکرد محور و مجموعه ویژگی های غنی، به عنوان یک جایگزین برتر برای بسته گزارش استاندارد Go، به ویژه در سناریوهای پر ترافیک است.

#Logging#گو#گولنگ#Zap#go#golang
نظرات ارزشمند شما :

در حال دریافت...

مقاله های مشابه

در حال دریافت...