Логирование в Golang

от автора

Привет, Хабр!

Как в Golang логирование поживает? Рассмотрим этот вопрос в статье.

Рассмотрим основные библиотеки и подходы.

Логирование в Go

Cтандартная библиотека log дает вам все необходимое для логирования без внешних зависимостей.

Для начала работы с log достаточно импортировать пакет и использовать его функции:

import "log"  func main() {     log.Println("This is a log message!") }

Этот код выведет сообщение вместе с датой и временем его записи. Выглядит достаточно просто.

Функции log:

Print, Printf, Println: Print выводит сообщение, Printf позволяет форматировать вывод (подобно fmt.Printf), а Println добавляет новую строку в конце.

Fatal, Fatalf, Fatalln: Эти функции работают как Print-функции, но после вывода сообщения вызывают os.Exit(1), завершая программу.

Panic, Panicf, Panicln: Похожи на Fatal-функции, но вместо завершения программы вызывают панику.

С помощью логгера также можно настроить префикс сообщений, формат времени и определить, куда будут выводиться логи:

SetFlags: Определяет форматирование вывода. Например, log.Ldate | log.Ltime добавит дату и время к каждому сообщению.

SetPrefix: Устанавливает префикс для каждого сообщения.

SetOutput: Позволяет перенаправить вывод логов в любой io.Writer, будь то файл, буфер или HTTP-ответ.

Часто логи нужно сохранять в файлы для последующего анализа. Это делается путем перенаправления вывода логгера:

file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil {     log.Fatal("Failed to open log file:", err) } log.SetOutput(file)

Теперь все логи будут аккуратно сохраняться в app.log.

Также в логере существуют стандартные уровни логирования: Debug, Info, Warn, Error.

Библиотека Logrus

Logrus полностью совместима со стандартным интерфейсом логгера Go, но предоставляет гораздо больше функциональности.

Logrus поддерживает различные уровни логирования, такие как Debug, Info, Warn, Error, Fatal и Panic:

log.Debug("Debug message") log.Info("Info message") log.Warn("Warning message") log.Error("Error message") log.Fatal("Fatal message") // вызовет os.Exit(1) после логирования log.Panic("Panic message") // вызовет панику после логирования

Logrus умеет логировать структурированные данные:

log.WithFields(log.Fields{     "username": "johndoe",     "id": 123, }).Info("User details")

Также в лоргусе можно настраиват формат вывода логов.Можно выбрать между встроенными форматтерами, такими как JSON и текст, или создать свой собственный:

// JSON форматтер log.SetFormatter(&log.JSONFormatter{})  // текстовый форматтер с настройками log.SetFormatter(&log.TextFormatter{     FullTimestamp: true, })

Можно определить, куда будут отправляться ваши логи, используя метод SetOutput. Это может быть любой объект, который реализует интерфейс io.Writer, например, файл, стандартный вывод или удаленный сервер:

file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err == nil {     log.SetOutput(file) } else {     log.Info("Failed to log to file, using default stderr") }

Еще есть «hooks» — специальные функции, которые вызываются при каждом логировании сообщения:

log.AddHook(MyCustomHook{})

Можно настроить уровень логирования, чтобы контролировать, какие сообщения фактически записываются:

log.SetLevel(log.WarnLevel)

Библиотека Zap

Zap предлагает API, который позволяет быстро записывать логи, и предоставляет множество опций для настройки.

Как и большинство логгеров, Zap предлагает различные уровни логирования: Debug, Info, Warn, Error, DPanic, Panic, и Fatal:

logger.Debug("Debug message") logger.Info("Info message") logger.Warn("Warning message") logger.Error("Error message") logger.DPanic("DPanic message")  logger.Panic("Panic message")    logger.Fatal("Fatal message")  

Zap также позволяет легко добавлять контекстные поля к вашим логам:

logger.Info("Failed to fetch URL",     zap.String("url", url),     zap.Int("attempt", 3),     zap.Duration("backoff", time.Second), )

Есть два основных типа логгеров: Logger и SugaredLogger. Logger предлагает более высокую производительность, в то время как SugaredLogger предлагает более удобный, но менее производительный API:

logger, _ := zap.NewProduction() defer logger.Sync() // flushes buffer, if any  // Sugared логгер sugar := logger.Sugar() sugar.Infow("failed to fetch URL",     "url", url,     "attempt", 3,     "backoff", time.Second, )

C помощью Zap можно легко настраивать определение уровня логирования, форматирование и место назначения логов:

cfg := zap.Config{     Encoding:         "json",     Level:            zap.NewAtomicLevelAt(zap.InfoLevel),     OutputPaths:      []string{"stdout", "/tmp/logs"},     ErrorOutputPaths: []string{"stderr"},     EncoderConfig: zapcore.EncoderConfig{         MessageKey: "msg",         LevelKey:    "level",         TimeKey:     "ts",         EncodeTime:  zapcore.ISO8601TimeEncoder,         ...     }, } logger, _ := cfg.Build()

Zap позволяет создавать пользовательские encoder’ы для определения, как логи будут сериализованы, и WriteSyncer’ы для определения, куда они будут записаны:

encoderConfig := zapcore.EncoderConfig{...} core := zapcore.NewCore(     zapcore.NewJSONEncoder(encoderConfig),     zapcore.AddSync(os.Stdout),     zap.DebugLevel, ) logger := zap.New(core)

Примеры реализации

На Lorgus:

package main  import (     "os"     "github.com/sirupsen/logrus" )  var log = logrus.New()  func init() {     // установим уровень логирования     log.SetLevel(logrus.DebugLevel)      // установим форматирование логов в джейсоне     log.SetFormatter(&logrus.JSONFormatter{})      // установим вывод логов в файл     file, err := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)     if err == nil {         log.SetOutput(file)     } else {         log.Info("Не удалось открыть файл логов, используется стандартный stderr")     } }  func main() {   // пример лога     log.WithFields(logrus.Fields{         "username": "Иван Иванов",         "id": 42,     }).Info("Новый пользователь зарегистрирован")      simulateError() }  func simulateError() {     // представим, что здесь произошла ошибка     err := "Сервер базы данных не отвечает"     log.WithFields(logrus.Fields{         "event": "connect_database",         "error": err,     }).Error("Ошибка при подключении к базе данных") }

На Zap:

package main  import (     "go.uber.org/zap" )  var logger *zap.Logger  func init() {     var err error     logger, err = zap.NewProduction() // Или NewDevelopment для более подробного логирования     if err != nil {         panic(err) // Не удалось создать логгер     }     defer logger.Sync() // все асинхронные логи будут записаны перед выходом }  func main() {     // пример логирования     logger.Info("Приложение запущено")      simulateError()     simulateDebug() }  func simulateError()     // к примеру здесь произошло логирование     err := "Сервер базы данных не отвечает"     logger.Error("Ошибка при подключении к базе данных",         zap.String("event", "connect_database"),         zap.String("error", err),     ) }  func simulateDebug() {   // отладочное сообщение     feature := "Новая фича"     logger.Debug("Отладочная информация",         zap.String("feature", feature),     ) }

В микросервисах

Для начала нужно то, чтобы сервисы логируют данные в едином формате, предпочтительно в JSON, для упрощения парсинга и анализа.

Для отслеживания запросов через разные сервисы можно использовать Correlation ID — уникальный идентификатор, который передается между сервисами и записывается в логи.

Когда запрос входит в вашу систему (например, через API-шлюз), вы должны сгенерировать уникальный Correlation ID, если он еще не существует. Затем этот ID должен передаваться от сервиса к сервису.

package main  import (     "net/http"     "github.com/google/uuid" )  func main() {     http.HandleFunc("/", handler)     http.ListenAndServe(":8080", nil) }  func handler(w http.ResponseWriter, r *http.Request) {     // чекаем, есть ли Correlation ID в запросе     cid := r.Header.Get("X-Correlation-ID")     if cid == "" {         // если нет, генерируем новый         cid = uuid.New().String()     }      // логируем с Correlation ID     log.WithFields(logrus.Fields{         "correlation_id": cid,     }).Info("Запрос получен")      // добавляем Correlation ID в ответ     w.Header().Set("X-Correlation-ID", cid) }

Когда один сервис вызывает другой, он должен передать Correlation ID в вызове. Это можно сделать, добавив ID в заголовки HTTP-запроса или в тело сообщения, если вы используете другой протокол:

func callAnotherService(cid string) {     // ... код для вызова другого сервиса ...     req, _ := http.NewRequest("GET", "http://another-service", nil)     req.Header.Set("X-Correlation-ID", cid)     // ... отправка запроса ... }

С lorgus базовое логирование микросервсиов может выглядеть так:

package main  import (     "github.com/sirupsen/logrus"     "os" )  func main() {     log := logrus.New()     log.Formatter = &logrus.JSONFormatter{} // Устанавливаем формат логов в JSON      // добавляем Hook для отправки логов в Logstash или другую систему     log.AddHook(NewMyLogstashHook())      // запрос с Correlation ID     correlationID := "12345"     log.WithFields(logrus.Fields{         "correlation_id": correlationID,         "event":          "request_started",     }).Info("Начало обработки запроса")      // ..здесь бизнес логинка....      log.WithFields(logrus.Fields{         "correlation_id": correlationID,         "event":          "request_finished",     }).Info("Запрос обработан") }  // NewMyLogstashHook - создает новый Hook для отправки логов в Logstash или другую систему func NewMyLogstashHook() logrus.Hook {     //  логика подключения к Logstash или другой системе     //  можно использовать logrus-logstash-hook или аналогичные библиотеки     return &MyLogstashHook{} }  type MyLogstashHook struct {     // ... параметры подключения ... }  func (hook *MyLogstashHook) Fire(entry *logrus.Entry) error {     //  логика отправки лога     // к примеру, формируем JSON и отправляем его в Logstash     return nil }  func (hook *MyLogstashHook) Levels() []logrus.Level {     // указываем, для каких уровней логирования активен этот Hook     return logrus.AllLevels }

Больше про инфраструктуру и логирование эксперты из OTUS рассказывают в рамках практических онлайн-курсов.

Логирование может значительно упростить процесс отладки и мониторинга, а также повысить надежность приложений.

Keep coding, keep improving, и до новых встреч на Хабре.

и… с наступающим Новым Годом! ?


ссылка на оригинал статьи https://habr.com/ru/articles/782812/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *