-
timestamp: Время в UTC
- Время в UTC для синхронизации всех записей в логах
-
app_name: Имя приложения
- Нужен для идентификакции и возможности фильтрации по имени приложения
-
app_version: Версия приложения
- Нужен для определения версии приложения
-
hostname: Имя хоста на котором запущен сервис
- Имя хоста/ноды, на котором работает приложение, нужен для быстрой возможности определения общих проблем из-за инфраструктуры
-
request_id: ID запроса от пользователя, либо для запросов генерируемых сервисом,
-
Уникальный, и генерируется один раз на входе первым же приложением/балансировщиком, в который прилетел запрос без заголовка X-Request-Id
-
Request ID в UUID hex, с дефисами в формате 8-4-4-4-12
-
Request ID не должен изменяться сервисом, даже если пришел не в правильном формате
-
Нужен для трасировки запроса по всему стеку приложений
-
-
level: Log level: trace, debug, info, warn, error, critical
- Нужно синхронизировать понятия уровней логирования для каждого из сервисов, для того чтобы иметь возможность быстро обогащать логи дополнительной, требуемой, информацией для исследования проблем
-
uid: User UID: UUID с дефисами в формате 8-4-4-4-12
-
UID не должен изменяться сервисом, даже если пришел не в правильном формате
-
Нужен для поиска проблем у конкретного пользователя, а так же для подсчета скопа пользователей которые могли быть затронуты исследуемым инцидентом
-
-
user_agent: User Agent
-
передается в заголовке User-Agent
-
Каждый сервис проставляет свой собственный, уникальный для каждого отдельного приложения user agent
-
Каждый сервер должен кастомизировать свой user-agent заголовок, чтобы сервисы в которые приходят запросы от него могли четко идентифицировать потребителя
-
-
client_ip: Client IP Address
-
Обычно проставляется балансировщиком в заголовок x-forwarded-for
-
Должен быть проброшен через все сервисы, для того чтобы определить откуда пришел проблемный запрос на всех уровнях архитектуры, а так же для возможности определения проблемных геозон где пользователи могут испытывать проблемы
-
-
message: Тело сообщения
-
http_method: Request HTTP Method
- Тип запроса
-
http_status: HTTP статус ответа
-
requested_path: Request Path (relative)
- uri запроса не включающий в себя схему и хост
В общем случае, логи у сервиса делятся на 2 типа:
-
Логи отображающие внешние события, влияющие не только на этот сервис (запросы, ответы на запросы)
-
Логи отображающие внутренние события, влияющие только на этот сервис
В случаях, когда в сервис поступают запросы из вне, или когда сервису нужно сгенерировать запросы в свои зависимости для получения дополнительной информации, нужно иметь возможность отделить запросы и ответы друг от друга.
Для этого нужно обогатить логи следующими параметрами:
-
Пришел запрос в сервис:
"type": ->
-
Отдаем ответ:
"type": <-
-
Создаем запрос в зависимость:
"type": =>>
-
Получаем ответ от зависимости:
"type": <<=
Формат записи обычного лога, не относящегося к внутренним событиям:
{"timestamp":TIMESTAMP, "level":LEVEL, "message":MESSAGE, app_name":app_name, "app_version":app_version, "hostname":hostname, "extras": EXTRAS}
-
где EXTRAS, это строка с дополнительными данными, записанная в формате:
"extras": "<key>=<value>,<key>=<value> …"
-
так же в эти логи могут быть добавлены добавлены дополнительные поля, если лог относится к ходу обработки внешнего события:
"request_id":request_id, "uid":uid, "user_agent":user_agent, "client_ip":client_ip, "path":path
Формат записи лога относящегося к внешним событиям:
{"timestamp":TIMESTAMP, "level":LEVEL, "type":"->", "message":MESSAGE, app_name":app_name, "app_version":app_version, "hostname":hostname, "request_id":request_id, "uid":uid, "user_agent":user_agent, "client_ip":client_ip, "path":path, "extras": EXTRAS}
{"timestamp":TIMESTAMP, "level":LEVEL, "type":"=>>", "http_method":<method>, "requested_path":<requested_path>, "message":MESSAGE, app_name":app_name, "app_version":app_version, "hostname":hostname, "request_id":request_id, "uid":uid, "user_agent":user_agent, "client_ip":client_ip, "path":path, "extras": EXTRAS}
{"timestamp":TIMESTAMP, "level":LEVEL, "type":"<<=", "http_status":<http status>, "http_method":<method>, "requested_path":<requested_path>, "message":MESSAGE, app_name":app_name, "app_version":app_version, "hostname":hostname, "request_id":request_id, "uid":uid, "user_agent":user_agent, "client_ip":client_ip, "path":path, "extras": EXTRAS}
{"timestamp":TIMESTAMP, "level":LEVEL, "type":"<-", "http_status":<http status>, "http_method":<method>, "requested_path":<requested_path>, "message":MESSAGE, app_name":app_name, "app_version":app_version, "hostname":hostname, "request_id":request_id, "uid":uid, "user_agent":user_agent, "client_ip":client_ip, "path":path, "extras": EXTRAS}
-
в данном случае поля
"type","message","app_name","app_version","hostname","request_id","uid","user_agent","client_ip","path"
являются обязательными -
где EXTRAS, это строка с дополнительными данными, записанная в формате:
"extras": "<key>=<value>,<key>=<value> …"
Если логи пушатся в ELK не путем сбора логов из stdout, а например прямой записью через gelf, или из файла логирования, то в stdout было бы здорово писать логи в более человеко читаемом формате.
Формат записи логов для внешних событий:
-
Пришел запрос в сервис: -> Start handle request
- Нужен для быстрого визуального определения типа записи в лог, а так же для различных систем фильтрации
-
Посылаем ответ: <- Response <SUCCESS|PARTIAL|ERROR>
- Нужно для того чтобы быстро понять/отфильтровать был ли ответ сформированный сервисом успешным
-
Делаем запрос: =>> Making request to
- Определение запроса создаваемый самим сервисом в его зависимости
-
Получаем ответ: <<= Got response for
- Определение успешности ответа от зависимостей сервиса на заданный им запрос
- Определение успешности ответа от зависимостей сервиса на заданный им запрос
Важное пояснение, относительно этого пункта:
сочетания символов:
" > ", " < ", " =>> ", " <<= "
играют важную роль в быстром парсинге лога глазами, и их присутствие необходимо в описываемых выше случаях логирования.
Формат записи обычного лога, не относящегося к внешним, в stdout:
TIMESTAMP LEVEL MESSAGE APP_NAME APP_VERSION HOSTNAME EXTRAS
-
где EXTRAS:
<key>=<value> <key>=<value> ...
- Все поля кроме message и extras должны быть одной длины
-
так же в эти логи могут быть добавлены добавлены дополнительные поля, если лог относится к ходу обработки внешнего события:
REQUEST_ID UID USER_AGENT CLIENT_IP PATH
Формат записи сообщения на событие из вне в stdout:
TIMESTAMP LEVEL -> Start handle request {MESSAGE} APP_NAME REQUEST_ID APP_VERSION HOSTNAME UID USER_AGENT CLIENT_IP PATH EXTRAS
TIMESTAMP LEVEL =>> Making request to <method> <url> {MESSAGE} APP_NAME REQUEST_ID APP_VERSION HOSTNAME UID USER_AGENT CLIENT_IP PATH EXTRAS
TIMESTAMP LEVEL <<= Got response <http status> for <method> <url> {MESSAGE} APP_NAME REQUEST_ID APP_VERSION HOSTNAME UID USER_AGENT CLIENT_IP PATH EXTRAS
TIMESTAMP LEVEL <- Response <http status> <SUCCESS|PARTIAL|ERROR> {MESSAGE} APP_NAME REQUEST_ID APP_VERSION HOSTNAME UID USER_AGENT CLIENT_IP PATH EXTRAS
-
где перемменные объявленные в {} могут не существовать;
EXTRAS: <key>=<value> <key>=<value> ...
-
Все поля кроме message и extras должны быть одной длины
-
в данном случае поля
REQUEST_ID UID USER_AGENT CLIENT_IP PATH
являются обязательными