Как организовать логирование в Go-приложении?

Ответ

В Go существует два основных подхода к логированию: неструктурированное (стандартная библиотека) и структурированное (сторонние библиотеки), которое является предпочтительным для современных бэкенд-сервисов.

1. Стандартная библиотека log (Неструктурированное логирование)

Простой механизм для вывода текстовых сообщений. Подходит для небольших утилит или на ранних этапах разработки.

import "log"

log.Println("Сервер запущен на порту 8080") // Просто текстовая строка

Недостаток: Текстовые логи сложно парсить, фильтровать и анализировать в автоматизированных системах (ELK, Splunk, Grafana Loki).

2. Структурированное логирование (Рекомендуемый подход)

Логи записываются в формате JSON или key-value, что делает их машиночитаемыми. Это стандарт для микросервисной архитектуры.

Популярные библиотеки:

  • zerolog: Очень высокая производительность и удобный API.
  • zap: Разработана в Uber, также нацелена на максимальную производительность.
  • slog: Новый официальный пакет для структурированного логирования, добавленный в Go 1.21. Становится стандартом де-факто.

Пример с slog (Go 1.21+):

package main

import (
    "log/slog"
    "os"
)

func main() {
    // Создаем логгер, который пишет в stdout в формате JSON
    logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))

    // Устанавливаем его как логгер по умолчанию
    slog.SetDefault(logger)

    // Примеры логирования с разными уровнями и атрибутами
    slog.Info(
        "Пользователь успешно авторизовался",
        slog.String("username", "admin"),
        slog.Int("user_id", 123),
    )

    slog.Error(
        "Не удалось обработать запрос",
        slog.String("error", "database connection failed"),
        slog.String("request_id", "abc-123-xyz"),
    )
}
// Вывод:
// {"time":"...","level":"INFO","msg":"Пользователь успешно авторизовался","username":"admin","user_id":123}
// {"time":"...","level":"ERROR","msg":"Не удалось обработать запрос","error":"database connection failed","request_id":"abc-123-xyz"}

Лучшие практики и важные концепции:


  1. Уровни логирования (Log Levels): Используйте разные уровни для классификации сообщений (DEBUG, INFO, WARN, ERROR). Это позволяет настраивать детализацию логов в разных окружениях (например, DEBUG на dev, INFO и выше на prod).



  2. Контекстное логирование: Обогащайте логи сквозными данными, такими как request_id или trace_id. Это критически важно для отслеживания потока выполнения запроса в микросервисах. Логгер следует передавать через context.Context.



  3. Вывод в stdout: В контейнеризированных средах (Docker, Kubernetes) всегда пишите логи в stdout/stderr. Система оркестрации сама позаботится об их сборе и перенаправлении.



  4. Не логируйте чувствительные данные: Никогда не записывайте в логи пароли, токены, персональные данные и другую конфиденциальную информацию.



  5. Формат: Используйте JSON для продакшн-окружений для легкой интеграции с системами сбора логов и текстовый формат для локальной разработки для лучшей читаемости.