
Привет, Хабр!
Как в 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/
Добавить комментарий