Перейти к основному содержанию
`Ctrl`+`K`

# Настройки логирования

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

Он позволяет включать логирование точечно — только для нужных методов, типов документов или уровней сообщений (INFO, WARNING и т.д.).

Это используется для:

- Отладки — логирование включается только для проблемного сценария, не засоряя общий лог.

- Мониторинга ошибок — можно настроить логирование только для WARNING и ERROR, игнорируя информационные сообщения.

- Аудита — все изменения важных объектов фиксируются с привязкой к контексту и тегами.

Всё это работает без изменения кода и перезапуска — настройки применяются динамически.

Для формирования механизма управления логированием разработчик в коде:

1. Регистрирует настройки логирования для методов класса.

2. Задаёт условия применения логирования для этих настроек.

3. Использует логирующую сессию, чтобы записи в лог учитывали настройки и условия.

Совет

В стандартной поставке существует ограниченный набор классов, на которых включено логирование.

Настройки можно открыть через раздел `Сущности - Классы`, открыть карточку нужного класса, в карточке открыть закладку `Настройки логирования`.

## Ключевые возможности

- Логирование можно настроить точечно для конкретных методов, типов объектов или подклассов.

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

- Автоматическая фильтрация записей по уровню логирования (ошибка, информационное сообщение, предупреждение и др.) с учётом иерархии, заданной пользователем.

- Регистрация настроек программно через API.

## Структура

### Лог

`Btk_Log` «Лог» — V-коллекция, используемая для хранения записей о событиях, действиях пользователей и системных операциях в Global. В отличие от стандартного аудита, лог позволяет выполнить пользовательскую настройку записываемых в таблицу сообщений.

Структура данных `Btk_Log` позволяет сохранять информацию о:

- Объекте-источнике (экземпляр класса).

- Вызываемой процедуре (процедуре, инициировавшей запись в лог).

- Типе лога.

- Логирующей сессии (транзакции, в рамках которой была произведена вставка в лог).

- Теге (служит для структурирования информации).

- Тексте лога.

- Вызываемом методе (текстовое поле, в котором описана цепочка вызовов методов, приведших к записи).
  Пример: `api1.proc1 -> api2.proc2`.

- Дополнительных характеристиках (в формате JSON).

#### Использования лога

Для обеспечения записи сообщения в лог необходимо выполнить следующие шаги:

1. Определить класс, к которому подключается `Btk_Log`, объекты которого будут выступать в качестве источника записи в лог.

2. В программный код добавить создание логирующей сессии.

3. В программный код добавить обработку, выполняющую вставку записи в таблицу `Btk_Log` при срабатывании соответствующего триггера события.

4. Выполнить настройку логирования для разграничения записей.

Для консультантов по внедрению Global ERP:

Для обеспечения записи сообщения в лог необходимо создать задачу (ДП) с указанием класса, к которому необходимо подключить `Btk_Log`, и описанием содержимого лога (значения, устанавливаемые в поля лога).

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

На закладке «Настройка логирования» для класса отображается дерево последовательно вызванных методов, на которых происходит запись в лог.

    Pm_CBStatementDocApi.processDocsByCBS (родитель)
    └── Pm_CBStatementDocApi.processDocs (дочерний метод)

Ниже приведена ER-диаграмма связи Btk_Log, настроечных классов и непосредственно самого класса, на котором необходимо включить логирование.

### Условия логирования

Условия логирования определяют, разрешено ли записывать сообщения определённого уровня (например, INFO, WARNING) для конкретного метода с учётом контекста выполнения: класса, подкласса или типа объекта.

Открыть условия можно через кнопку `...` в колонке `Настройки логирования`.

Зачем нужны условия?

- Фильтрация записей в лог в зависимости от:

  - Уровня важности (INFO, WARNING, ERROR и т.д.).

  - Типа объекта (например, только для документов типа «Выписка банка»).

  - Подкласса (например, только для исходящих документов).

- Сокращение объёма логов в продакшене.

- Включение детального логирования только для определённых сценариев или объектов.

Как работают условия?

1. Регистрация условия происходит через `Btk_LogClassSettingCondApi()` или через интерфейс.

2. При записи в лог (с `bUseSetting = true` и внутри сессии) система:

   - Определяет, какой метод вызывает `insertMsg`.

   - Проверяет, есть ли для него зарегистрированные условия.

   - Сравнивает тип логирования сообщения с разрешёнными типами в условиях.

   - Применяет иерархию уровней: если разрешён INFO, то разрешены и WARNING, ERROR. Если разрешён WARNING — INFO не разрешён.

3. Условия не наследуются от родителей — каждый метод должен иметь свои условия.

4. Условия для дочернего метода проверяются только в том случае, если все вышестоящие методы в иерархии имеют активные настройки логирования (даже если для них не заданы условия).

### Логирующая сессия

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

Без логирующей сессии:

- Запись в лог происходит напрямую, как обычный вызов `insertMsg`.

- Настройки класса игнорируются полностью, даже если указано `bUseSetting = true`.

- Нет возможности применить фильтрацию по типу объекта, подклассу или уровню логирования.

Зачем нужна сессия:

- Объединение нескольких логируемых сообщений в одну сессию.

- Изоляция настроек — сессия гарантирует, что настройки одного потока/процесса не влияют на другие. Даже если создается дочерняя логирующая сессия для записи лога, то исходная сессия не изменится.

- Определение контекста вызова — система знает, в каком методе происходит запись и кто его вызвал.

- Применение условий — только внутри сессии система проверяет, разрешено ли писать лог для текущего метода, типа объекта, уровня логирования и т.д.

Как работает:

При вызове `Btk_LogApi().withLogSession(...)` система:

- Сохраняет в контексте имя метода и его идентификатор.

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

- Начинает отслеживать все последующие вызовы `insertMsg(..., bUseSetting = true)` внутри блока.

- При записи в лог:

  - Проверяет, зарегистрирован ли метод в дереве настроек.

  - Проверяет, активны ли все его родители.

  - Применяет только его собственные условия.

  - Принимает решение о записи сообщения в лог.

### Ключевые API:

    Btk_LogApi (основной API для записи логов)
    ├── Btk_LogClassSettingApi (регистрация методов в дереве настроек)
    ├── Btk_LogTypeApi (управление типами логов)
    ├── Btk_LogClassSettingCondApi (управление условиями логирования)
    └── Btk_LogSession (управление контекстом логирования)

## Создание

### Создание настроек

Настройку можно задать через код и через интерфейс системы. По умолчанию рекомендуется создавать настройки через код — именно этот способ используется в стандартной реализации.

Настройка через интерфейс предназначена для нетиповых сценариев, когда требуется создать пользовательские методы, отличные от тех, что создаются по умолчанию (например, JEXL-скрипты).

Данный метод следует использовать исключительно при наличии обоснованного сценария и полного понимания его влияния на систему.

Регистрация методов, на которых происходит запись в лог:

        //Регистрация верхнеуровневой (родительской) настройки для метода Pm_CBStatementDocApi.processDocsByCBS
        val idSettingParent = Btk_LogClassSettingApi().register("Pm_CBStatementDocApi.processDocsByCBS", idClass)
        //Регистрация дочерней настройки для метода Pm_CBStatementDocApi.processDocs вызываемого из метода Pm_CBStatementDocApi.processDocsByCBS (с указанием родителя)
        val idSettingChild = Btk_LogClassSettingApi().register("Pm_CBStatementDocApi.processDocs", idClass, idSettingParent)

### Создание условии

Условия можно задать через код и через интерфейс системы.
Регистрация условий, по которым проверяется настройка логирования:

        //Регистрация условия на классе
        Btk_LogClassSettingCondApi().registerByClass(idSettingParent, Btk_LogTypeApi().findByMnemoCode("INFO"))
        //Регистрация условия на типе объекта
        Btk_LogClassSettingCondApi().registerByObjectType(idSettingChild, Btk_LogTypeApi().findByMnemoCode("INFO"), idOTCBStatementDoc)
        //Регистрация условия на подклассе
        Btk_LogClassSettingCondApi().registerBySubClass(idSettingChild, Btk_LogTypeApi().findByMnemoCode("WARINING"), idSBCBStatementDoc)

### Создание логирующей сессии

Логирующая сессия задается только из кода.
Пример регистрации логирующей сессии и записи в лог с учетом настроек:

      // Открытие логирующей сессии от объекта логирования
      def processDocsByCBS(idpCBStatement: NLong): Unit = {
        val ropvCBStatement = Pm_ClientBankStatementApi().load(idpCBStatement)
        // Открытия логирующей сессии
        Btk_LogApi().withLogSession("Pm_CBStatementDocApi.processDocsByCBS", ropvCBStatement.gid) {
          // Запись в лог
          Btk_LogApi().insertMsg(
            "INFO"
            ,ropvCBStatement
            ,"Начало обработки банковской выписки"
            ,"Обработка банковской выписки"
            ,bUseSetting = true // Параметр указывает, что при записи учитывается настройка на классе
          )
          // исполняемая процедура
          processDocs(refreshByParent(ropvCBStatement).map(_.get(_.id)).toList)
        }
      }

      // Открытие логирующей сессии от класса
      def processDocs(idapCBStatementDoc: List[NLong]): Unit = {
        // Открытия логирующей сессии
        Btk_LogApi().withLogSession("Pm_CBStatementDocApi.processDocsByCBS", idClass) {
          // исполняемая процедура
          // Запись в лог
          Btk_LogApi().insertMsg(
            "INFO"
            ,ropvCBStatementDoc
            ,"Завершение обработки банковской выписки"
            ,"Обработка банковской выписки"
            ,bUseSetting = true // Параметр указывает, что при записи учитывается настройка на классе
          )
        }
      }

Примечание

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

      lazy val sLogSettingProcessDocsByCBS = "Pm_CBStatementDocApi.processDocsByCBS" // sLSProcessDocsByCBS
      lazy val sLogSettingProcessDocs = "Pm_CBStatementDocApi.processDocs" // sLSProcessDocs

## Использование

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

Интерпретация банковской выписки:

      def processDocsByCBS(idpCBStatement: NLong): Unit = {
        val ropvCBStatement = Pm_ClientBankStatementApi().load(idpCBStatement)
        Btk_LogApi().withLogSession(sLogSettingProcessDocsByCBS, ropvCBStatement.gid) {
          val sStartProcMsg = s"Запущена интерпретация выписки ${ropvCBStatement.get(_.sNum)} (${ropvCBStatement.gid}) от ${ropvCBStatement.get(_.dStatement).toString("dd.MM.yyyy hh:mm:ss")}."
          Btk_LogApi().atInsertMsg("INFO", ropvCBStatement, sStartProcMsg, "", bUseSetting = true)
          Btk_InfoLogPkg().info(sStartProcMsg)

          val idavCBStatementDoc = refreshByParent(ropvCBStatement).map(_.get(_.id))
          processDocs(idavCBStatementDoc.toList)

          val sFinishProcMsg = s"Завершена интерпретация выписки ${ropvCBStatement.get(_.sNum)} (${ropvCBStatement.gid}) от ${ropvCBStatement.get(_.dStatement).toString("dd.MM.yyyy hh:mm:ss")}."
          Btk_LogApi().atInsertMsg("INFO", ropvCBStatement, sFinishProcMsg, "", bUseSetting = true)
          Btk_InfoLogPkg().info(sFinishProcMsg)
        }
      }

Вынесение логики в отдельный метод:

    def isDocProcessed(rop: Pm_CBStatementDocApi#ApiRop): Boolean = {
          val ropvCBStatement = Pm_ClientBankStatementApi().load(rop.get(_.idClientBankStatement))
          Btk_LogApi().withLogSession(sLogSettingIsDocProcessed, rop.gid) {
            if (rop.get(_.idStateMC) > 100.nr) {
              val sErrorMsg = s"Процедура интерпретации позиции банковской выписки ${rop.get(_.sNumDoc)} по документу ${ropvCBStatement.get(_.sNum)} (${ropvCBStatement.gid}) от ${ropvCBStatement.get(_.dStatement).toString("dd.MM.yyyy hh:mm:ss")} невозможна, так как она уже обработана пользователем"
              Btk_LogApi().atInsertMsg("ERROR", rop, sErrorMsg, "", bUseSetting = true)
              Btk_InfoLogPkg().error(sErrorMsg)
              true
            } else {
              val sInfoMsg = s"Проверка пользовательской обработки позиции выписки ${rop.get(_.sNumDoc)} по документу ${ropvCBStatement.get(_.sNum)} (${ropvCBStatement.gid}) от ${ropvCBStatement.get(_.dStatement).toString("dd.MM.yyyy hh:mm:ss")}"
              Btk_LogApi().atInsertMsg("INFO", rop, sInfoMsg, "", bUseSetting = true)
              Btk_InfoLogPkg().info(sInfoMsg)
              false
            }
          }
        }

Дополнительные примеры настройки и использования можно посмотреть в реализации:

- ru.bitec.app.pm.clientbank.Pm_CBStatementDocApi#regLogSetting

- ru.bitec.app.pm.clientbank.Pm_CBStatementDocApi#processDocs

## Результат логирования

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

Все логи системы лежат в `Настройки системы - Сущности - Классы - btk_log`.

### Чистка журнала логов

Очистка логов для классов с преднастроенным логированием выполняется автоматически. Она настроена в типовом задании очистки устаревших данных с окном хранения 10 дней в менеджере заданий.

Примечание

Для ручной очистки реализован API-метод `clearBtkLog()`, принимающий на вход системное имя класса и количество дней сдвига. К нему подключен джоб, автоматически очищающий таблицу.

При создании новых методов логирования необходимо зарегистрировать соответствующую запись в этом задании. Это обеспечивает автоматическое удаление устаревших записей логов и предотвращает неконтролируемый рост объема данных.

Детальнее про задания очистки смотри в разделе «Менеджер заданий»

## Расширение

Для добавления новых методов в систему логирования:

1. Зарегистрируйте метод через `Btk_LogClassSettingApi().register()`.

2. Определите родительский метод в иерархии вызовов.

3. Настройте условия логирования через `Btk_LogClassSettingCondApi()`.

4. Используйте константы для имен методов во избежание ошибок.

## Ограничения

- Уровень WARNING не включает INFO — фильтрация работает только на повышение уровня (INFO включает WARNING и ERROR; WARNING включает только WARNING и ERROR).

- Нет автоматического построения дерева вызовов — вся иерархия регистрируется вручную.

- Нет встроенной валидации — можно зарегистрировать несуществующий метод.

- Запись в лог происходит только при выполнении всех условий:

  - Метод присутствует в дереве настроек.

  - Все вышестоящие методы активны.

  - Для текущего метода выполняются условия логирования.