Небольшая заметка + чеклист о том, чего нельзя забывать при работе с логгером.
Вот вам чеклист, копируйте в Trello, Jira, Notion и куда угодно
- Все пункты (ссылка)
- Запросы. Middleware
- Запросы. Request
- Запросы. Response (optional)
- Запросы. Time profiler
- Запросы. Statuses
- Запросы. Error as Logger.error
- Логируйте в отдельном процессе
- Не стесняйтесь логировать все
- Настройте Skipper
- Добавьте кастомные статусы
- Используйте DI
- Всегда используйте внешние системы
- Docker + stdout
- Синхронизируйте время
- Настройте оповещения
- UserID
- TraceID
- Frontend
Запросы
Не важно откуда и как (HTTP, MQTT, WS, GQL, etc.) вы всегда должны логгировать запрос на входе и итоговый ответ.
Запросы. Middleware – Лучше всего выносить логику логирования в срез AOP (в более человеческом варианте «выносить в middleware»).
Запросы. Request – Обязательно фиксируйте что вам прислали
Запросы. Response (optional) – Ответ необязательно фиксировать (он может быть слишком большим)
Запросы. Time profiler – Фиксируйте время, которое занял запрос
Запросы. Statuses – Логируйте все статусы
Запросы. Error as Logger.error – Логируйте ошибку как тип «Ошибка»
Логируйте в отдельном процессе
Всегда убеждайтесь, что ваш логгер или библиотека делают логи в отдельном процессе, чтобы не срать в основной процесс.
Не стесняйтесь логировать все
Разработчики слишком часто переживают: «Что можно логгировать а что нельзя? В каких слоях ставить логи, а в каких нет?» – я считаю, что можно логировать все и всегда, и впринципе, чем больше тем лучше.
Главное соблюдать «уровни логов» так, чтобы логи, которые могут быть не нужны на проде, в него и не попадали.
Настройте Skipper
Это функция, которая по определенному правилу (например содержанию запроса) будет пропускать входное или выходное логирование или какие-то отдельные поля.
Скорее всего, вы не захотите, чтобы в ваших логах лежали пароли пользователей, которые были залогинены из запросов на регистрацию и аутентификацию.
Добавьте кастомные статусы
Как у HTTP есть STATUS_CODE, так и вам советую создать отдельное поле со статус кодами и для логгера.
Описание статус кодов держите в Wiki.
Используйте DI
Очень распространенная тактика – логировать глобальным логгером.
Впринципе, не помню, чтобы это было большой проблемой, но для тестирование (как и все глобальное) это может создать некоторые неудобства.
Лучше использовать DI там, где это возможно.
Всегда используйте внешние системы
Сейчас существует миллиард систем для отправки логов.
Самая моя любимая – ELK (Elastic Search, Logstash, Kibana).
Моментально раскрывается в Docker и дает из коробки огромный и удобный функционал.
(22.06.20) Буду тестить ELK в Heroku, посмотрим как получится.
Если у вас Kube8, тогда вам нужен DataDog. Там еще и мониторинг, и баг-трекинг и трэйсинг в комплекте.
(22.06.20) Но еще мне советовали DataDog и для обычного Docker, чекну и напишу.
P.S. Впринципе stdout Google App Engine уже идет с мониторингом из коробки (Cloud Logging), но мне дико не заходит интерфейс и функционал.
Docker + stdout
Если уж заговорили про внешние системы логирования, то достаточно оптимальным будет следующий вариант:
Совмещаете stdout вашего приложения с Docker контейнером, а на stdout Docker контейнера навешиваете слушателя (FileBeats), который уже будет отправлять логи в ELK.
Вот здесь это неплохо описано.
Основные преимущества: (1) вам не нужно делать HTTP запроса из приложения, (2) приложение больше не отвечает за способы логирования, а значит при изменении логики логгирования (смена сервера / системы логирования / транспорта / т.д.) не придется трогать приложение.
Синхронизируйте время
Логи всегда привязаны ко времени, поэтому запомните:
(1) Указывайте время и сервера, и клиентов.
(2) Если пишете в fs, то проверьте правильность времени сервера.
(3) Если пишите во внешнюю систему проверьте, что там правильно указано время (сколько же мы однажды намучались из-за рассинхрона)
(4) Проверьте, в самом timestamp не должно быть поправки на локальное время.
Настройте оповещения
Slack, Email, SMS на крайний случай.
Без оповещений, логгирование бесполезно.
Правило оповещения «> Х запросов с 1-го IP»
Есть малюсенький шанс успеть отловить DDoS атаку до первых потерь, если поставить правило, которое сделает предупреждение слишком большого кол-ва запросов с 1-го IP.
Всегда используйте библиотеку для логгирования
Есть случаи, когда люди предпочитают писать свои логгеры, ссылаюсь на «легкость» реализации и считая использование библиотеки overkill.
Если вы почитаете что делает нормальная библиотека логирования, а еще лучше, посмотрите ее код, вы поймете, что ничего легкого в логгерах нет.
Поэтому упаси вас здравый смысл писать свое. Просто не надо. Сначала попробуйте библиотеку, потом уже решите что и как вы хотите «переписать».
Для Golang посоветую Logrus или Zap.
Для Nodejs – Winston.
Вот для пара удобных фич логгеров:
Во-первых, они идут с очень удобной переключалкой между транспортами логгирования, типа stdout / fs / внешними API (ELK, Sentry, Graylog и так далее) и при этом менять форматирование под нужный транспорт.
Во-вторых, если это поддерживает язык, то они будут логировать в отдельном треде / рутине / цикле и так далее, чтобы не срать в основной процесс + она сама освободит дескрипторы.
В-третьих, они могут автоматически добавлять timestamp к логам, брать данные из запроса и переформатировать их, форматировать
UserID
В 90% случаев запросы будут инициализированы юзерами, 90% важных запросов идут от аутентифицированных юзеров, обязательно логируйте их UserID.
Мы так очень часто выручали чат-поддержку и клиентов, когда знали кто конкретно какие запросы делает.
TraceID
Трассировка – утрированно, это способ прослеживания всего пути запроса через все система от инициатора запроса, в систему и все ее подсистемы и обратно к инициатору.
Начиная с самого источника действия (в случае с вебом, это запрос от клиента на сервер) добавляйте в метаданные поле «TraceID», значением которого будет uuid строка
Пример: в HTTP запрос добавляем headers: {"X-Trace-Id": "..."}
, а значением будет uuid строка headers: {"X-Trace-Id": "d6891392-e2a2-4139-bf15-7c0a3d8233b"}
.
И дальше абсолютно не важно через сколько миллиардов микросервисов пройдет запрос от клиента и обратно к нему, важно, что этот TraceID должен быть везде и всегда, чтобы в будущем, вы могли по нему отследить как запрос прошел от клиента, в вашу систему, поварился внутри и вернулся обратно.
Лайфхак. Чаще всего будет происходить ситуация при которой на внутреннии запросы захочется поставить свой TraceID, но при этом надо как-то связать его с TraceID из клиентского запроса. Для этого во все meta / headers всех запросов всегда добавляйте необязательное поле «OriginTraceID», куда вы будете добавлять TraceID оригинального запроса.
Frontend
Все тоже самое, что написано сверху, продублировать и для Frontend.
Люди почему-то очень стесняются делать логи на фронте, но в большинстве продуктов, в этом нет никакой проблемы.