В процессе разработки сложных механизмов автоматизации, таких как загрузка данных из внешних систем или комплексное формирование цепочки документов, мы часто сталкиваемся с необходимостью логирования процесса. Однако возникает фундаментальная проблема: если вся операция выполняется в рамках одной транзакции (для обеспечения целостности данных), то при возникновении любой ошибки и последующей команде ОтменитьТранзакцию(), все записи в регистры сведений, сделанные «по ходу дела», также будут стерты. Рассмотрим подробно, как обойти это ограничение платформы 1С и реализовать механизм «автономных транзакций».
Прежде чем мы перейдем к решениям, проанализируем ситуацию. Транзакция в 1С подчиняется принципу атомарности: либо все действия внутри нее фиксируются в базе данных, либо не фиксируется ничего. Это критически важно для бухгалтерского или складского учета, но крайне неудобно для системного логирования. Если мы создаем пять документов и на пятом происходит ошибка, мы хотим знать, что именно пошло не так с первыми четырьмя, сохранив эти записи в специальном регистре сведений.
Стандартными средствами внутри одного потока выполнения (одного сеанса) записать данные «частично» невозможно. Нам необходимо либо вывести процесс логирования за пределы текущей транзакции, либо сохранить информацию в оперативной памяти до момента завершения или отката транзакции.
Это самый простой и эффективный способ, не требующий создания дополнительных сеансов или внешних соединений. Его суть заключается в том, что мы не записываем данные в регистр немедленно. Вместо этого мы собираем информацию в коллекцию (например, Массив или ТаблицаЗначений), которая находится в оперативной памяти. Переменные в памяти не подвержены откату транзакций базы данных.
Разберем по шагам реализацию этого метода:
Попытка — Исключение.Исключение сначала вызываем ОтменитьТранзакцию(), а затем запускаем процедуру записи накопленных данных из памяти в базу.Посмотрим на пример кода:
ПеременнаяЛог = Новый Массив; // Данные в памяти не удаляются при откате транзакции
НачатьТранзакцию();
Попытка
// Имитация процесса создания документов
Для Каждого Заказ Из СписокЗаказов Цикл
// Добавляем запись в лог (в память)
НоваяЗаписьЛога = Новый Структура("Дата, Сообщение", ТекущаяДата(), "Обработка заказа: " + Заказ.Номер);
ПеременнаяЛог.Добавить(НоваяЗаписьЛога);
// Тут может произойти ошибка
ЗаказОбъект = Заказ.ПолучитьОбъект();
ЗаказОбъект.Записать(РежимЗаписиДокумента.Проведение);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
// ТРАНЗАКЦИЯ ОТМЕНЕНА, но ПеременнаяЛог все еще содержит данные!
// Теперь мы можем спокойно записать их в регистр сведений
ЗаписатьНакопленныйЛогВБазу(ПеременнаяЛог);
ВызватьИсключение КраткоеОписаниеОшибки(ИнформацияОбОшибке());
КонецПопытки;
Если нам критически важно записывать логи в режиме «реального времени» (например, чтобы администратор мог видеть прогресс выполнения длительной операции), метод с накоплением в памяти не подойдет. В этом случае мы можем использовать механизм фоновых заданий. Каждое фоновое задание запускается в отдельном сеансе и имеет свой собственный контекст транзакций.
Разберем, как это работает: внутри основной транзакции мы вызываем процедуру, которая запускает фоновое задание для записи одной строки лога. Поскольку фоновое задание выполняется независимо, откат основной транзакции никак не повлияет на данные, уже записанные фоновым заданием.
Пример вызова фонового задания для логирования:
Процедура ЗаписатьЛогАвтономно(ТекстСообщения)
ПараметрыПроцедуры = Новый Массив;
ПараметрыПроцедуры.Добавить(ТекстСообщения);
// Запускаем фоновое задание. Оно создаст новый сеанс и свою транзакцию.
ФоновыеЗадания.Выполнить("МойОбщийМодуль.ЗаписатьВРегистрЛога", ПараметрыПроцедуры);
КонецПроцедуры
Важный нюанс: Частое создание фоновых заданий (например, для каждой строки лога в цикле из 10 000 итераций) создаст колоссальную нагрузку на сервер приложений 1С и может привести к исчерпанию лицензий или оперативной памяти. Этот метод хорош только для редких, ключевых записей.
Проанализируем еще один современный подход — использование внутреннего HTTP-сервиса. Мы можем опубликовать в нашей же базе простой HTTP-сервис, который принимает данные и записывает их в регистр логов. Когда основной код в транзакции делает HTTP-запрос к самому себе (к localhost), платформа создает новое соединение. Это соединение не видит транзакцию вызывающего потока.
Плюсы этого метода:
Выясним причину, по которой многие разработчики забывают про стандартный ЖурналРегистрации. Данные в него пишутся напрямую в файлы (или базу SQLite/SQL в зависимости от версии платформы), минуя механизм транзакций основной базы данных. Если ваша цель — просто сохранить историю событий для последующего анализа администратором, использование ЗаписатьВЖурналРегистрации является самым быстрым и «дешевым» с точки зрения ресурсов способом.
Пример использования:
ЗаписатьВЖурналРегистрации("ЗагрузкаДанных",
УровеньЖурналаРегистрации.Информация,
,
,
"Документ успешно обработан: " + НомерДокумента);
Чтобы пользователи могли анализировать эти данные в удобном виде (с отборами и отчетами), можно написать простую обработку, которая считывает данные из журнала регистрации с помощью метода ВыгрузитьЖурналРегистрации() и выводит их в таблицу на форме.
Рассмотрим вариант, когда нам нужно передавать данные лога между вызовами различных процедур и функций глубоко внутри транзакции, не таская за собой массив в качестве параметра. Для этого можно использовать ПараметрыСеанса.
Мы можем создать параметр сеанса с типом ХранилищеЗначения. В него мы будем помещать наш массив структур лога. Поскольку параметры сеанса живут в памяти серверного вызова, они сохраняют свое состояние даже после отката транзакции СУБД. После того как управление вернется в верхнюю точку и транзакция будет отменена в блоке Исключение, мы извлечем данные из параметра сеанса и запишем их в регистр.
Алгоритм работы:
ХранилищеЗначения.Проанализировав все варианты, можно сделать следующие выводы:
Попытка и запись в Исключении после отката. Это максимально быстро.Помните, что транзакция — это мощный инструмент обеспечения целостности, и попытки «обмануть» ее всегда требуют четкого понимания того, как платформа управляет сеансами и памятью. Выбирайте метод исходя из ваших требований к производительности и оперативности доступа к данным лога.