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

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

Он позволяет включать логирование точечно — только для нужных методов, типов документов или уровней сообщений (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, настроечных классов и непосредственно самого класса, на котором необходимо включить логирование.
diag

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

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

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

11

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

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

    • Уровня важности (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

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

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

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

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

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

Примечание

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

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

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

Расширение#

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

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

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

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

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

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

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

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

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

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

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

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

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