В практике разработки на платформе 1С часто возникает задача: при возникновении ошибки в процессе записи документа или другого объекта необходимо зафиксировать подробности этого события в специальный регистр сведений. Казалось бы, достаточно обернуть потенциально опасный код в конструкцию Попытка...Исключение и в блоке обработки ошибки выполнить запись в регистр. Однако на деле разработчики сталкиваются с тем, что запись в регистре не появляется, хотя код записи отрабатывает без ошибок. Давайте разберем, почему так происходит и какие существуют надежные способы решения этой задачи.
Для начала проанализируем ситуацию с точки зрения архитектуры платформы. В 1С методы ПередЗаписью, ПриЗаписи и ОбработкаПроведения всегда выполняются внутри транзакции базы данных. Основное свойство транзакции — атомарность. Это значит, что либо все изменения внутри транзакции фиксируются, либо, если произошла ошибка, все они отменяются (происходит откат).
Когда в блоке Попытка случается исключение, платформа помечает текущую транзакцию как «битую» или «обреченную на откат». Даже если вы поймали ошибку в блоке Исключение и попытались записать данные в другой регистр сведений (например, РегистрОшибок), эта запись все равно станет частью общей транзакции. В момент, когда процедура завершится и платформа поймет, что основная операция (запись документа) не может быть завершена корректно, она отменит абсолютно все изменения, сделанные в этой транзакции, включая вашу запись об ошибке. Таким образом, регистр остается пустым, а контроль изменения объектов в базе 1С стандартными средствами становится невозможен.
Рассмотрим, как можно обойти это системное ограничение, используя механизмы, работающие вне основной транзакции.
Это один из самых элегантных и современных способов. Фоновое задание запускается в отдельном сеансе, а значит, оно имеет свою собственную транзакционную область. Даже если основная транзакция документа откатится, фоновое задание, уже запущенное параллельно, успешно выполнит запись в базу данных.
Разберем алгоритм реализации этого метода:
Исключение мы собираем все необходимые данные об ошибке.Посмотрим на пример кода в модуле документа:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
Попытка
// Наш основной код, который может вызвать исключение
Результат = 1 / 0; // Искусственная ошибка
Исключение
// Собираем данные об ошибке
СтруктураОшибки = Новый Структура;
СтруктураОшибки.Вставить("ИмяДокумента", Метаданные().Имя);
СтруктураОшибки.Вставить("ТекстОшибки", ОписаниеОшибки());
СтруктураОшибки.Вставить("ДатаОшибки", ТекущаяДата());
СтруктураОшибки.Вставить("Пользователь", ИмяПользователя());
// Запускаем фоновое задание для записи в регистр
ФоновыеЗадания.Выполнить("ОбщийМодульЛогирования.ЗаписатьОшибкуВРегистр", СтруктураОшибки);
КонецПопытки;
КонецПроцедуры
В общем модуле ОбщийМодульЛогирования (у которого установлены флаги "Сервер" и "Вызов сервера") создадим процедуру:
Процедура ЗаписатьОшибкуВРегистр(ПараметрыОшибки) Экспорт
МенеджерЗаписи = РегистрыСведений.РегистрОшибок.СоздатьМенеджерЗаписи();
ЗаполнитьЗначенияСвойств(МенеджерЗаписи, ПараметрыОшибки);
МенеджерЗаписи.Период = ТекущаяДата();
МенеджерЗаписи.Записать();
КонецПроцедуры
Если вам не критично хранить ошибки именно в регистре сведений в режиме реального времени, наиболее надежным и штатным методом является использование Журнала регистрации. Механизм записи в журнал (метод ЗаписьЖурналаРегистрации) реализован платформой так, что он работает независимо от транзакций базы данных. Запись в журнале сохранится, даже если произойдет критический сбой и полный откат транзакции документа.
Проанализируем преимущества этого подхода. Во-первых, это не создает лишней нагрузки на базу данных в момент возникновения ошибки. Во-вторых, это гарантирует сохранность данных. Чтобы позже перенести эти данные в регистр сведений для удобного анализа, можно настроить регламентное задание.
Пример использования в коде:
Попытка
// Код записи документа
Исключение
ТекстЛога = "Ошибка при записи документа: " + ЭтотОбъект.Метаданные().Имя + ". " + ОписаниеОшибки();
ЗаписьЖурналаРегистрации("ОшибкиДокументов", УровеньЖурналаРегистрации.Ошибка, , ЭтотОбъект, ТекстЛога);
КонецПопытки;
Для автоматизации последующей обработки мы можем создать регламентное задание, которое раз в час будет считывать записи из журнала регистрации с событием "ОшибкиДокументов" и программно заполнять наш регистр сведений РегистрОшибок, или применить готовое решение для выгрузки журнала регистрации в Clickhouse.
Рассмотрим более сложный, но эффективный в распределенных системах метод — вызов собственного HTTP-сервиса. Когда мы вызываем внешний адрес (даже если этот сервис опубликован в этой же самой базе), 1С создает новое соединение. Это новое соединение работает в своем контексте и своей транзакции.
Как это работает по шагам:
HTTP-сервис с методом POST (для этого удобно использовать конструктор API для обмена базы 1С 8.3).Исключение вы формируете HTTP-запрос к самому себе (через адрес публикации базы).Важный момент: этот метод требует наличия свободных лицензий, так как HTTP-вызов инициирует новый сеанс на сервере 1С. Также стоит учитывать таймауты — вызов сервиса не должен тормозить работу основного процесса. Поэтому данный метод обычно используется только тогда, когда нужно передать информацию об ошибке в какую-то внешнюю систему мониторинга.
Часто программисты пытаются решить проблему, вынося код записи в регистр в отдельную процедуру того же модуля или даже в другой ОбщийМодуль. Мы должны понимать: в 1С уровень вложенности транзакций всегда равен единице. Все вызовы процедур и функций, которые происходят после начала транзакции (в момент нажатия кнопки "Записать"), являются частью этой самой транзакции. Пока не будет выполнена финальная фиксация (Commit) на уровне платформы, ни одно изменение не попадет в физические таблицы базы данных.
Именно поэтому "обмануть" систему простым перемещением кода невозможно — нужно именно выйти за границы текущего потока выполнения или воспользоваться механизмами, которые платформа специально вынесла за рамки транзакционной модели (как Журнал регистрации).
Мы проанализировали ситуацию и выяснили, что стандартная запись в регистр внутри блока Исключение в модуле объекта обречена на удаление из-за отката транзакции. Для решения этой задачи мы рекомендуем придерживаться следующей логики:
Используя эти подходы, вы сможете гарантированно собирать информацию о сбоях в системе, что значительно упростит дальнейшую отладку и поддержку вашего программного продукта.