Ответ
В 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"}
Лучшие практики и важные концепции:
-
Уровни логирования (Log Levels): Используйте разные уровни для классификации сообщений (
DEBUG,INFO,WARN,ERROR). Это позволяет настраивать детализацию логов в разных окружениях (например,DEBUGна dev,INFOи выше на prod). -
Контекстное логирование: Обогащайте логи сквозными данными, такими как
request_idилиtrace_id. Это критически важно для отслеживания потока выполнения запроса в микросервисах. Логгер следует передавать черезcontext.Context. -
Вывод в
stdout: В контейнеризированных средах (Docker, Kubernetes) всегда пишите логи вstdout/stderr. Система оркестрации сама позаботится об их сборе и перенаправлении. -
Не логируйте чувствительные данные: Никогда не записывайте в логи пароли, токены, персональные данные и другую конфиденциальную информацию.
-
Формат: Используйте
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"}
Видишь? Всё по полочкам. Не просто "ошибка", а сразу видно, какая ошибка и к какому запросу относится. Красота, ёпта.
А теперь, блядь, главные правила, чтобы не выстрелить себе в ногу, а потом охуевать:
- Уровни, сука, используй.
DEBUG,INFO,WARN,ERROR. На деве у тебя может бытьDEBUG, чтобы видеть каждую соплю, а на проде — толькоINFOи выше, чтобы лог не раздулся до овердохуища. - Контекст — это всё. Обязательно добавляй в каждый лог
request_id,trace_idили ещё какую хитрожопую метку. Иначе как ты потом в микросервисах поймёшь, какой запрос куда пошёл и где сдох? Логгер передавай черезcontext.Context, не будь мудаком. - Пиши в stdout. Ты же в контейнерах всё запускаешь? Так вот, контейнерная среда сама заберёт логи из стандартного вывода. Не надо придумывать велосипеды и писать в файлы, которые потом никто не найдёт.
- Не логируй всякую хуйню. Пароли, токены, номера карт — это пиздец, а не данные для лога. Один раз зальёшь такое в прод — и терпения ноль ебать у всей команды безопасности.
- Формат. На проде —
JSON, чтобы машинам удобно было. Локально можешь текстовый формат включить, чтобы глаза не ебать.slogэто умеет.
Вот и вся философия. Не усложняй, но и не будь примитивным, как этот Герасим из рассказа, который только "Муму" и мог сказать.