Как организовать логирование в 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 для продакшн-окружений для легкой интеграции с системами сбора логов и текстовый формат для локальной разработки для лучшей читаемости.

Ответ 18+ 🔞

Так, слушай, вот смотри. В Го, блядь, с логированием вообще два пути, как в той сказке — налево пойдёшь, направо пойдёшь, а в итоге в пизде окажешься, если не разберёшься.

Первый путь — это стандартная библиотека log. Ну, знаешь, такая простая, как валенок. Написал log.Println("Сервер запущен") и всё, пиздец. Для какой-нибудь мелкой утилитки, которую ты запускаешь раз в год, чтобы кота покормить — сойдёт. Но если ты делаешь нормальный сервис, который должен жить и не падать, то это — чистой воды распиздяйство. Потому что эти текстовые логи потом, блядь, ни в какую систему мониторинга не запихнёшь, их парсить — это ж ебать мои старые костыли, проще самому глазами смотреть.

Второй путь — это структурированное логирование. Вот это уже, блядь, по-взрослому. Логи записываются не просто строками, а в формате, который машины понимают, обычно в JSON. Это как раз для тех, кто не хочет потом ночами сидеть и вручную искать, где же этот ёбаный баг.

Есть, конечно, библиотеки: zerolog — быстрая, как угорелая, zap — от Uber, тоже шустрая. Но, блядь, с версии 1.21 в Го появился свой родной пакет — slog. Вот он и становится, по сути, стандартом. И правильно, нахуй плодить сущности.

Смотри, как это выглядит с slog:

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. Уровни, сука, используй. DEBUG, INFO, WARN, ERROR. На деве у тебя может быть DEBUG, чтобы видеть каждую соплю, а на проде — только INFO и выше, чтобы лог не раздулся до овердохуища.
  2. Контекст — это всё. Обязательно добавляй в каждый лог request_id, trace_id или ещё какую хитрожопую метку. Иначе как ты потом в микросервисах поймёшь, какой запрос куда пошёл и где сдох? Логгер передавай через context.Context, не будь мудаком.
  3. Пиши в stdout. Ты же в контейнерах всё запускаешь? Так вот, контейнерная среда сама заберёт логи из стандартного вывода. Не надо придумывать велосипеды и писать в файлы, которые потом никто не найдёт.
  4. Не логируй всякую хуйню. Пароли, токены, номера карт — это пиздец, а не данные для лога. Один раз зальёшь такое в прод — и терпения ноль ебать у всей команды безопасности.
  5. Формат. На проде — JSON, чтобы машинам удобно было. Локально можешь текстовый формат включить, чтобы глаза не ебать. slog это умеет.

Вот и вся философия. Не усложняй, но и не будь примитивным, как этот Герасим из рассказа, который только "Муму" и мог сказать.