При реализации механизмов логирования внешних обменов (например, через HTTP или REST API) разработчики часто сталкиваются с проблемой: события происходят настолько быстро, что стандартная периодичность регистра сведений (одна секунда) не позволяет сохранить все записи. В итоге последующие записи за ту же секунду просто затирают предыдущие, и хронология обмена превращается в «кашу» или содержит только последнее событие. Рассмотрим подробнее, как обойти это ограничение платформы 1С и гарантировать сохранность каждой строки лога.
В платформе 1С:Предприятие периодический регистр сведений обеспечивает уникальность записей по комбинации измерений и периода. Если периодичность установлена в значение Секунда, то система физически не может хранить две разные записи с одинаковыми значениями измерений внутри одной и той же секунды. При записи набора данных с тем же периодом старая запись обновляется (перезаписывается).
Проанализируем ситуацию: если ваш скрипт обмена выполняет чтение, запись и логирование ошибок в течение 100–200 миллисекунд, все эти события получат одну и ту же отметку времени. Чтобы этого избежать, нам нужно либо изменить структуру хранения, либо искусственно повысить уникальность ключа записи.
Самый простой и эффективный способ для логов — сделать регистр непериодическим. В этом случае поле Период исчезает, и уникальность определяется только измерениями. Однако нам все равно нужно соблюдать хронологию. Разберем, как это сделать правильно.
Добавим в регистр измерение МоментВремениЧислом с типом Число(15, 0). В него мы будем записывать значение, полученное функцией ТекущаяУниверсальнаяДатаВМиллисекундах(). Посмотрим на пример кода:
МенеджерЗаписи = РегистрыСведений.ЛогАПИ.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ПериодЧислом = ТекущаяУниверсальнаяДатаВМиллисекундах();
МенеджерЗаписи.Событие = "Начало чтения данных";
МенеджерЗаписи.Сессия = ИдентификаторСессии;
МенеджерЗаписи.Записать();
Важный нюанс: Стандартный тип данных Дата в 1С хранит только секунды. Если вы попытаетесь записать миллисекунды в обычное поле даты, они будут отсечены. Именно поэтому мы используем числовое поле. Это позволит нам сортировать записи в отчетах по возрастанию этого числа, точно соблюдая порядок возникновения событий.
Если даже миллисекунд недостаточно (например, при многопоточной обработке, когда два потока могут вызвать функцию в один и тот же такт процессора), можно применить так называемый «грязный хак». Добавим измерение ИдентификаторЗаписи с типом УникальныйИдентификатор.
Выясним преимущества этого метода: УникальныйИдентификатор (GUID) гарантирует, что ни одна запись не будет перезаписана, так как ключ уникальности всегда будет разным. Проанализируем пример реализации:
НаборЗаписей = РегистрыСведений.ЛогАПИ.СоздатьНаборЗаписей();
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Период = ТекущаяДата();
НоваяЗапись.ИдентификаторЗаписи = Новый УникальныйИдентификатор();
НоваяЗапись.Сообщение = "Ошибка при парсинге JSON";
НаборЗаписей.Записать(Ложь); // Параметр Ложь позволяет дописывать данные, не затирая существующие
Однако помните, что поиск по GUID и индексация таких полей могут замедлить работу базы данных при огромных объемах логов. Используйте этот метод только в сочетании с другими измерениями (например, Идентификатор сессии).
Рассмотрим альтернативный архитектурный подход. Вместо того чтобы писать каждую строчку лога в регистр, мы можем создавать документ СессияОбмена. В табличную часть этого документа мы будем записывать все события по порядку.
Почему это удобно:
Рассмотрим пример заполнения такого лога в процессе итерации по массиву данных из API:
ДокЛог = Документы.ЛогСессииАПИ.СоздатьДокумент();
ДокЛог.Дата = ТекущаяДата();
ДокЛог.Статус = "В процессе";
Для Каждого СтрокаОтвета Из МассивДанныхАПИ Цикл
НоваяСтрока = ДокЛог.События.Добавить();
НоваяСтрока.Время = ТекущаяУниверсальнаяДатаВМиллисекундах();
НоваяСтрока.Текст = "Обработка объекта: " + СтрокаОтвета.Имя;
КонецЦикла;
ДокЛог.Записать(РежимЗаписиДокумента.Запись);
Запись в регистр или документ на каждой итерации цикла обмена — это «смерть» для производительности, так как каждое обращение к базе данных инициирует новую транзакцию. Проанализируем ситуацию: если у вас 1000 записей, лучше накопить их в памяти и записать один раз.
Для этого удобно использовать объект ДополнительныеСвойства или просто временную ТаблицуЗначений. Посмотрим, как это реализовать:
ТЗ_Лог = Новый ТаблицаЗначений;
ТЗ_Лог.Колонки.Добавить("Событие");
ТЗ_Лог.Колонки.Добавить("Момент");
// ... в цикле наполняем ТЗ ...
НоваяСтрока = ТЗ_Лог.Добавить();
НоваяСтрока.Событие = "Данные получены";
НоваяСтрока.Момент = ТекущаяУниверсальнаяДатаВМиллисекундах();
// В конце процесса записываем всё одним набором
НаборЗаписей = РегистрыСведений.ЛогАПИ.СоздатьНаборЗаписей();
НаборЗаписей.Загрузить(ТЗ_Лог);
НаборЗаписей.Записать(Ложь);
Разберем по шагам, что нужно сделать, чтобы ваш лог обмена работал идеально:
ИдентификаторСессии + НомерСтроки (или Миллисекунды). Это обеспечит 100% уникальность.ЗаписьЖурналаРегистрации() будет более уместным. Он работает асинхронно и практически не нагружает основную транзакцию.Применяя эти методы, вы добьетесь того, что хронология событий в ваших обменах будет сохраняться с точностью до миллисекунды, а риск потери данных из-за перезаписи будет полностью исключен.