Как программно фиксировать ошибки в журнале регистрации 1С:Предприятия 8.3

· 9 мин

Содержание

Ошибку в журнал регистрации 1С записывают методом глобального контекста ЗаписьЖурналаРегистрации() с уровнем УровеньЖурналаРегистрации.Ошибка. Метод работает на сервере, в толстом клиенте и во внешнем соединении. Из тонкого клиента его вызвать нельзя: там запись идёт через серверный вызов или через механизм БСП.

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

Зачем писать ошибки в журнал регистрации, а не в сообщения

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

Что даёт своя запись в журнал:

  • 🔍 Разбор задним числом. Пользователь говорит «вчера не провелось», а в журнале видно, какой документ, в какую секунду и с каким текстом исключения.
  • 🧭 Привязка к данным. В поле «Данные» лежит ссылка на документ. Из журнала можно открыть его одним щелчком.
  • 🌙 Контроль фоновых процессов. Обмены, загрузки банка, регламентные задания работают без пользователя. Журнал — единственный свидетель.
  • 📊 Статистика. Отбор по своему имени события показывает, как часто падает конкретная операция.

Платформа и так пишет в журнал много служебного: входы, запись объектов, ошибки сеансов. Но текст прикладного исключения с вашим контекстом (номер строки файла, код контрагента, ответ сервиса) туда не попадёт, пока вы не запишете его сами.

Метод ЗаписьЖурналаРегистрации: параметры и уровни

Синтаксис:

ЗаписьЖурналаРегистрации(ИмяСобытия, Уровень, ОбъектМетаданных, Данные, Комментарий, РежимТранзакции);
ПараметрЧто передаватьОбязательный
ИмяСобытияСтрока, ваше имя событияДа
УровеньЗначение УровеньЖурналаРегистрацииНет, по умолчанию «Информация»
ОбъектМетаданныхОбъект метаданных, например Метаданные.Документы.РеализацияТоваровУслугНет
ДанныеСсылка или другое значение, к которому относится событиеНет
КомментарийТекст ошибки и контекстНет
РежимТранзакцииРежимТранзакцииЗаписиЖурналаРегистрации.Независимая или .ТранзакционнаяНет, по умолчанию независимая

Уровни журнала, от самого важного:

  1. Ошибка — операция не выполнилась.
  2. Предупреждение — выполнилась, но с отклонением (пропущена строка файла, не найден контрагент).
  3. Информация — штатное событие, полезное для истории (обмен завершён, загружено 120 строк).
  4. Примечание — подробности для отладки.

Имя события задавайте иерархически, через точку: ОбменСБанком.ЗагрузкаВыписки, ИнтеграцияСайт.ВыгрузкаЗаказов. Так события одной подсистемы стоят рядом в списке отбора, и их легко выбрать группой.

Базовый пример: перехват исключения

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

ПодробноеПредставлениеОшибки() даёт текст исключения вместе со стеком вызовов: модуль, номер строки, цепочку процедур. Для журнала это лучший вариант. Пользователю показывайте КраткоеПредставлениеОшибки() — без стека.

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

В старых модулях встречается ОписаниеОшибки(). Он возвращает только текст, без стека, поэтому по нему трудно найти место падения. В новом коде используйте пару ИнформацияОбОшибке() + ПодробноеПредставлениеОшибки().

Добавляем контекст в комментарий

Текст исключения часто звучит как Поле объекта не обнаружено (Количество). Без контекста он бесполезен. Добавьте, что именно обрабатывали:

Комментарий = СтрШаблон("Файл: %1; строка: %2; ИНН: %3
    |%4",
    ИмяФайла,
    Формат(НомерСтроки, "ЧГ=0"),
    ИННКонтрагента,
    ПодробноеПредставлениеОшибки(ИнфоОбОшибке));

Формат(…, "ЧГ=0") убирает пробел-разделитель групп, иначе номер строки 1250 превратится в «1 250», и поиск по комментарию его не найдёт.

Не пишите в комментарий пароли, токены, полные номера карт и паспортные данные. Журнал видят все, у кого есть право на его просмотр, и он долго хранится.

Запись ошибок из клиентского кода и в клиент-серверном режиме

В клиент-серверной базе журнал ведёт сервер 1С. В файловой — сам процесс 1С пишет в каталог 1Cv8Log рядом с файлом базы. Для разработчика разница одна: в тонком и веб-клиенте метода ЗаписьЖурналаРегистрации() нет. Попытка вызвать его в процедуре &НаКлиенте даст ошибку при проверке модуля.

Варианта два.

Вариант 1. Свой серверный вызов

&НаКлиенте
Процедура ВыполнитьОбмен(Команда)
    Попытка
        ОтправитьЗапрос();
    Исключение
        ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
        ЗаписатьОшибкуНаСервере(ТекстОшибки);
        ПоказатьПредупреждение(, "Обмен не выполнен. Подробности в журнале регистрации.");
    КонецПопытки;
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ЗаписатьОшибкуНаСервере(ТекстОшибки)
    ЗаписьЖурналаРегистрации("ОбменССайтом.Ошибка",
        УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
КонецПроцедуры

&НаСервереБезКонтекста не тащит на сервер данные формы, вызов получается лёгким.

Вариант 2. Механизм БСП

В типовых конфигурациях на БСП (Бухгалтерия 3.0, ЗУП 3.1, УТ 11 и другие) есть готовая пара: на клиенте события копятся в список, потом уходят на сервер одним вызовом.

// в клиентском обработчике
ЖурналРегистрацииКлиент.ДобавитьСообщениеДляЖурналаРегистрации(
    "ОбменССайтом.Ошибка", "Ошибка", ТекстОшибки, , Истина);

Последний параметр Истина отправляет накопленные сообщения сразу. Если передать Ложь, они уйдут позже, вместе со следующим серверным вызовом подсистемы. Имя уровня здесь передаётся строкой: «Ошибка», «Предупреждение», «Информация», «Примечание».

В расширениях и доработках типовых предпочтительнее вариант БСП: он уже учитывает особенности веб-клиента и не требует своего серверного модуля.

Ошибки в фоновых и регламентных заданиях

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

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

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

ВызватьИсключение без параметров повторяет исходное исключение. Задание получает статус «Завершено аварийно», и это видно в списке регламентных заданий. Если «проглотить» ошибку, задание будет числиться успешным, а данные не загрузятся.

Когда задание запускает форма, ошибку можно забрать и в вызывающем коде. У объекта ФоновоеЗадание есть свойство ИнформацияОбОшибке: после аварийного завершения в нём лежит исключение задания.

Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИдентификаторЗадания);
Если Задание <> Неопределено
    И Задание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно Тогда
    ЗаписьЖурналаРегистрации("ДлительнаяОперация.Ошибка",
        УровеньЖурналаРегистрации.Ошибка, , ,
        ПодробноеПредставлениеОшибки(Задание.ИнформацияОбОшибке));
КонецЕсли;

Для длинных циклов в фоне (загрузка 10 000 строк) не пишите событие на каждую строку. Копите ошибки в массив и в конце записывайте одно событие уровня «Предупреждение» со сводкой: сколько строк обработано, сколько пропущено и первые 20–30 причин.

Режим транзакции: когда запись должна откатываться

По умолчанию запись в журнал независимая. Если код упал внутри транзакции и она откатилась, событие в журнале останется. Для ошибок это именно то, что нужно: данные не записались, а след причины сохранился.

Режим РежимТранзакцииЗаписиЖурналаРегистрации.Транзакционная делает запись частью транзакции. Событие появится в журнале только при её фиксации и исчезнет при откате.

Что пишемРежимПочему
Ошибку, из-за которой откатывается транзакцияНезависимаяИначе запись откатится вместе с данными, и ошибки «не было»
Факт успешного изменения данных («цены обновлены у 340 позиций»)ТранзакционнаяЗапись не должна остаться, если изменения откатились
Предупреждения внутри цикла с откатомНезависимаяНужно видеть, на какой строке начались проблемы

Классическая ловушка: запись об успехе в независимом режиме внутри транзакции. Транзакция потом откатывается, а в журнале висит «Загружено успешно». Такой журнал вводит в заблуждение сильнее, чем пустой.

Как найти свои записи: отбор, выгрузка и настройка журнала

Просмотр в интерфейсе

В Бухгалтерии 3.0, ЗУП 3.1 и УТ 11 журнал открывается так: Администрирование → Обслуживание → Журнал регистрации. В Конфигураторе — Администрирование → Журнал регистрации.

Чтобы отсечь служебный шум:

  1. Нажмите Установить отбор.
  2. В поле «Событие» выберите только свои события. Иерархические имена с точкой здесь и окупаются: вся подсистема выбирается сразу.
  3. В «Уровне» оставьте «Ошибка» и «Предупреждение».
  4. Сузьте интервал дат до суток, когда случился сбой.

Двойной щелчок по строке открывает полный комментарий с текстом исключения и стеком.

Выгрузка журнала из кода

Для отчётов и отправки во внешние системы журнал читают методом ВыгрузитьЖурналРегистрации():

Отбор = Новый Структура;
Отбор.Вставить("ДатаНачала", НачалоДня(ТекущаяДатаСеанса()) - 86400);
Отбор.Вставить("ДатаОкончания", ТекущаяДатаСеанса());
Отбор.Вставить("Уровень", УровеньЖурналаРегистрации.Ошибка);

События = Новый ТаблицаЗначений;
ВыгрузитьЖурналРегистрации(События, Отбор);

Для Каждого Строка Из События Цикл
    // Строка.Дата, Строка.Событие, Строка.Комментарий
КонецЦикла;

Здесь отбор берёт все ошибки со вчерашнего начала дня по текущий момент. В рабочей базе журнал бывает огромным, поэтому всегда ограничивайте период: выгрузка без отбора по датам может работать минутами.

Настройка уровня регистрации

В Конфигураторе: Администрирование → Настройка журнала регистрации. Здесь задают, какие события платформа вообще сохраняет, и как часто делить журнал на файлы.

Если выбран уровень «Регистрировать ошибки», ваши записи уровня «Информация» в журнал не попадут. Код отработает без исключений, а событий не будет. Это частая причина фразы «логирование не работает ».

Диагностика: выберите симптом

Отправка ошибок 1С во внешние системы логирования

Журнал регистрации живёт внутри одной базы. Если баз десяток, удобнее собирать ошибки в одно место: Graylog, Elasticsearch с Kibana, Grafana Loki или любой другой сборщик.

Три рабочих подхода:

ПодходКак устроенПлюсыМинусы
Отправка из кода в момент ошибкиРядом с ЗаписьЖурналаРегистрации() вызывается HTTP-запрос к сборщикуОшибка видна сразуЕсли сборщик недоступен, нужен таймаут, иначе зависнет основной код
Регламентная выгрузкаЗадание раз в 5–10 минут читает журнал через ВыгрузитьЖурналРегистрации() и отправляет новые записиОсновной код не трогаемЗадержка до следующего запуска
Внешний парсер файлов журналаОтдельная программа читает каталог 1Cv8Log и шлёт события в сборщикНе нагружает сервер 1С кодомНужен доступ к файлам сервера и отдельный сервис

Пример отправки в Graylog через HTTP-вход формата GELF (путь /gelf):

Функция ОтправитьВGraylog(ИмяСобытия, ТекстОшибки)
    Данные = Новый Структура;
    Данные.Вставить("version", "1.1");
    Данные.Вставить("host", ИмяКомпьютера());
    Данные.Вставить("short_message", ИмяСобытия);
    Данные.Вставить("full_message", ТекстОшибки);
    Данные.Вставить("level", 3);
    Данные.Вставить("_infobase", СтрокаСоединенияИнформационнойБазы());

    Запись = Новый ЗаписьJSON;
    Запись.УстановитьСтроку();
    ЗаписатьJSON(Запись, Данные);
    Тело = Запись.Закрыть();

    Соединение = Новый HTTPСоединение("graylog.local", 12201, , , , 5);
    Запрос = Новый HTTPЗапрос("/gelf");
    Запрос.Заголовки.Вставить("Content-Type", "application/json");
    Запрос.УстановитьТелоИзСтроки(Тело, КодировкаТекста.UTF8);

    Попытка
        Ответ = Соединение.ОтправитьДляОбработки(Запрос);
        Возврат Ответ.КодСостояния = 202;
    Исключение
        Возврат Ложь;
    КонецПопытки;
КонецФункции

Уровень 3 в GELF соответствует «error» по шкале syslog. Последний параметр конструктора HTTPСоединение — таймаут в секундах: 5 секунд не дадут проведению документа зависнуть, если сервер логов лёг. Если отправка не удалась, ошибка всё равно останется в журнале регистрации — его не заменяйте внешней системой, а дублируйте.

Хост, порт и флаг «отправлять или нет» храните в константах или настройках, а не в коде. Тогда тестовая копия базы не будет слать ошибки в боевой сборщик.

Частые вопросы

Чем журнал регистрации отличается от технологического журнала?
Журнал регистрации хранит прикладные события конкретной базы, его можно читать из 1С и писать в него кодом. Технологический журнал включают на сервере файлом logcfg.xml, он фиксирует работу платформы: вызовы, блокировки, запросы СУБД. Писать в него из встроенного языка нельзя.
Сколько хранится журнал регистрации?
Пока его не сократят вручную. В Конфигураторе в окне журнала есть команда сокращения журнала: старые записи удаляются или выгружаются в файл. Для больших баз это стоит делать регулярно, с выгрузкой архива.
Можно ли записать в журнал событие от имени другого пользователя?
Нет. Платформа сама подставляет пользователя текущего сеанса. Если операция выполняется от имени регламентного задания, в журнале будет пользователь, указанный в его настройках, или пустое значение.
Нужны ли права, чтобы писать в журнал регистрации?
Для записи кодом отдельное право не требуется. Право нужно для просмотра журнала: без него пользователь не откроет журнал, но события его сеанса всё равно запишутся.
Как записать ошибку из внешней обработки?
Так же, как из конфигурации: в серверной процедуре модуля обработки вызовите ЗаписьЖурналаРегистрации(). Параметр ОбъектМетаданных оставьте пустым, а в имени события укажите название обработки.