---
metadata:
  - name: generator
    content: Diplodoc Platform v5.61.1
alternate:
  - https://ydb.tech/docs/ru/contributor/logging.md
  - href: https://ydb.tech/docs/ru/contributor/logging.md
    type: text/markdown
    title: Markdown version
  - href: https://ydb.tech/docs/ru/llms.txt
    rel: describedby
sourcePath: ru/core/contributor/logging.md
---
> **Documentation Index:** Fetch the complete configuration index at https://ydb.tech/docs/ru/llms.txt

# Логирование

## Назначение

В YDB осуществляется **структурированное логирование** происходящих событий.
Структурированность подразумевает, что в логе (журнале работы) текстовое описание событий отделено от параметров этих событий, а сами значения параметров хранятся и обрабатываются независимо друг от друга. Например, если произошла ошибка чтения файла, то её описание — это текст вида "Ошибка чтения файла", а параметры — это путь к файлу, код системной ошибки, текстовое описание системной ошибки и т.п.

Разделение описания событий и их параметров впоследствии может быть использовано для эффективного поиска сообщений, сбора статистики, оптимизированного хранения журнала и прочих задач.

## Базовые средства записи сообщений в лог {#simple-logging}

В большинстве случаев при записи сообщений в лог выполняются следующие условия:

1. Исходный код содержится в некотором файле `.cpp` (логирование из заголовочных файлов поддерживается, но [рассматривается далее](#log-in-header-file)).
2. В одном файле `.cpp` все сообщения пишутся от имени одного и того же компонента. Поэтому в начале файла (но после всех директив `#include`) должен быть определён макрос `YDB_LOG_THIS_FILE_COMPONENT`, который задаёт код компонента для всего файла.

{% note info %}

<!-- source: ru/contributor/_includes/log_components.md -->
Логирование в YDB предполагает, что система состоит из большого количества внутренних компонентов, за каждым из которых закреплён его уникальный код. C полным списком компонентов и их кодов можно ознакомиться [на GitHub](https://github.com/ydb-platform/ydb/blob/main/ydb/library/services/services.proto#L6).
<!-- endsource: ru/contributor/_includes/log_components.md -->

При записи сообщения в лог должен быть явно или неявно указан код компонента, от имени которого происходит запись этого сообщения. Впоследствии имя компонента всегда выводится в лог и может быть использовано для поиска или фильтрации содержимого лога.

Кроме того, для каждого компонента могут быть [настроены](https://ydb.tech/docs/ru/reference/configuration/log_config.md#entry-objects) свои параметры логирования.

{% endnote %}

Пример:

```cpp
#define YDB_LOG_THIS_FILE_COMPONENT NKikimrServices::STATESTORAGE
```

<!-- source: ru/contributor/_includes/undef-ydb-log-this-file-component.md -->
{% note warning %}

Система сборки может объединить несколько файлов `.cpp` в одну единицу компиляции с помощью `JOIN_SRCS`, который подключает их через директивы `#include`. Поэтому в конце каждого исходного файла настоятельно рекомендуется отменять определение макроса:

```cpp
#undef YDB_LOG_THIS_FILE_COMPONENT
```

В противном случае определение макроса `YDB_LOG_THIS_FILE_COMPONENT` может распространиться на несколько файлов исходного кода.

{% endnote %}
<!-- endsource: ru/contributor/_includes/undef-ydb-log-this-file-component.md -->

3. Логирование происходит в ходе работы [актора](https://ydb.tech/docs/ru/concepts/glossary.md#actor), и для работы с контекстом (в частности, отправки сообщений актору логирования) доступна переменная `NActors::TlsActivationContext`.

{% note warning %}

Если хотя бы одно из трёх перечисленных условий не выполняется, то следует воспользоваться [расширенными средствами логирования](#extended-logging).

{% endnote %}

Далее в этом файле `.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` — значение параметра.

Примеры записи сообщений в журнал:

1. Сообщение без параметров:

```cpp
YDB_LOG_INFO("Module started");
```

2. Сообщение с параметрами:

```cpp
YDB_LOG_ERROR("Unable to open file",
    {"sourceFilePath", filename},
    {"errorCode", err});
```

{% cut "Динамическое определение уровня сообщения" %}

В некоторых случаях уровень сообщения определяется динамически на этапе исполнения. Тогда следует использовать макрос, который принимает уровень сообщения в виде аргумента. Синтаксис макроса:

```cpp
YDB_LOG(prio, message, ...values...);
```

Пример использования:

```cpp
auto prio = NActors::NLog::PRI_ERROR;
...
YDB_LOG(prio, "Unable to open file",
    {"sourceFilePath", filename},
    {"errorCode", err});
```

{% endcut %}

## Расширенные средства записи сообщений в лог {#extended-logging}

### Ядро логирования

Наиболее низкоуровневым средством для записи сообщений в журнал является макрос `YDB_LOG_CTX_COMP`. Он проверяет необходимость логирования (согласно [настройкам](https://ydb.tech/docs/ru/reference/configuration/log_config.md)), формирует текстовое сообщение, прикрепляет к нему опциональные параметры и затем отправляет всю эту информацию актору логирования. Далее, актор логирования обрабатывает полученное сообщение согласно своим текущим настройкам (например, записывает в файл или syslog).

{% note info %}

Все остальные средства логирования (в том числе и [базовые средства логирования](#simple-logging)) построены на основе макроса `YDB_LOG_CTX_COMP`.

{% endnote %}

Полный синтаксис макроса логирования выглядит следующим образом:

```cpp
YDB_LOG_CTX_COMP(ctx, prio, comp, message, ...values...)
```

В аргументах вызова макроса указываются следующие параметры:

- `ctx` — контекст исполнения актора (необходим для отправки сообщения актору логирования);
- `prio` — уровень логирования сообщения (соответствует [уровням логирования](https://ydb.tech/docs/ru/reference/configuration/log_config.md#log-levels));
- `comp` — идентификатор компонента;
- `message` — текстовое сообщение;
- `...values...` — опциональные параметры. Каждый параметр указывается в виде пары `{name, value}`, где `name` — текстовая строка с названием параметра, а `value` — значение параметра.

В примерах ниже `EXAMPLE_COMP_CODE` обозначает код компонента логирования. В рабочем коде вместо него укажите существующий компонент, например `NKikimrServices::STATESTORAGE`.

{% cut "Примеры записи сообщения в журнал" %}

1. Сообщение без параметров:

```cpp
YDB_LOG_CTX_COMP(ctx, PRI_INFO, EXAMPLE_COMP_CODE, "Module started");
```

2. Сообщение с параметрами:

```cpp
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`.

{% endcut %}

Макрос `YDB_LOG_CTX_COMP` подразумевает динамическое определение уровня сообщения и его передачу в виде аргумента. По аналогии с базовыми средствами логирования, существует ряд макросов, которые не требуют передачи уровня сообщения в виде аргумента.

{% cut "Макросы без указания уровня сообщения в виде аргумента" %}

|Макрос|Уровень сообщения  |
|--|--|
|`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`.

Пример:

```cpp
YDB_LOG_ERROR_CTX_COMP(ctx, EXAMPLE_COMP_CODE, "Unable to open file",
    {"sourceFilePath", filename},
    {"errorCode", err});
```

{% endcut %}

На основе макроса `YDB_LOG_CTX_COMP` работает множество других макросов, рассматриваемых далее. Их названия строятся по следующей схеме:

1. Имя макроса всегда начинается с `YDB_LOG`.
2. Если макрос не требует указания уровня сообщения, то к имени макроса через символ подчеркивания добавляется название уровня сообщения.
3. Если макрос требует указания контекста исполнения, то к имени макроса добавляется `_CTX`.
4. Если макрос требует указания компонента, то к имени макроса добавляется `_COMP`.

### Использование стандартного контекста исполнения

В большинстве случаев для передачи сообщений актору логирования используется контекст исполнения, доступный через глобальную переменную `NActors::TlsActivationContext`. Для того, чтобы исходные тексты не были визуально перегружены обращениями к этой глобальной переменной, введён дополнительный макрос `YDB_LOG_COMP`, который всегда использует `NActors::TlsActivationContext` и не требует указания параметра `CTX`.

Пример:

```cpp
YDB_LOG_COMP(PRI_ERROR, EXAMPLE_COMP_CODE, "Unable to open file",
    {"sourceFilePath", filename},
    {"errorCode", err});
```

{% note info %}

Построение имени макроса `YDB_LOG_COMP` полностью укладывается в базовый принцип именования макросов, рассмотренный в [ядре логирования](#extended-logging).

{% endnote %}

В этом примере используется стандартный контекст исполнения, а логирование происходит от имени компонента с кодом `EXAMPLE_COMP_CODE`.

{% cut "Макросы без указания уровня сообщения в виде аргумента" %}

|Макрос|Уровень сообщения  |
|--|--|
|`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`  | Очень подробная отладочная информация.|

Пример:

```cpp
YDB_LOG_ERROR_COMP(EXAMPLE_COMP_CODE, "Unable to open file",
    {"sourceFilePath", filename},
    {"errorCode", err});
```

{% endcut %}

### Использование компонента по умолчанию

Используется тот же механизм, что и в [базовых средствах логирования](#simple-logging):

1. В начале файла (но после всех директив `#include`) должен быть определён макрос `YDB_LOG_THIS_FILE_COMPONENT`. Он задаёт код компонента для всего файла.

<!-- source: ru/contributor/_includes/undef-ydb-log-this-file-component.md -->
{% note warning %}

Система сборки может объединить несколько файлов `.cpp` в одну единицу компиляции с помощью `JOIN_SRCS`, который подключает их через директивы `#include`. Поэтому в конце каждого исходного файла настоятельно рекомендуется отменять определение макроса:

```cpp
#undef YDB_LOG_THIS_FILE_COMPONENT
```

В противном случае определение макроса `YDB_LOG_THIS_FILE_COMPONENT` может распространиться на несколько файлов исходного кода.

{% endnote %}
<!-- endsource: ru/contributor/_includes/undef-ydb-log-this-file-component.md -->

2. Далее в файле должны использоваться макросы логирования, не требующие указания кода компонента. Они аналогичны уже рассмотренным ранее, но их имя не содержит в себе строку `_COMP`.

Пример:

```cpp
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
YDB_LOG_CTX(ctx, PRI_ERROR, "Unable to open file",
    {"sourceFilePath", filename},
    {"errorCode", err});
```

{% cut "Макросы без указания уровня сообщения в виде аргумента" %}

|Макрос|Уровень сообщения  |
|--|--|
|`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`  | Очень подробная отладочная информация.|

Пример:

```cpp
#define YDB_LOG_THIS_FILE_COMPONENT EXAMPLE_COMP_CODE
...
YDB_LOG_ERROR_CTX(ctx, "Unable to open file",
    {"sourceFilePath", filename},
    {"errorCode", err});
```

{% endcut %}

### Логирование в заголовочных файлах {#log-in-header-file}

{% note warning %}

Крайне не рекомендуется определять макрос `YDB_LOG_THIS_FILE_COMPONENT` в заголовочных файлах, поскольку это может приводить к следующим последствиям:

1. Сложная логика определения кода компонента (не очевидно, из какого заголовочного файла берётся определение `YDB_LOG_THIS_FILE_COMPONENT`).
2. Ошибки при компиляции (в разных файлах макрос `YDB_LOG_THIS_FILE_COMPONENT` может быть определён по-разному).

{% endnote %}

Если в заголовочном файле необходимо логирование, то следует использовать макросы, требующие явного указания кода компонента:

1. `YDB_LOG_CTX_COMP` — требует явного указания уровня сообщения, контекста исполнения и кода компонента.
2. `YDB_LOG_XXXX_CTX_COMP` — не требует указания уровня сообщения.
3. `YDB_LOG_COMP` — использует стандартный контекст исполнения.
4. `YDB_LOG_XXXX_COMP` — использует стандартный контекст исполнения и не требует указания уровня сообщения.

## Построение сообщений

### Переиспользуемые наборы прикрепляемых параметров

Если один и тот же набор параметров прикрепляется к различным сообщениям, то во избежание дублирования кода можно заранее создать этот набор параметров, а затем его переиспользовать при логировании различных событий. Для этого предназначен макрос `YDB_LOG_CREATE_MESSAGE`. В качестве аргументов он принимает набор параметров (в виде пар `{name, value}`), а в качестве результата возвращает объект, который впоследствии может быть использован при отправке сообщений в журнал.

{% cut "Пример" %}

```cpp
#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
}
```

{% endcut %}

Результат вызова макроса `YDB_LOG_CREATE_MESSAGE` является экземпляром класса `TStructuredMessage`. Этот экземпляр хранит набор входящих в него параметров, их имена, типы и значения. Экземпляры класса `TStructuredMessage` можно хранить в локальных и глобальных переменных, копировать, передавать и т.д.

{% note info %}

Можно считать, что класс `TStructuredMessage` является специализированным контейнером для хранения пар `{name, value}`, прикрепляемых к сообщениям журнала.

{% endnote %}

Поддерживаются операции по изменению ранее созданного набора параметров:

1. Добавление и обновление.

{% cut "Пример" %}

```cpp
YDB_LOG_UPDATE_MESSAGE(context,
    {"socket", socketNum});
```

{% endcut %}

2. Удаление параметров.

{% cut "Пример" %}

```cpp
context.RemoveValue("socket");
```

{% endcut %}

### Вложенные наборы параметров

Возможно создание вложенных наборов прикрепляемых параметров. Для этого при логировании сообщения (или создании другого набора параметров) в паре `{name, value}` в качестве значения должен быть указан ранее созданный набор значений (т.е. экземпляр класса `TStructuredMessage`). Тогда в итоговое сообщение (или создаваемый набор значений) будут автоматически добавлены все параметры из набора `value`, но к именам этих параметров будет добавлен префикс, указанный в `name`, с разделителем `.`.

{% cut "Пример" %}

```cpp
#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`.

{% endcut %}

### Контексты логирования

Формирование и переиспользование наборов прикрепляемых параметров неудобно в следующих случаях:

1. Эти наборы нужно указывать при логировании большого количества сообщений.
2. Эти наборы нужно передавать во все вызываемые функции, чтобы там указывать при логировании сообщений.

Можно упростить исходный код, если использовать **контекст логирования** — набор параметров, которые будут автоматически прикрепляться ко всем сообщениям, отправляемым в журнал в данном потоке исполнения. Для этого нужно вызвать макрос `YDB_LOG_CREATE_CONTEXT` и передать ему пары `{name, value}` (так же, как при отправке сообщений в журнал или создании переиспользуемых наборов), а также ранее созданные объекты `TStructuredMessage`. Добавление перечисленных в `YDB_LOG_CREATE_CONTEXT` параметров происходит при логировании в блоке кода, где вызван этот макрос, а также во вложенных блоках кода и всех вызываемых функциях.

{% cut "Пример использования контекста логирования" %}

```cpp
#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`.

{% endcut %}

Контексты логирования организованы в виде стека. Если контекст логирования настроен, то он хранится в верхушке стека. У каждого потока исполнения свой стек контекстов логирования.

При вызове `YDB_LOG_CREATE_CONTEXT` происходят действия:

1. Создаётся новый набор параметров (как и при вызове `YDB_LOG_CREATE_MESSAGE`), содержащий все параметры из уже существующего контекста, а также параметры, непосредственно указанные в макросе `YDB_LOG_CREATE_CONTEXT`.
2. Новый набор параметров помещается на верхушку стека и, таким образом, становится текущим контекстом логирования в данном потоке исполнения.
3. При записи сообщения в журнал (вызове макроса `YDB_LOG_CTX_COMP` или т.п.) к сообщению автоматически прикрепляются параметры из верхушки стека контекстов логирования, а поверх этого набора записываются параметры, указанные в макросе логирования.
4. Верхушка стека будет автоматически удалена при выходе из текущего блока, и таким образом произойдёт возврат к предыдущему настроенному контексту.

Чтобы добавить параметры в текущий контекст или обновить их значения, необходимо использовать макрос `YDB_LOG_UPDATE_CONTEXT`:

```cpp
YDB_LOG_UPDATE_CONTEXT({"requestId", requestId});
```

В макросе `YDB_LOG_UPDATE_CONTEXT` может быть указано сразу несколько параметров и их значений. Если указанные параметры уже существуют в контексте, то их значения будут обновлены. Если указанные параметры отсутствуют в контексте, то их значения будут добавлены в контекст.

Чтобы удалить параметры из текущего контекста, необходимо передать их имена в `YDB_LOG_REMOVE_CONTEXT`:

```cpp
YDB_LOG_REMOVE_CONTEXT("requestId");
```

Макросы `YDB_LOG_UPDATE_CONTEXT` и `YDB_LOG_REMOVE_CONTEXT` могут вызываться сколько угодно раз. Они изменяют только текущий контекст (то есть верхушку стека контекстов), но не создают новых контекстов и никак не затрагивают другие контексты, расположенные ниже по стеку контекстов.

## Рекомендации по наполнению логов

### Безопасность

Запрещается записывать в лог пароли, токены доступа, ключи шифрования, строки подключения и персональные данные. Если значение необходимо для диагностики, то перед передачей в макрос необходимо удалить чувствительную часть, замаскировать её или заменить необратимым хешем.

### Стиль кодирования

Рекомендуется следующий стиль кодирования при использовании макросов логирования:

1. Вся обязательная информация (контекст исполнения, код компонента, уровень сообщения и текст сообщения) располагается на одной строке.
2. Каждая пара `{name, value}` располагается на отдельной строке.

Пример записи сообщения с несколькими параметрами:

```cpp
    YDB_LOG_DEBUG("Handle TEvNodeWardenNotifyConfigMismatch",
        {"clusterStateGeneration", ClusterStateGeneration},
        {"msgGeneration", msgGeneration},
        {"clusterStateGuid", ClusterStateGuid},
        {"msgGuid", msgGuid});
```

### Стиль текстовых сообщений

Рекомендуется следующий стиль написания текстов сообщений:

1. Текст является осмысленным (и желательно корректным) предложением на английском языке, говорящим о некотором событии внутри системы, при этом:

   - текст начинается с заглавной буквы;
   - если текст представляет собой отдельное предложение, то точка в конце сообщения не ставится;
   - если текст состоит из нескольких предложений, то они разделяются точками, но после последнего предложения точка не ставится.

2. Текст может содержать имена классов и функций. Если актор логирует факт получения некоторого сообщения, то хорошей практикой является логирование с текстом `Handle <имя класса-события>`.
3. Сообщения являются фиксированным текстом без динамически формируемых фрагментов.
4. Вся динамическая информация, известная только на момент исполнения, должна помещаться в виде прикрепляемых параметров.

Для согласования текстов сообщений и названий параметров существует простое эмпирическое правило:
> Если взять текст сообщения и к нему добавить значения параметров в виде `(name=value, name=value, ... )`, то должно получиться осмысленное, законченное, однозначное и понятное человеку текстовое сообщение.

{% cut "Пример согласованных текста сообщения и названий параметров" %}

```cpp
YDB_LOG_ERROR_CTX(ctx, "Unable to open file",
    {"sourceFilePath", filename},
    {"errorCode", err});
```

Предполагаемое описание события выглядит так (параметры сообщения перечисляются в алфавитном порядке):

```text
Unable to open file (errorCode = ..., sourceFilePath = ...)
```

{% endcut %}

### Общие рекомендации по именованию отдельных параметров

Стиль именования отдельных параметров строится на двух базовых принципах:

1. В контексте конкретного сообщения значение параметра должно интерпретироваться просто и однозначно.

{% list tabs %}

- Хорошо

```cpp
YDB_LOG_ERROR("Response timeout elapsed",
    {"nodeHostName", hostName},
    {"requestNum", requestNum},
    {"waitAtPosixTime", startTime},
    {"timeoutMs", timeout});
```

- Плохо

```cpp
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`?

{% endlist %}

2. Семантически близкие параметры должны иметь одинаковые названия, поскольку различное именование одних и тех же понятий затрудняет поиск записей в логах. Например, нежелательно в одних сообщениях идентификатор транзакции обозначить как `txId`, а в других — как `transactionId`.

{% list tabs %}

- Хорошо

```cpp
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});
...
```

- Плохо

```cpp
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` каждый раз хранит идентификаторы совершенно разных сущностей.

{% endlist %}

3. Названия параметров пишутся в `camelCase`.


### Частные рекомендации по именованию отдельных параметров

Далее перечисляются рекомендации, направленные на выполнение двух указанных выше принципов:

1. Нежелательно в качестве названий параметров использовать отдельные общеупотребимые слова (`id`, `item`, `value`, `path`, `child`, `parent`, `min`, `max`, и т.п.) — это осложняет поиск записей в логах. Например, слово `id` обозначает идентификатор, но при этом совершенно непонятно, к какой сущности он относится, хотя в контексте конкретного сообщения смысл этого параметра может быть предельно ясен.

{% list tabs %}

- Хорошо

```cpp
YDB_LOG_INFO("Started transaction",
    {"txId", ...});
...
YDB_LOG_INFO("Received query",
    {"queryId", ...});
...
YDB_LOG_INFO("Sent request to actor",
    {"actorId", ...});
```

- Плохо

```cpp
YDB_LOG_INFO("Started transaction",
    {"id", ...});
...
YDB_LOG_INFO("Received query",
    {"id", ...});
...
YDB_LOG_INFO("Sent request to actor",
    {"id", ...});
```

{% endlist %}

2. Названия параметров должны состоять из нескольких слов, причём главным должно быть последнее слово, а каждое предшествующее должно уточнять смысл последующего. Например:

- `actorId` — идентификатор актора;
- `operationId` — идентификатор операции;
- `operationName` — название операции;
- `operationSessionId` — идентификатор сессии, в рамках которой выполняется операция.

{% list tabs %}

- Хорошо

```cpp
YDB_LOG_INFO("Copy data to another shard",
    {"srcShardId", ...},
    {"dstShardId", ...});
```

- Плохо

```cpp
YDB_LOG_INFO("Copy data to another shard",
    {"srcId", ...},
    {"dstShard", ...});
```

Здесь непонятно ни идентификатором какой сущности является параметр `srcId`, ни как трактовать значение `dstShard` (это идентификатор, название или что-либо ещё).

{% endlist %}

3. Правила обозначения отдельных сущностей и коллекций:

- отдельное упоминание имени сущности в единственном числе предполагает, что значение параметра содержит описание этой сущности. Например, `table` — это описание таблицы (однако, в данном случае лучше добавлять суффикс, то есть `tableId` явно указывает, что параметр содержит идентификатор таблицы, `tableName` — название таблицы, `tableDesc` — описание таблицы и т.д.);
- отдельное упоминание имени сущности во множественном числе предполагает, что значение параметра содержит описание коллекции сущностей. Например,
`tables` — это описание нескольких таблиц (но не их количество);
- отдельное упоминание имени сущности во множественном числе с добавлением `Count` предполагает, что значение параметра содержит количество элементов в коллекции. Например, `tablesCount` — количество таблиц;
- нежелательно для обозначения количества элементов в коллекции к названию параметра вместо `Count` добавлять `Size`. Например, по названию параметра `totalFilesSize` — непонятно, речь идёт о количестве файлов или их суммарном размере.

{% list tabs %}

- Хорошо

```cpp
YDB_LOG_INFO("Backup volumes progress",
    {"currentVolumeNum", ...},
    {"volumesCount", ...},
    {"volumesSizeBytes", ...});
```

- Плохо

```cpp
YDB_LOG_INFO("Backup volumes progress",
    {"currentVolume", ...},
    {"volumes", ...});
```

{% endlist %}

4. Если в названии параметра используется общепринятая в YDB аббревиатура или термин, то её следует писать в том регистре, в котором принято. Примеры названия таких параметров:

- `VDiskId` — идентификатор VDisk (пишется не как `vdiskId`);
- `PDiskId` — идентификатор PDisk (пишется не как `pdiskId`);
- и т.д.

5. Допустимо использовать популярные сокращения (`idx` вместо `index`, `msg` вместо `message`, `tx` вместо `transaction` и т.д.).

6. Если параметр содержит числовую величину, то желательно к названию параметра добавлять суффикс:

- `Id`, если число является идентификатором (но не как `ID`);
- название единиц измерения (например, для объёмов памяти — `Byte`, `KByte`, `MByte`, для времени —  `Ms`, `Sec`, `Min`, для указания процентных долей чего-либо — `Percent` и так далее). Если используется нестандартная единица измерения, то желательно использовать предлог `In` (например, `bufferSizeInBlocks`);

{% list tabs %}

- Хорошо

```cpp
YDB_LOG_INFO("Table dump progress",
    {"tableId", ...},
    {"dumpedRecordsCount", ...},
    {"dumpedSizeBytes", ...},
    {"totalSizeBytes", ...});
```

- Плохо

```cpp
YDB_LOG_INFO("Table dump progress",
    {"table", ...},
    {"dumpedRecords", ...},
    {"dumpedSize", ...},
    {"totalSize", ...});
```

{% endlist %}

7. Если числовое значение параметра человеку удобнее анализировать не в десятичном виде, то лучше это значение указывать как символьную строку и дополнять общепринятыми символами (например, у шестнадцатеричного представления должен быть префикс `0x`, а у двоичного — префикс '0b' и т.д.). Хорошей практикой является дополнение названия параметра суффиксом `Hex` или т.п.

{% list tabs %}

- Хорошо

```cpp
unsigned flags;
...
YDB_LOG_INFO("Invalid access rights to file",
    {"filename", ...},
    {"aclFlags", flags},
    {"aclFlagsOct", ToOct(flags)});         // Make flags as "0XXXXX" string
```

- Плохо

```cpp
unsigned flags;
...
YDB_LOG_INFO("Invalid access rights to file",
    {"filename", ...},
    {"aclFlags", flags});
```

{% endlist %}

8. Если параметр содержит текстовую информацию, то желательно к названию параметра добавлять суффикс, позволяющий легче интерпретировать значение параметра (например, `Name`, `Desc`, `Json`, `Base64` и т.п.).
9. Для именования параметров можно применять правила именования локальных переменных (например, для логических признаков следует в начало названия параметра добавлять `is`, `has` или т. п.).

### Общепринятые названия параметров

Существуют общепринятые названия параметров, которые желательно использовать.

|Название|Содержимое параметра|
|--|--|
|`actorClassName`|Название класса, в котором реализован актор.|
|`actorStateName`|Состояние актора.|
|`backtrace`|Стек возникновения исключения (результат функции `TBackTrace::FromCurrentException().PrintToString()`).|
|`ev`|Описание сообщения, которое обрабатывает актор.|
|`evType`|Тип сообщения, которое обрабатывает актор.|
|`exception`|Описание произошедшего исключения C++.|
|`selfId`|Идентификатор актора, который обрабатывает сообщение.|

### Методология обогащения логов контекстной информацией

Предлагается следующая методология для обогащения событий, которые пишет в журнал конкретный актор.

**Шаг 1.** В акторе должна быть реализована функция, которая формирует текущий контекст логирования в виде набора параметров, прикрепляемых к логируемым сообщениям.

{% cut "Пример реализации" %}

```cpp
TStructuredMessage GetLogContext() const {
    return YDB_LOG_CREATE_MESSAGE(
        {"actorClassName", "TFileReader"},
        {"selfId", SelfId()},
        {"sourceFilePath", filename});
}
```

Вместо имени `GetLogContext` может использоваться `GetLogPrefix` или т.п. В принципе, это может быть даже не функция, а поле класса, создаваемое в конструкторе и изменяемое в ходе работы актора.

{% endcut %}

**Шаг 2.** Настройка контекста в "точках входа". У каждого актора есть небольшой набор функций, которые могут быть вызваны ядром акторной системы. Сюда входят:

1. Функция `Bootstrap`.
2. Функции обработки сообщений (ищутся по макросу `STATEFN` или т.п.).
3. Прочие виртуальные функции, которые переопределяет актор (они легко ищутся по ключевому слову `override`).

Далее в первой строке каждой такой функции с помощью макроса `YDB_LOG_CREATE_CONTEXT` должен настраиваться контекст логирования.

{% cut "Пример настройки контекста" %}

```cpp
STATEFN(StateWork) {
    YDB_LOG_CREATE_CONTEXT(GetLogContext());
    switch (ev->GetTypeRewrite()) {
        ...
    }
}
```

{% endcut %}
