Как программно фиксировать ошибки в журнале регистрации 1С:Предприятия 8.3
Содержание
Ошибку в журнал регистрации 1С записывают методом глобального контекста ЗаписьЖурналаРегистрации() с уровнем УровеньЖурналаРегистрации.Ошибка. Метод работает на сервере, в толстом клиенте и во внешнем соединении. Из тонкого клиента
его вызвать нельзя: там запись идёт через серверный вызов или через механизм БСП.
Ниже готовые фрагменты кода для каждого случая: обработчик Попытка … Исключение, клиентская форма, фоновое задание, регламентное задание. Плюс как потом найти свои записи среди тысяч служебных событий и как отправить их во внешнюю систему логов. Всё сказанное относится к платформе 8.3 и не зависит от конфигурации.
Зачем писать ошибки в журнал регистрации, а не в сообщения
Сообщить() показывает текст пользователю и исчезает вместе с окном. Если ошибка случилась ночью в обмене или в регламентном задании, сообщение никто не увидит. Журнал регистрации хранит событие с датой, пользователем, компьютером, сеансом и ссылкой на объект.
Что даёт своя запись в журнал:
- 🔍 Разбор задним числом. Пользователь говорит «вчера не провелось», а в журнале видно, какой документ, в какую секунду и с каким текстом исключения.
- 🧭 Привязка к данным. В поле «Данные» лежит ссылка на документ. Из журнала можно открыть его одним щелчком.
- 🌙 Контроль фоновых процессов. Обмены, загрузки банка, регламентные задания работают без пользователя. Журнал — единственный свидетель.
- 📊 Статистика. Отбор по своему имени события показывает, как часто падает конкретная операция.
Платформа и так пишет в журнал много служебного: входы, запись объектов, ошибки сеансов. Но текст прикладного исключения с вашим контекстом (номер строки файла, код контрагента, ответ сервиса) туда не попадёт, пока вы не запишете его сами.
Метод ЗаписьЖурналаРегистрации: параметры и уровни
Синтаксис:
ЗаписьЖурналаРегистрации(ИмяСобытия, Уровень, ОбъектМетаданных, Данные, Комментарий, РежимТранзакции);
| Параметр | Что передавать | Обязательный |
|---|---|---|
| ИмяСобытия | Строка, ваше имя события | Да |
| Уровень | Значение УровеньЖурналаРегистрации | Нет, по умолчанию «Информация» |
| ОбъектМетаданных | Объект метаданных, например Метаданные.Документы.РеализацияТоваровУслуг | Нет |
| Данные | Ссылка или другое значение, к которому относится событие | Нет |
| Комментарий | Текст ошибки и контекст | Нет |
| РежимТранзакции | РежимТранзакцииЗаписиЖурналаРегистрации.Независимая или .Транзакционная | Нет, по умолчанию независимая |
Уровни журнала, от самого важного:
- Ошибка — операция не выполнилась.
- Предупреждение — выполнилась, но с отклонением (пропущена строка файла, не найден контрагент).
- Информация — штатное событие, полезное для истории (обмен завершён, загружено 120 строк).
- Примечание — подробности для отладки.
Имя события задавайте иерархически, через точку: ОбменСБанком.ЗагрузкаВыписки, ИнтеграцияСайт.ВыгрузкаЗаказов. Так события одной подсистемы стоят рядом в списке отбора, и их легко выбрать группой.
Базовый пример: перехват исключения
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ИнфоОбОшибке = ИнформацияОбОшибке();
ЗаписьЖурналаРегистрации("ЗагрузкаДокументов.Проведение",
УровеньЖурналаРегистрации.Ошибка,
ДокументОбъект.Метаданные(),
ДокументОбъект.Ссылка,
ПодробноеПредставлениеОшибки(ИнфоОбОшибке));
КонецПопытки;
ПодробноеПредставлениеОшибки() даёт текст исключения вместе со стеком вызовов: модуль, номер строки, цепочку процедур. Для журнала это лучший вариант. Пользователю показывайте КраткоеПредставлениеОшибки() — без стека.
Сохраняйте ИнформацияОбОшибке() в переменную первой строкой блока Исключение. Если до этого вызвать другой код, который сам перехватит исключение, исходная информация об ошибке будет потеряна.
В старых модулях встречается ОписаниеОшибки(). Он возвращает только текст, без стека, поэтому по нему трудно найти место падения. В новом коде используйте пару ИнформацияОбОшибке() + ПодробноеПредставлениеОшибки().
Добавляем контекст в комментарий
Текст исключения часто звучит как Поле объекта не обнаружено (Количество). Без контекста он бесполезен. Добавьте, что именно обрабатывали:
Комментарий = СтрШаблон("Файл: %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 журнал открывается так: Администрирование → Обслуживание → Журнал регистрации. В Конфигураторе — Администрирование → Журнал регистрации.
Чтобы отсечь служебный шум:
- Нажмите Установить отбор.
- В поле «Событие» выберите только свои события. Иерархические имена с точкой здесь и окупаются: вся подсистема выбирается сразу.
- В «Уровне» оставьте «Ошибка» и «Предупреждение».
- Сузьте интервал дат до суток, когда случился сбой.
Двойной щелчок по строке открывает полный комментарий с текстом исключения и стеком.
Выгрузка журнала из кода
Для отчётов и отправки во внешние системы журнал читают методом ВыгрузитьЖурналРегистрации():
Отбор = Новый Структура;
Отбор.Вставить("ДатаНачала", НачалоДня(ТекущаяДатаСеанса()) - 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 секунд не дадут проведению документа зависнуть, если сервер логов лёг. Если отправка не удалась, ошибка всё равно останется в журнале регистрации — его не заменяйте внешней системой, а дублируйте.
Хост, порт и флаг «отправлять или нет» храните в константах или настройках, а не в коде. Тогда тестовая копия базы не будет слать ошибки в боевой сборщик.