Ответ
Структурированное логирование — это подход к записи логов не в виде простого текста, а в формате с четкой структурой (чаще всего JSON или logfmt), где каждое сообщение представляет собой набор пар «ключ-значение».
Плюсы:
- Машиночитаемость: Структурированные логи легко парсятся и индексируются автоматизированными системами (ELK, Grafana Loki, Splunk, Datadog). Это упрощает поиск, фильтрацию и агрегацию данных.
- Контекстуальность: Позволяет добавлять к сообщению произвольные контекстные поля (
user_id,request_id,duration_ms), что делает логи гораздо более информативными для отладки и анализа. - Надежный поиск: Вместо поиска по подстроке в текстовых логах можно выполнять точные запросы по полям, например,
level=error AND user_id=123.
Минусы:
- Повышенный объем: JSON и другие форматы обычно занимают больше места, чем простой текст, что увеличивает затраты на хранение.
- Сложнее для быстрого чтения человеком: Необработанные JSON-логи могут быть менее читаемыми в консоли по сравнению с простым текстом. Однако многие инструменты предоставляют "красивый" вывод для разработки.
- Накладные расходы на производительность: Сериализация данных в JSON или другой формат требует дополнительных ресурсов CPU по сравнению с простой записью строки.
Примеры в Go:
Начиная с Go 1.21, в стандартной библиотеке появился пакет slog для структурированного логирования, который является предпочтительным выбором.
Пример с slog (стандартная библиотека):
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("Пользователь вошел в систему",
slog.String("username", "admin"),
slog.String("ip_address", "127.0.0.1"),
)
}
Вывод slog:
{"time":"2023-10-26T15:00:00.123Z","level":"INFO","msg":"Пользователь вошел в систему","username":"admin","ip_address":"127.0.0.1"}
Пример с logrus (популярная библиотека):
Примечание: logrus находится в режиме поддержки, новым проектам рекомендуется использовать slog или zerolog.
import "github.com/sirupsen/logrus"
func main() {
log := logrus.New()
log.SetFormatter(&logrus.JSONFormatter{})
log.WithFields(logrus.Fields{
"user": "admin",
"ip": "127.0.0.1",
}).Info("User logged in")
} Ответ 18+ 🔞
А, структурированное логирование, говоришь? Ну это ж, блядь, как раз тот случай, когда из простой хуйни делают целую науку, но, сука, науку полезную!
Смотри, раньше-то мы логи писали как бог на душу положит: строка, да и всё. "Ошибка какая-то, нахуй". А теперь, понимаешь, надо чтобы не только человек, но и какая-нибудь тупая машина в ELK могла это прочитать и сказать: "Э, сабака сука, вот тут юзер admin с айпишником 127.0.0.1 в 15:00:00.123 нас ебал!"
Плюсы, блядь, очевидные:
- Для машин — мёд: Всё в полях, всё по ключам. Не надо регулярками выковыривать, как гланды. Закинул в систему — она сама всё разложила, проиндексировала и говорит: "Ищи, чё надо?". Хочешь все ошибки за сегодня? Пожалуйста. Хочешь логи конкретного юзера
user_id=123? Да похуй, вот тебе. - Контекст, ёпта: Раньше в логе было "запрос упал". И хуй поймёшь — чей запрос, на какую хуйню, сколько длился. А теперь можно прилепить к сообщению любые поля:
request_id,duration_ms,endpoint. Сразу видно, где собака зарыта, а где просто мартышлюшка накосячила. - Поиск, который не сосёт: Вместо того чтобы
grep'ом по тексту шариться (а там ещё и формат сообщения мог измениться, блядь!), ты ищешь по полям.level=error AND service=auth. Пиздец как удобно.
Но и минусы, конечно, есть, куда ж без них:
- Места жрёт, как не в себя: JSON — он же, сука, жирный! Каждое сообщение обрастает кавычками, скобками, названиями полей. Объём логов вырастает в разы, а хранение — это деньги, блядь.
- Человеку читать — пиздец: Сырой JSON в консоли — это же просто стена текста, глаза сломаешь. Хотя для разработки есть форматтеры, которые это всё красиво раскрашивают.
- Производительность чуть сосёт: Сериализовать данные в JSON — это не просто строку склеить, тут процессор поработать должен. На высоких нагрузках это может стать бутылочным горлышком, если, конечно, ты не Яндекс, а то у них там овердохуища всего.
Вот, смотри, как в Go с версии 1.21 это делается нахуй правильно, через новый slog:
import (
"log/slog"
"os"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
logger.Info("Пользователь вошел в систему",
slog.String("username", "admin"),
slog.String("ip_address", "127.0.0.1"),
)
}
И на выходе получится такая вот красота, от которой любая система мониторинга просто обоссытся от счастья:
{"time":"2023-10-26T15:00:00.123Z","level":"INFO","msg":"Пользователь вошел в систему","username":"admin","ip_address":"127.0.0.1"}
А раньше-то, блядь, использовали logrus (и многие до сих пор используют, хотя он уже, считай, на пенсии):
import "github.com/sirupsen/logrus"
func main() {
log := logrus.New()
log.SetFormatter(&logrus.JSONFormatter{})
log.WithFields(logrus.Fields{
"user": "admin",
"ip": "127.0.0.1",
}).Info("User logged in")
}
Короче, суть в чём: если пишешь что-то серьёзнее "Hello, world", то структурированные логи — это не прихоть, а необходимость. Иначе потом, когда всё накроется медным тазом, будешь как Герасим из рассказа: стоять, мычать "Муму" и нихуя не понимать, где искать причину.