Логирование
Назначение
В YDB осуществляется структурированное логирование происходящих событий.
Структурированность подразумевает, что в логе (журнале работы) текстовое описание событий отделено от параметров этих событий, а сами значения параметров хранятся и обрабатываются независимо друг от друга. Например, если произошла ошибка чтения файла, то её описание — это текст вида "Ошибка чтения файла", а параметры — это путь к файлу, код системной ошибки, текстовое описание системной ошибки и т.п.
Разделение описания событий и их параметров впоследствии может быть использовано для эффективного поиска сообщений, сбора статистики, оптимизированного хранения журнала и прочих задач.
Базовые средства записи сообщений в лог
В большинстве случаев при записи сообщений в лог выполняются следующие условия:
- Исходный код содержится в некотором файле
.cpp(логирование из заголовочных файлов поддерживается, но рассматривается далее). - В одном файле
.cppвсе сообщения пишутся от имени одного и того же компонента. Поэтому в начале файла (но после всех директив#include) должен быть определён макросYDB_LOG_THIS_FILE_COMPONENT, который задаёт код компонента для всего файла.
Примечание
Логирование в YDB предполагает, что система состоит из большого количества внутренних компонентов, за каждым из которых закреплён его уникальный код. C полным списком компонентов и их кодов можно ознакомиться на GitHub.
При записи сообщения в лог должен быть явно или неявно указан код компонента, от имени которого происходит запись этого сообщения. Впоследствии имя компонента всегда выводится в лог и может быть использовано для поиска или фильтрации содержимого лога.
Кроме того, для каждого компонента могут быть настроены свои параметры логирования.
Пример:
#define YDB_LOG_THIS_FILE_COMPONENT NKikimrServices::STATESTORAGE
Важно
Система сборки может объединить несколько файлов .cpp в одну единицу компиляции с помощью JOIN_SRCS, который подключает их через директивы #include. Поэтому в конце каждого исходного файла настоятельно рекомендуется отменять определение макроса:
#undef YDB_LOG_THIS_FILE_COMPONENT
В противном случае определение макроса YDB_LOG_THIS_FILE_COMPONENT может распространиться на несколько файлов исходного кода.
- Логирование происходит в ходе работы актора, и для работы с контекстом (в частности, отправки сообщений актору логирования) доступна переменная
NActors::TlsActivationContext.
Важно
Если хотя бы одно из трёх перечисленных условий не выполняется, то следует воспользоваться расширенными средствами логирования.
Далее в этом файле .cpp для логирования сообщений могут использоваться следующие макросы:
| Макрос | Уровень сообщения |
|---|---|
YDB_LOG_EMERG(message, ...values...) |
Возможен сбой системы (например, отказ кластера). |
YDB_LOG_ALERT(message, ...values...) |
Возможна деградация системы, компоненты системы могут выйти из строя. |
YDB_LOG_CRIT(message, ...values...) |
Критическое состояние. |
YDB_LOG_ERROR(message, ...values...) |
Некритическая ошибка. |
YDB_LOG_WARN(message, ...values...) |
Предупреждение, на которое следует отреагировать и исправить, если оно не временное. |
YDB_LOG_NOTICE(message, ...values...) |
Произошло событие, существенное для системы или пользователя. |
YDB_LOG_INFO(message, ...values...) |
Отладочная информация для сбора статистики. |
YDB_LOG_DEBUG(message, ...values...) |
Отладочная информация для разработчиков. |
YDB_LOG_TRACE(message, ...values...) |
Очень подробная отладочная информация. |
В аргументах вызова перечисленных макросов указываются следующие параметры:
message— текстовое сообщение;...values...— опциональные параметры. Каждый параметр указывается в виде пары{name, value}, гдеname— текстовая строка с названием параметра, аvalue— значение параметра.
Примеры записи сообщений в журнал:
- Сообщение без параметров:
YDB_LOG_INFO("Module started");
- Сообщение с параметрами:
YDB_LOG_ERROR("Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Динамическое определение уровня сообщения
В некоторых случаях уровень сообщения определяется динамически на этапе исполнения. Тогда следует использовать макрос, который принимает уровень сообщения в виде аргумента. Синтаксис макроса:
YDB_LOG(prio, message, ...values...);
Пример использования:
auto prio = NActors::NLog::PRI_ERROR;
...
YDB_LOG(prio, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Расширенные средства записи сообщений в лог
Ядро логирования
Наиболее низкоуровневым средством для записи сообщений в журнал является макрос YDB_LOG_CTX_COMP. Он проверяет необходимость логирования (согласно настройкам), формирует текстовое сообщение, прикрепляет к нему опциональные параметры и затем отправляет всю эту информацию актору логирования. Далее, актор логирования обрабатывает полученное сообщение согласно своим текущим настройкам (например, записывает в файл или syslog).
Примечание
Все остальные средства логирования (в том числе и базовые средства логирования) построены на основе макроса YDB_LOG_CTX_COMP.
Полный синтаксис макроса логирования выглядит следующим образом:
YDB_LOG_CTX_COMP(ctx, prio, comp, message, ...values...)
В аргументах вызова макроса указываются следующие параметры:
ctx— контекст исполнения актора (необходим для отправки сообщения актору логирования);prio— уровень логирования сообщения (соответствует уровням логирования);comp— идентификатор компонента;message— текстовое сообщение;...values...— опциональные параметры. Каждый параметр указывается в виде пары{name, value}, гдеname— текстовая строка с названием параметра, аvalue— значение параметра.
В примерах ниже EXAMPLE_COMP_CODE обозначает код компонента логирования. В рабочем коде вместо него укажите существующий компонент, например NKikimrServices::STATESTORAGE.
Примеры записи сообщения в журнал
- Сообщение без параметров:
YDB_LOG_CTX_COMP(ctx, PRI_INFO, EXAMPLE_COMP_CODE, "Module started");
- Сообщение с параметрами:
YDB_LOG_CTX_COMP(ctx, PRI_ERROR, EXAMPLE_COMP_CODE, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
В данном примере происходит запись сообщения с текстом Unable to open file и двумя параметрами: sourceFilePath (значение берётся из переменной filename) и errorCode (значение берётся из переменной err). Для передачи сообщения актору логирования используется контекст исполнения ctx, а в качестве кода компонента передаётся EXAMPLE_COMP_CODE.
Макрос YDB_LOG_CTX_COMP подразумевает динамическое определение уровня сообщения и его передачу в виде аргумента. По аналогии с базовыми средствами логирования, существует ряд макросов, которые не требуют передачи уровня сообщения в виде аргумента.
Макросы без указания уровня сообщения в виде аргумента
| Макрос | Уровень сообщения |
|---|---|
YDB_LOG_EMERG_CTX_COMP |
Возможен сбой системы (например, отказ кластера). |
YDB_LOG_ALERT_CTX_COMP |
Возможна деградация системы, компоненты системы могут выйти из строя. |
YDB_LOG_CRIT_CTX_COMP |
Критическое состояние. |
YDB_LOG_ERROR_CTX_COMP |
Некритическая ошибка. |
YDB_LOG_WARN_CTX_COMP |
Предупреждение, на которое следует отреагировать и исправить, если оно не временное. |
YDB_LOG_NOTICE_CTX_COMP |
Произошло событие, существенное для системы или пользователя. |
YDB_LOG_INFO_CTX_COMP |
Отладочная информация для сбора статистики. |
YDB_LOG_DEBUG_CTX_COMP |
Отладочная информация для разработчиков. |
YDB_LOG_TRACE_CTX_COMP |
Очень подробная отладочная информация. |
Перечисленные в таблице макросы не требуют указания аргумента prio.
Пример:
YDB_LOG_ERROR_CTX_COMP(ctx, EXAMPLE_COMP_CODE, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
На основе макроса YDB_LOG_CTX_COMP работает множество других макросов, рассматриваемых далее. Их названия строятся по следующей схеме:
- Имя макроса всегда начинается с
YDB_LOG. - Если макрос не требует указания уровня сообщения, то к имени макроса через символ подчеркивания добавляется название уровня сообщения.
- Если макрос требует указания контекста исполнения, то к имени макроса добавляется
_CTX. - Если макрос требует указания компонента, то к имени макроса добавляется
_COMP.
Использование стандартного контекста исполнения
В большинстве случаев для передачи сообщений актору логирования используется контекст исполнения, доступный через глобальную переменную NActors::TlsActivationContext. Для того, чтобы исходные тексты не были визуально перегружены обращениями к этой глобальной переменной, введён дополнительный макрос YDB_LOG_COMP, который всегда использует NActors::TlsActivationContext и не требует указания параметра CTX.
Пример:
YDB_LOG_COMP(PRI_ERROR, EXAMPLE_COMP_CODE, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Примечание
Построение имени макроса YDB_LOG_COMP полностью укладывается в базовый принцип именования макросов, рассмотренный в ядре логирования.
В этом примере используется стандартный контекст исполнения, а логирование происходит от имени компонента с кодом EXAMPLE_COMP_CODE.
Макросы без указания уровня сообщения в виде аргумента
| Макрос | Уровень сообщения |
|---|---|
YDB_LOG_EMERG_COMP |
Возможен сбой системы (например, отказ кластера). |
YDB_LOG_ALERT_COMP |
Возможна деградация системы, компоненты системы могут выйти из строя. |
YDB_LOG_CRIT_COMP |
Критическое состояние. |
YDB_LOG_ERROR_COMP |
Некритическая ошибка. |
YDB_LOG_WARN_COMP |
Предупреждение, на которое следует отреагировать и исправить, если оно не временное. |
YDB_LOG_NOTICE_COMP |
Произошло событие, существенное для системы или пользователя. |
YDB_LOG_INFO_COMP |
Отладочная информация для сбора статистики. |
YDB_LOG_DEBUG_COMP |
Отладочная информация для разработчиков. |
YDB_LOG_TRACE_COMP |
Очень подробная отладочная информация. |
Пример:
YDB_LOG_ERROR_COMP(EXAMPLE_COMP_CODE, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Использование компонента по умолчанию
Используется тот же механизм, что и в базовых средствах логирования:
- В начале файла (но после всех директив
#include) должен быть определён макросYDB_LOG_THIS_FILE_COMPONENT. Он задаёт код компонента для всего файла.
Важно
Система сборки может объединить несколько файлов .cpp в одну единицу компиляции с помощью JOIN_SRCS, который подключает их через директивы #include. Поэтому в конце каждого исходного файла настоятельно рекомендуется отменять определение макроса:
#undef YDB_LOG_THIS_FILE_COMPONENT
В противном случае определение макроса YDB_LOG_THIS_FILE_COMPONENT может распространиться на несколько файлов исходного кода.
- Далее в файле должны использоваться макросы логирования, не требующие указания кода компонента. Они аналогичны уже рассмотренным ранее, но их имя не содержит в себе строку
_COMP.
Пример:
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
YDB_LOG_CTX(ctx, PRI_ERROR, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Макросы без указания уровня сообщения в виде аргумента
| Макрос | Уровень сообщения |
|---|---|
YDB_LOG_EMERG_CTX |
Возможен сбой системы (например, отказ кластера). |
YDB_LOG_ALERT_CTX |
Возможна деградация системы, компоненты системы могут выйти из строя. |
YDB_LOG_CRIT_CTX |
Критическое состояние. |
YDB_LOG_ERROR_CTX |
Некритическая ошибка. |
YDB_LOG_WARN_CTX |
Предупреждение, на которое следует отреагировать и исправить, если оно не временное. |
YDB_LOG_NOTICE_CTX |
Произошло событие, существенное для системы или пользователя. |
YDB_LOG_INFO_CTX |
Отладочная информация для сбора статистики. |
YDB_LOG_DEBUG_CTX |
Отладочная информация для разработчиков. |
YDB_LOG_TRACE_CTX |
Очень подробная отладочная информация. |
Пример:
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
YDB_LOG_ERROR_CTX(ctx, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Логирование в заголовочных файлах
Важно
Крайне не рекомендуется определять макрос YDB_LOG_THIS_FILE_COMPONENT в заголовочных файлах, поскольку это может приводить к следующим последствиям:
- Сложная логика определения кода компонента (не очевидно, из какого заголовочного файла берётся определение
YDB_LOG_THIS_FILE_COMPONENT). - Ошибки при компиляции (в разных файлах макрос
YDB_LOG_THIS_FILE_COMPONENTможет быть определён по-разному).
Если в заголовочном файле необходимо логирование, то следует использовать макросы, требующие явного указания кода компонента:
YDB_LOG_CTX_COMP— требует явного указания уровня сообщения, контекста исполнения и кода компонента.YDB_LOG_XXXX_CTX_COMP— не требует указания уровня сообщения.YDB_LOG_COMP— использует стандартный контекст исполнения.YDB_LOG_XXXX_COMP— использует стандартный контекст исполнения и не требует указания уровня сообщения.
Построение сообщений
Переиспользуемые наборы прикрепляемых параметров
Если один и тот же набор параметров прикрепляется к различным сообщениям, то во избежание дублирования кода можно заранее создать этот набор параметров, а затем его переиспользовать при логировании различных событий. Для этого предназначен макрос YDB_LOG_CREATE_MESSAGE. В качестве аргументов он принимает набор параметров (в виде пар {name, value}), а в качестве результата возвращает объект, который впоследствии может быть использован при отправке сообщений в журнал.
Пример
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
void MyFunction(const std::string& filename) {
...
// Create message parameters
auto context = YDB_LOG_CREATE_MESSAGE(
{"sourceFilePath", filename});
...
if (err != 0) {
YDB_LOG_ERROR("MyFunction failed",
context, // Use message parameters
{"errorCode", err});
return;
}
...
YDB_LOG_NOTICE("MyFunction succeeded",
context); // Use message parameters
}
Результат вызова макроса YDB_LOG_CREATE_MESSAGE является экземпляром класса TStructuredMessage. Этот экземпляр хранит набор входящих в него параметров, их имена, типы и значения. Экземпляры класса TStructuredMessage можно хранить в локальных и глобальных переменных, копировать, передавать и т.д.
Примечание
Можно считать, что класс TStructuredMessage является специализированным контейнером для хранения пар {name, value}, прикрепляемых к сообщениям журнала.
Поддерживаются операции по изменению ранее созданного набора параметров:
- Добавление и обновление.
Пример
YDB_LOG_UPDATE_MESSAGE(context,
{"socket", socketNum});
- Удаление параметров.
Пример
context.RemoveValue("socket");
Вложенные наборы параметров
Возможно создание вложенных наборов прикрепляемых параметров. Для этого при логировании сообщения (или создании другого набора параметров) в паре {name, value} в качестве значения должен быть указан ранее созданный набор значений (т.е. экземпляр класса TStructuredMessage). Тогда в итоговое сообщение (или создаваемый набор значений) будут автоматически добавлены все параметры из набора value, но к именам этих параметров будет добавлен префикс, указанный в name, с разделителем ..
Пример
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
void MyFunction() {
// Create message parameters
auto context = YDB_LOG_CREATE_MESSAGE(
{"operationName", "read"},
{"sourceFilePath", filename});
...
if (err != 0) {
YDB_LOG_ERROR("MyFunction failed",
{"details", context}, // Use message parameters
{"errorCode", err});
return;
}
...
}
В этом примере к сообщению с текстом MyFunction failed будут прикреплены параметры details.operationName, details.sourceFilePath и errorCode.
Контексты логирования
Формирование и переиспользование наборов прикрепляемых параметров неудобно в следующих случаях:
- Эти наборы нужно указывать при логировании большого количества сообщений.
- Эти наборы нужно передавать во все вызываемые функции, чтобы там указывать при логировании сообщений.
Можно упростить исходный код, если использовать контекст логирования — набор параметров, которые будут автоматически прикрепляться ко всем сообщениям, отправляемым в журнал в данном потоке исполнения. Для этого нужно вызвать макрос YDB_LOG_CREATE_CONTEXT и передать ему пары {name, value} (так же, как при отправке сообщений в журнал или создании переиспользуемых наборов), а также ранее созданные объекты TStructuredMessage. Добавление перечисленных в YDB_LOG_CREATE_CONTEXT параметров происходит при логировании в блоке кода, где вызван этот макрос, а также во вложенных блоках кода и всех вызываемых функциях.
Пример использования контекста логирования
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
void MyFunction() {
YDB_LOG_CREATE_CONTEXT(
{"sourceFilePath", filename});
...
if (errorCode != 0) {
YDB_LOG_ERROR("MyFunction failed", // К сообщению будут прикреплены параметры sourceFilePath и errorCode
{"errorCode", errorCode});
return;
}
...
YDB_LOG_NOTICE("MyFunction succeeded"); // К сообщению будет прикреплён параметр sourceFilePath
}
Параметр sourceFilePath будет прикрепляться ко всем сообщениям, логируемым в данном потоке исполнения в функции MyFunction и вызываемых ею функциях вплоть до выхода из функции MyFunction.
Контексты логирования организованы в виде стека. Если контекст логирования настроен, то он хранится в верхушке стека. У каждого потока исполнения свой стек контекстов логирования.
При вызове YDB_LOG_CREATE_CONTEXT происходят действия:
- Создаётся новый набор параметров (как и при вызове
YDB_LOG_CREATE_MESSAGE), содержащий все параметры из уже существующего контекста, а также параметры, непосредственно указанные в макросеYDB_LOG_CREATE_CONTEXT. - Новый набор параметров помещается на верхушку стека и, таким образом, становится текущим контекстом логирования в данном потоке исполнения.
- При записи сообщения в журнал (вызове макроса
YDB_LOG_CTX_COMPили т.п.) к сообщению автоматически прикрепляются параметры из верхушки стека контекстов логирования, а поверх этого набора записываются параметры, указанные в макросе логирования. - Верхушка стека будет автоматически удалена при выходе из текущего блока, и таким образом произойдёт возврат к предыдущему настроенному контексту.
Чтобы добавить параметры в текущий контекст или обновить их значения, необходимо использовать макрос YDB_LOG_UPDATE_CONTEXT:
YDB_LOG_UPDATE_CONTEXT({"requestId", requestId});
В макросе YDB_LOG_UPDATE_CONTEXT может быть указано сразу несколько параметров и их значений. Если указанные параметры уже существуют в контексте, то их значения будут обновлены. Если указанные параметры отсутствуют в контексте, то их значения будут добавлены в контекст.
Чтобы удалить параметры из текущего контекста, необходимо передать их имена в YDB_LOG_REMOVE_CONTEXT:
YDB_LOG_REMOVE_CONTEXT("requestId");
Макросы YDB_LOG_UPDATE_CONTEXT и YDB_LOG_REMOVE_CONTEXT могут вызываться сколько угодно раз. Они изменяют только текущий контекст (то есть верхушку стека контекстов), но не создают новых контекстов и никак не затрагивают другие контексты, расположенные ниже по стеку контекстов.
Рекомендации по наполнению логов
Безопасность
Запрещается записывать в лог пароли, токены доступа, ключи шифрования, строки подключения и персональные данные. Если значение необходимо для диагностики, то перед передачей в макрос необходимо удалить чувствительную часть, замаскировать её или заменить необратимым хешем.
Стиль кодирования
Рекомендуется следующий стиль кодирования при использовании макросов логирования:
- Вся обязательная информация (контекст исполнения, код компонента, уровень сообщения и текст сообщения) располагается на одной строке.
- Каждая пара
{name, value}располагается на отдельной строке.
Пример записи сообщения с несколькими параметрами:
YDB_LOG_DEBUG("Handle TEvNodeWardenNotifyConfigMismatch",
{"clusterStateGeneration", ClusterStateGeneration},
{"msgGeneration", msgGeneration},
{"clusterStateGuid", ClusterStateGuid},
{"msgGuid", msgGuid});
Стиль текстовых сообщений
Рекомендуется следующий стиль написания текстов сообщений:
-
Текст является осмысленным (и желательно корректным) предложением на английском языке, говорящим о некотором событии внутри системы, при этом:
- текст начинается с заглавной буквы;
- если текст представляет собой отдельное предложение, то точка в конце сообщения не ставится;
- если текст состоит из нескольких предложений, то они разделяются точками, но после последнего предложения точка не ставится.
-
Текст может содержать имена классов и функций. Если актор логирует факт получения некоторого сообщения, то хорошей практикой является логирование с текстом
Handle <имя класса-события>. -
Сообщения являются фиксированным текстом без динамически формируемых фрагментов.
-
Вся динамическая информация, известная только на момент исполнения, должна помещаться в виде прикрепляемых параметров.
Для согласования текстов сообщений и названий параметров существует простое эмпирическое правило:
Если взять текст сообщения и к нему добавить значения параметров в виде
(name=value, name=value, ... ), то должно получиться осмысленное, законченное, однозначное и понятное человеку текстовое сообщение.
Пример согласованных текста сообщения и названий параметров
YDB_LOG_ERROR_CTX(ctx, "Unable to open file",
{"sourceFilePath", filename},
{"errorCode", err});
Предполагаемое описание события выглядит так (параметры сообщения перечисляются в алфавитном порядке):
Unable to open file (errorCode = ..., sourceFilePath = ...)
Общие рекомендации по именованию отдельных параметров
Стиль именования отдельных параметров строится на двух базовых принципах:
- В контексте конкретного сообщения значение параметра должно интерпретироваться просто и однозначно.
YDB_LOG_ERROR("Response timeout elapsed",
{"nodeHostName", hostName},
{"requestNum", requestNum},
{"waitAtPosixTime", startTime},
{"timeoutMs", timeout});
YDB_LOG_ERROR("Response timeout elapsed",
{"node", hostName},
{"request", requestNum},
{"wait", startTime},
{"timeout", timeout});
Если использовать упомянутое ранее правило согласования текстов сообщений и названий параметров, то получится предложение Response timeout elapsed (node=<строка>, request=<число>, wait=<число>, timeout=<число>), где будет непонятно: значение node — это имя хоста или некоторое внутреннее название-идентификатор узла? Значение request — это порядковый номер или числовой идентификатор? Значение wait — что это такое и как интерпретировать это значение? В каких единицах задано значение wait?
- Семантически близкие параметры должны иметь одинаковые названия, поскольку различное именование одних и тех же понятий затрудняет поиск записей в логах. Например, нежелательно в одних сообщениях идентификатор транзакции обозначить как
txId, а в других — какtransactionId.
YDB_LOG_INFO("Started transaction",
{"txId", transactionId});
...
YDB_LOG_INFO("Transaction modifies table",
{"queryId", queryId},
{"txId", transactionId});
...
YDB_LOG_INFO("Query commits transaction",
{"queryId", queryId},
{"txId", transactionId});
...
YDB_LOG_INFO("Started transaction",
{"id", transactionId});
...
YDB_LOG_INFO("Transaction modifies table",
{"id", queryId},
{"transactionId", transactionId});
...
YDB_LOG_INFO("Query commits transaction",
{"id", queryId},
{"txId", transactionId});
...
В этом примере идентификатор транзакции записывается в параметры с разными именами (id, transactionId и txId), и в то же время параметр с названием id каждый раз хранит идентификаторы совершенно разных сущностей.
- Названия параметров пишутся в
camelCase.
Частные рекомендации по именованию отдельных параметров
Далее перечисляются рекомендации, направленные на выполнение двух указанных выше принципов:
- Нежелательно в качестве названий параметров использовать отдельные общеупотребимые слова (
id,item,value,path,child,parent,min,max, и т.п.) — это осложняет поиск записей в логах. Например, словоidобозначает идентификатор, но при этом совершенно непонятно, к какой сущности он относится, хотя в контексте конкретного сообщения смысл этого параметра может быть предельно ясен.
YDB_LOG_INFO("Started transaction",
{"txId", ...});
...
YDB_LOG_INFO("Received query",
{"queryId", ...});
...
YDB_LOG_INFO("Sent request to actor",
{"actorId", ...});
YDB_LOG_INFO("Started transaction",
{"id", ...});
...
YDB_LOG_INFO("Received query",
{"id", ...});
...
YDB_LOG_INFO("Sent request to actor",
{"id", ...});
- Названия параметров должны состоять из нескольких слов, причём главным должно быть последнее слово, а каждое предшествующее должно уточнять смысл последующего. Например:
actorId— идентификатор актора;operationId— идентификатор операции;operationName— название операции;operationSessionId— идентификатор сессии, в рамках которой выполняется операция.
YDB_LOG_INFO("Copy data to another shard",
{"srcShardId", ...},
{"dstShardId", ...});
YDB_LOG_INFO("Copy data to another shard",
{"srcId", ...},
{"dstShard", ...});
Здесь непонятно ни идентификатором какой сущности является параметр srcId, ни как трактовать значение dstShard (это идентификатор, название или что-либо ещё).
- Правила обозначения отдельных сущностей и коллекций:
- отдельное упоминание имени сущности в единственном числе предполагает, что значение параметра содержит описание этой сущности. Например,
table— это описание таблицы (однако, в данном случае лучше добавлять суффикс, то естьtableIdявно указывает, что параметр содержит идентификатор таблицы,tableName— название таблицы,tableDesc— описание таблицы и т.д.); - отдельное упоминание имени сущности во множественном числе предполагает, что значение параметра содержит описание коллекции сущностей. Например,
tables— это описание нескольких таблиц (но не их количество); - отдельное упоминание имени сущности во множественном числе с добавлением
Countпредполагает, что значение параметра содержит количество элементов в коллекции. Например,tablesCount— количество таблиц; - нежелательно для обозначения количества элементов в коллекции к названию параметра вместо
CountдобавлятьSize. Например, по названию параметраtotalFilesSize— непонятно, речь идёт о количестве файлов или их суммарном размере.
YDB_LOG_INFO("Backup volumes progress",
{"currentVolumeNum", ...},
{"volumesCount", ...},
{"volumesSizeBytes", ...});
YDB_LOG_INFO("Backup volumes progress",
{"currentVolume", ...},
{"volumes", ...});
- Если в названии параметра используется общепринятая в YDB аббревиатура или термин, то её следует писать в том регистре, в котором принято. Примеры названия таких параметров:
VDiskId— идентификатор VDisk (пишется не какvdiskId);PDiskId— идентификатор PDisk (пишется не какpdiskId);- и т.д.
-
Допустимо использовать популярные сокращения (
idxвместоindex,msgвместоmessage,txвместоtransactionи т.д.). -
Если параметр содержит числовую величину, то желательно к названию параметра добавлять суффикс:
Id, если число является идентификатором (но не какID);- название единиц измерения (например, для объёмов памяти —
Byte,KByte,MByte, для времени —Ms,Sec,Min, для указания процентных долей чего-либо —Percentи так далее). Если используется нестандартная единица измерения, то желательно использовать предлогIn(например,bufferSizeInBlocks);
YDB_LOG_INFO("Table dump progress",
{"tableId", ...},
{"dumpedRecordsCount", ...},
{"dumpedSizeBytes", ...},
{"totalSizeBytes", ...});
YDB_LOG_INFO("Table dump progress",
{"table", ...},
{"dumpedRecords", ...},
{"dumpedSize", ...},
{"totalSize", ...});
- Если числовое значение параметра человеку удобнее анализировать не в десятичном виде, то лучше это значение указывать как символьную строку и дополнять общепринятыми символами (например, у шестнадцатеричного представления должен быть префикс
0x, а у двоичного — префикс '0b' и т.д.). Хорошей практикой является дополнение названия параметра суффиксомHexили т.п.
unsigned flags;
...
YDB_LOG_INFO("Invalid access rights to file",
{"filename", ...},
{"aclFlags", flags},
{"aclFlagsOct", ToOct(flags)}); // Make flags as "0XXXXX" string
unsigned flags;
...
YDB_LOG_INFO("Invalid access rights to file",
{"filename", ...},
{"aclFlags", flags});
- Если параметр содержит текстовую информацию, то желательно к названию параметра добавлять суффикс, позволяющий легче интерпретировать значение параметра (например,
Name,Desc,Json,Base64и т.п.). - Для именования параметров можно применять правила именования локальных переменных (например, для логических признаков следует в начало названия параметра добавлять
is,hasили т. п.).
Общепринятые названия параметров
Существуют общепринятые названия параметров, которые желательно использовать.
| Название | Содержимое параметра |
|---|---|
actorClassName |
Название класса, в котором реализован актор. |
actorStateName |
Состояние актора. |
backtrace |
Стек возникновения исключения (результат функции TBackTrace::FromCurrentException().PrintToString()). |
ev |
Описание сообщения, которое обрабатывает актор. |
evType |
Тип сообщения, которое обрабатывает актор. |
exception |
Описание произошедшего исключения C++. |
selfId |
Идентификатор актора, который обрабатывает сообщение. |
Методология обогащения логов контекстной информацией
Предлагается следующая методология для обогащения событий, которые пишет в журнал конкретный актор.
Шаг 1. В акторе должна быть реализована функция, которая формирует текущий контекст логирования в виде набора параметров, прикрепляемых к логируемым сообщениям.
Пример реализации
TStructuredMessage GetLogContext() const {
return YDB_LOG_CREATE_MESSAGE(
{"actorClassName", "TFileReader"},
{"selfId", SelfId()},
{"sourceFilePath", filename});
}
Вместо имени GetLogContext может использоваться GetLogPrefix или т.п. В принципе, это может быть даже не функция, а поле класса, создаваемое в конструкторе и изменяемое в ходе работы актора.
Шаг 2. Настройка контекста в "точках входа". У каждого актора есть небольшой набор функций, которые могут быть вызваны ядром акторной системы. Сюда входят:
- Функция
Bootstrap. - Функции обработки сообщений (ищутся по макросу
STATEFNили т.п.). - Прочие виртуальные функции, которые переопределяет актор (они легко ищутся по ключевому слову
override).
Далее в первой строке каждой такой функции с помощью макроса YDB_LOG_CREATE_CONTEXT должен настраиваться контекст логирования.
Пример настройки контекста
STATEFN(StateWork) {
YDB_LOG_CREATE_CONTEXT(GetLogContext());
switch (ev->GetTypeRewrite()) {
...
}
}