Как реализовать параллельную запись лога в файл на клиенте во время работы фонового задания на сервере?

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

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

Однако часто возникает сопутствующая задача: необходимо вести подробный и структурированный лог работы процесса. Более того, этот лог должен записываться на клиентском компьютере в режиме реального времени. Это критически важно на случай непредвиденного падения сеанса, выключения компьютера пользователя или обрыва связи — лог должен быть сохранен на физический диск вплоть до секунды сбоя, чтобы администратор мог понять, на каком объекте прервался процесс.

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

В чем заключается основная сложность?

Фоновое задание выполняется в изолированном контексте на сервере приложений 1С. Оно не имеет прямого доступа к файловой системе локального компьютера пользователя (клиента). Попытка вызвать клиентские методы или напрямую обратиться к диску C:\ из серверного кода приведет к ошибке выполнения.

Нам необходимо решить две ключевые задачи:

  1. Организовать эффективный и малозатратный транспорт структурированных данных (событий лога) с сервера на клиент.
  2. Обеспечить на стороне клиента быструю, потоковую запись этих данных в локальный файл таким образом, чтобы при внезапном закрытии программы файл не оказался поврежденным («битым»).

Способ 1. Использование встроенного механизма БСП «ДлительныеОперации»

Если ваша конфигурация разработана на базе Библиотеки стандартных подсистем (БСП), то наиболее лаконичным решением будет использование штатного механизма сообщения прогресса длительных операций.

Функция БСП ДлительныеОперации.СообщитьПрогресс() позволяет передавать с сервера структуру или строку, которую клиентское приложение может перехватить во время работы обработчика ожидания.

Рассмотрим, как это выглядит на практике. Ниже представлен пример кода, который выполняется в фоновом задании на сервере:


// Серверный код фонового задания
Процедура ВыполнитьСозданиеДокументов(ПараметрыЗапуска) Экспорт
    
    ВсегоДокументов = ПараметрыЗапуска.ДанныеДляСоздания.Количество();
    
    Для Индекс = 0 По ВсегоДокументов - 1 Цикл
        
        ДанныеДокумента = ПараметрыЗапуска.ДанныеДляСоздания[Индекс];
        
        Попытка
            // Полезное действие по созданию документа
            НовыйДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
            // ... заполнение реквизитов ...
            НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);
            
            Статус = "Успешно";
            ОписаниеОшибки = "";
            СсылкаНаДокумент = Строка(НовыйДокумент.Ссылка);
        Исключение
            Статус = "Ошибка";
            ОписаниеОшибки = КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
            СсылкаНаДокумент = "";
        КонецПопытки;
        
        // Формируем структуру с данными для лога
        СтруктураСобытия = Новый Структура;
        СтруктураСобытия.Вставить("Дата", ТекущаяДата());
        СтруктураСобытия.Вставить("Документ", СсылкаНаДокумент);
        СтруктураСобытия.Вставить("Статус", Статус);
        СтруктураСобытия.Вставить("Описание", ОписаниеОшибки);
        
        // Сериализуем структуру в формат JSON для передачи на клиент
        ЗаписьJSON = Новый ЗаписьJSON;
        ЗаписьJSON.УстановитьСтроку();
        ЗаписатьJSON(ЗаписьJSON, СтруктураСобытия);
        СтрокаJSON = ЗаписьJSON.Закрыть();
        
        // Расчет процента выполнения
        ПроцентВыполнения = Окр(((Индекс + 1) / ВсегоДокументов) * 100);
        
        // Передаем сериализованную строку лога на клиент через прогресс БСП
        ДлительныеОперации.СообщитьПрогресс(ПроцентВыполнения, СтрокаJSON);
        
    КонецЦикла;
    
КонецПроцедуры

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


// Клиентский код управляемой формы
&НаКлиенте
Процедура ОбработчикОжиданияОпроса()
    
    // Получаем текущее состояние фонового задания через механизмы БСП
    ПараметрыОпроса = ДлительныеОперацииКлиент.ПараметрыОпросаДлительнойОперации();
    СостояниеОперации = ДлительныеОперацииКлиент.ОперацияВыполнена(ИдентификаторЗадания, ПараметрыОпроса);
    
    // Проверяем наличие переданных данных прогресса
    Если СостояниеОперации.Свойства.Свойство("Прогресс") И ЗначениеЗаполнено(СостояниеОперации.Прогресс) Тогда
        
        // Извлекаем переданную строку JSON
        СтрокаJSON = СостояниеОперации.Прогресс.Текст;
        
        // Записываем полученную информацию в локальный файл
        ЗаписатьСтрокуВЛокальныйЛог(СтрокаJSON);
        
    КонецЕсли;
    
    Если СостояниеОперации.Статус <> "Выполняется" Тогда
        ОтключитьОбработчикОжидания("ОбработчикОжиданияОпроса");
        ПоказатьПредупреждение(, "Обработка завершена!");
    КонецЕсли;
    
КонецПроцедуры

Способ 2. Облегченный штатный механизм без БСП (Сообщения пользователю)

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

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

Давайте посмотрим на программную реализацию этого подхода.

Код в фоновом задании на сервере:


// На сервере в цикле фонового задания
СообщениеЛога = Новый Структура("Дата, Документ, Статус", ТекущаяДата(), "Реализация №1", "Успешно");

// Сериализация в JSON
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.УстановитьСтроку();
ЗаписатьJSON(ЗаписьJSON, СообщениеЛога);
СтрокаJSON = ЗаписьJSON.Закрыть();

// Отправка через системный механизм сообщений
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = СтрокаJSON;
Сообщение.Сообщить();

Код опроса на стороне клиента:


&НаКлиенте
Процедура ОбработчикОжиданияОпросаБезБСП()
    
    // Получаем массив только тех сообщений, которые появились с момента последнего опроса
    НовыеСообщения = ПолучитьСообщенияПользователю(Истина);
    
    Если НовыеСообщения.Количество() > 0 Тогда
        Для Каждого Сообщение Из НовыеСообщения Цикл
            // Каждое сообщение содержит одну сериализованную JSON-строку
            ЗаписатьСтрокуВЛокальныйЛог(Сообщение.Текст);
        КонецЦикла;
    КонецЕсли;
    
КонецПроцедуры

Выбор формата файла: Почему JSON Lines (NDJSON) — идеальное решение?

В исходном обсуждении авторы сталкивались с дилеммой: использовать простой плоский текстовый файл с кастомными разделителями или пытаться сформировать полноценный XML/JSON файл. Второй вариант неудобен тем, что в случае аварийного завершения сеанса файл останется незакрытым (например, без корневого тега </root> или закрывающей квадратной скобки ]), что сделает его поврежденным и непригодным для дальнейшего автоматического парсинга.

Для решения этой проблемы мы рекомендуем использовать современный формат JSON Lines (NDJSON). Его суть предельно проста: каждая строка текстового файла является независимым, валидным JSON-объектом.

Преимущества JSON Lines:

Реализуем процедуру записи в файл на клиенте в формате JSON Lines:


&НаКлиенте
Процедура ЗаписатьСтрокуВЛокальныйЛог(СтрокаJSON)
    
    ПутьКЛогу = "C:\Logs\BackgroundProcess.log";
    
    // Открываем файл в режиме дозаписи (параметр "Дописывать" установлен в Истина)
    ФайлЛога = Новый ЗаписьТекста;
    ФайлЛога.Открыть(ПутьКЛогу, КодировкаТекста.UTF8, СимволыВСтроке.Нет, Истина);
    
    // Записываем одну изолированную строку
    ФайлЛога.ЗаписатьСтроку(СтрокаJSON);
    
    // Закрываем файл для физического сброса буферов ОС на диск
    ФайлЛога.Закрыть();
    
КонецПроцедуры

Обратите внимание на вызов метода Закрыть(). Операционные системы склонны кэшировать дисковые операции записи. Если не закрывать файл, то при внезапном отключении питания компьютера последние несколько секунд лога могут остаться в оперативной памяти и не записаться на физический накопитель. Поочередное открытие и закрытие файла решает эту проблему.

Теперь посмотрим, насколько просто и элегантно можно прочитать такой лог программно и преобразовать его обратно в коллекцию 1С (например, для вывода в отчет или таблицу на форме):


&НаКлиенте
Процедура ПрочитатьЛогИЗаполнитьТаблицу()
    
    ПутьКЛогу = "C:\Logs\BackgroundProcess.log";
    
    ФайлЛога = Новый ЧтениеТекста;
    ФайлЛога.Открыть(ПутьКЛогу, КодировкаТекста.UTF8);
    
    СтрокаЛога = ФайлЛога.ПрочитатьСтроку();
    Пока СтрокаЛога <> Неопределено Цикл
        
        Если ЗначениеЗаполнено(СокращенныйЛП(СтрокаЛога)) Тогда
            // Десериализуем конкретную строку JSON в структуру 1С
            ЧтениеJSON = Новый ЧтениеJSON;
            ЧтениеJSON.УстановитьСтроку(СтрокаЛога);
            ДанныеСобытия = ПрочитатьJSON(ЧтениеJSON);
            ЧтениеJSON.Закрыть();
            
            // Здесь мы можем работать с "ДанныеСобытия" как с обычной Структурой
            ДобавитьСтрокуВТаблицуФормы(ДанныеСобытия);
        КонецЕсли;
        
        СтрокаЛога = ФайлЛога.ПрочитатьСтроку();
    КонецЦикла;
    
    ФайлЛога.Закрыть();
    
КонецПроцедуры

Начиная с версии платформы 8.3.23, вы можете использовать еще более лаконичные функции ПрочитатьЗначениеJSON(), которые позволяют избавиться от ручного создания объектов чтения/записи.


Способ 3. Передовое решение без опроса: Система взаимодействия

Использование классического обработчика ожидания на клиенте имеет один большой недостаток — холостые вызовы. Клиент раз в 1-2 секунды дергает сервер вопросом: «Ну что, есть новые записи?», нагружая сеть и рабочий процесс 1С.

Если в вашей организации настроена Система взаимодействия (начиная с версии платформы 8.3.10), вы можете организовать асинхронную передачу логов по push-технологии.

Алгоритм работы выглядит следующим образом:

  1. При старте фонового задания клиент создает служебное (невидимое пользователю) обсуждение в Системе взаимодействия и подписывается на событие получения новых сообщений.
  2. Серверное фоновое задание отправляет сообщения в это обсуждение по мере выполнения работы.
  3. На клиенте мгновенно срабатывает обработчик события. Клиент принимает сообщение, извлекает текст JSON и записывает его в локальный файл.

Этот подход полностью устраняет лишнюю нагрузку на сервер и обеспечивает моментальное (с точностью до миллисекунд) логирование действий фонового процесса на клиентском ПК.

Резюме

Для реализации параллельного логирования на клиенте лучшим архитектурным решением является связка «JSON Lines + Сообщения пользователю (или СообщитьПрогресс БСП)». Такой подход гарантирует сохранность лога в структурированном виде при любых аварийных сбоях и обеспечивает высокую производительность системы без перегрузки оперативной памяти.

← На главную