Как записать данные об ошибке в регистр сведений, если происходит откат транзакции

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С часто возникает задача: при возникновении ошибки в процессе записи документа или другого объекта необходимо зафиксировать подробности этого события в специальный регистр сведений. Казалось бы, достаточно обернуть потенциально опасный код в конструкцию Попытка...Исключение и в блоке обработки ошибки выполнить запись в регистр. Однако на деле разработчики сталкиваются с тем, что запись в регистре не появляется, хотя код записи отрабатывает без ошибок. Давайте разберем, почему так происходит и какие существуют надежные способы решения этой задачи.

Выясним причину: почему стандартная запись не работает

Для начала проанализируем ситуацию с точки зрения архитектуры платформы. В 1С методы ПередЗаписью, ПриЗаписи и ОбработкаПроведения всегда выполняются внутри транзакции базы данных. Основное свойство транзакции — атомарность. Это значит, что либо все изменения внутри транзакции фиксируются, либо, если произошла ошибка, все они отменяются (происходит откат).

Когда в блоке Попытка случается исключение, платформа помечает текущую транзакцию как «битую» или «обреченную на откат». Даже если вы поймали ошибку в блоке Исключение и попытались записать данные в другой регистр сведений (например, РегистрОшибок), эта запись все равно станет частью общей транзакции. В момент, когда процедура завершится и платформа поймет, что основная операция (запись документа) не может быть завершена корректно, она отменит абсолютно все изменения, сделанные в этой транзакции, включая вашу запись об ошибке. Таким образом, регистр остается пустым, а контроль изменения объектов в базе 1С стандартными средствами становится невозможен.

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

Решение 1. Использование фоновых заданий

Это один из самых элегантных и современных способов. Фоновое задание запускается в отдельном сеансе, а значит, оно имеет свою собственную транзакционную область. Даже если основная транзакция документа откатится, фоновое задание, уже запущенное параллельно, успешно выполнит запись в базу данных.

Разберем алгоритм реализации этого метода:

  1. В блоке Исключение мы собираем все необходимые данные об ошибке.
  2. Вызываем экспортную процедуру общего модуля через механизм фоновых заданий.
  3. Передаем данные в фоновое задание только простыми типами (строки, числа, даты, структуры). Это важно, так как ссылки на еще не записанные объекты могут быть недоступны в другом сеансе.

Посмотрим на пример кода в модуле документа:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
    Попытка
        // Наш основной код, который может вызвать исключение
        Результат = 1 / 0; // Искусственная ошибка
    Исключение
        // Собираем данные об ошибке
        СтруктураОшибки = Новый Структура;
        СтруктураОшибки.Вставить("ИмяДокумента", Метаданные().Имя);
        СтруктураОшибки.Вставить("ТекстОшибки", ОписаниеОшибки());
        СтруктураОшибки.Вставить("ДатаОшибки", ТекущаяДата());
        СтруктураОшибки.Вставить("Пользователь", ИмяПользователя());

        // Запускаем фоновое задание для записи в регистр
        ФоновыеЗадания.Выполнить("ОбщийМодульЛогирования.ЗаписатьОшибкуВРегистр", СтруктураОшибки);
    КонецПопытки;
КонецПроцедуры

В общем модуле ОбщийМодульЛогирования (у которого установлены флаги "Сервер" и "Вызов сервера") создадим процедуру:


Процедура ЗаписатьОшибкуВРегистр(ПараметрыОшибки) Экспорт
    МенеджерЗаписи = РегистрыСведений.РегистрОшибок.СоздатьМенеджерЗаписи();
    ЗаполнитьЗначенияСвойств(МенеджерЗаписи, ПараметрыОшибки);
    МенеджерЗаписи.Период = ТекущаяДата();
    МенеджерЗаписи.Записать();
КонецПроцедуры

Решение 2. Запись в Журнал регистрации

Если вам не критично хранить ошибки именно в регистре сведений в режиме реального времени, наиболее надежным и штатным методом является использование Журнала регистрации. Механизм записи в журнал (метод ЗаписьЖурналаРегистрации) реализован платформой так, что он работает независимо от транзакций базы данных. Запись в журнале сохранится, даже если произойдет критический сбой и полный откат транзакции документа.

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

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


Попытка
    // Код записи документа
Исключение
    ТекстЛога = "Ошибка при записи документа: " + ЭтотОбъект.Метаданные().Имя + ". " + ОписаниеОшибки();
    ЗаписьЖурналаРегистрации("ОшибкиДокументов", УровеньЖурналаРегистрации.Ошибка, , ЭтотОбъект, ТекстЛога);
КонецПопытки;

Для автоматизации последующей обработки мы можем создать регламентное задание, которое раз в час будет считывать записи из журнала регистрации с событием "ОшибкиДокументов" и программно заполнять наш регистр сведений РегистрОшибок, или применить готовое решение для выгрузки журнала регистрации в Clickhouse.

Решение 3. Использование внешних соединений (HTTP или Web-сервисы)

Рассмотрим более сложный, но эффективный в распределенных системах метод — вызов собственного HTTP-сервиса. Когда мы вызываем внешний адрес (даже если этот сервис опубликован в этой же самой базе), 1С создает новое соединение. Это новое соединение работает в своем контексте и своей транзакции.

Как это работает по шагам:

  1. Вы создаете в конфигурации HTTP-сервис с методом POST (для этого удобно использовать конструктор API для обмена базы 1С 8.3).
  2. В блоке Исключение вы формируете HTTP-запрос к самому себе (через адрес публикации базы).
  3. HTTP-сервис получает данные и записывает их в регистр сведений (поможет модуль с универсальными процедурами HTTP-сервиса).

Важный момент: этот метод требует наличия свободных лицензий, так как HTTP-вызов инициирует новый сеанс на сервере 1С. Также стоит учитывать таймауты — вызов сервиса не должен тормозить работу основного процесса. Поэтому данный метод обычно используется только тогда, когда нужно передать информацию об ошибке в какую-то внешнюю систему мониторинга.

Почему вынос в отдельную процедуру или модуль не помогает?

Часто программисты пытаются решить проблему, вынося код записи в регистр в отдельную процедуру того же модуля или даже в другой ОбщийМодуль. Мы должны понимать: в 1С уровень вложенности транзакций всегда равен единице. Все вызовы процедур и функций, которые происходят после начала транзакции (в момент нажатия кнопки "Записать"), являются частью этой самой транзакции. Пока не будет выполнена финальная фиксация (Commit) на уровне платформы, ни одно изменение не попадет в физические таблицы базы данных.

Именно поэтому "обмануть" систему простым перемещением кода невозможно — нужно именно выйти за границы текущего потока выполнения или воспользоваться механизмами, которые платформа специально вынесла за рамки транзакционной модели (как Журнал регистрации).

Подведем итоги

Мы проанализировали ситуацию и выяснили, что стандартная запись в регистр внутри блока Исключение в модуле объекта обречена на удаление из-за отката транзакции. Для решения этой задачи мы рекомендуем придерживаться следующей логики:

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

← На главную