В современных информационных системах, особенно в 1С:Предприятии, часто возникает задача аудита действий пользователей. Одна из таких задач – отслеживание, какие именно формы справочников и документов открывает пользователь, включая администраторов, не внося при этом изменений в данные. Это может быть необходимо для анализа активности, выявления злоупотреблений, оценки эффективности использования функционала или для сбора статистики. Давайте подробно разберем несколько подходов к решению этой проблемы, обсудим их преимущества, недостатки и особенности реализации.
Этот метод является одним из наиболее гибких, централизованных и предпочтительных для реализации аудита открываемых форм, особенно если мы хотим избежать прямого изменения типовой конфигурации. Он позволяет нам перехватывать процесс получения формы системой до того, как она будет отображена пользователю.
Суть метода: Мы создаем подписку на событие ОбработкаПолученияФормы. Это событие вызывается каждый раз, когда система готовится открыть какую-либо форму. В обработчике этой подписки мы получаем всю необходимую информацию об открываемой форме и можем записать ее в специально созданный регистр сведений.
Переходим в конфигураторе к объекту метаданных Подписки на события. Создаем новую подписку. В качестве источника события укажем все типы объектов, для которых нам нужен аудит открытия форм. Это могут быть:
ФормаОбъекта (для справочников, документов, планов видов характеристик и т.д.)ФормаСписка (для списков справочников, документов, регистров сведений и т.д.)ФормаВыбора (для форм выбора элементов)ФормаДокумента (для отдельных форм документов)ФормаОтчета (для форм отчетов)ФормаОбработки (для форм обработок)Для события выбираем ОбработкаПолученияФормы.
В качестве обработчика подписки указываем экспортную процедуру в общем модуле. Например, мы можем создать новый общий модуль АудитОткрытияФорм и в нем процедуру ОбработатьПолучениеФормы(ИмяФормы, Параметры, СтандартнаяОбработка). Этот общий модуль желательно сделать серверным, чтобы запись в регистр сведений выполнялась на сервере, минимизируя нагрузку на клиент и обеспечивая надежность записи.
В процедуре обработчика мы получаем следующие параметры:
ИмяФормы (тип Строка) – полное строковое имя открываемой формы, например, "Документ.РеализацияТоваровУслуг.ФормаДокумента".Параметры (тип Структура) – структура с параметрами, переданными при открытии формы. В ней часто содержится ключ объекта (например, ссылка на элемент справочника или документ), для которого открывается форма.СтандартнаяОбработка (тип Булево) – позволяет отменить стандартное открытие формы, но для целей аудита это нам не требуется.Из параметра ИмяФормы мы можем получить полное имя открываемой формы. Чтобы получить объект метаданных формы, мы можем использовать функцию Метаданные.НайтиПоПолномуИмени(ИмяФормы). Это позволит нам узнать, к какому объекту метаданных относится данная форма (например, к справочнику "Номенклатура" или документу "РеализацияТоваровУслуг").
Из параметра Параметры мы можем определить, открывается ли форма для конкретного объекта. Например, если в структуре Параметры есть свойство Ключ или КлючОбъекта, и оно заполнено, это будет ссылка на конкретный элемент или документ. Для форм списков в Параметрах может содержаться отбор, который также можно проанализировать для более детальной информации.
Создадим новый регистр сведений, например, ЖурналОткрытияФорм. Его измерениями могут быть:
Пользователь (тип СправочникСсылка.Пользователи)ДатаВремя (тип ДатаВремя)ИмяФормы (тип Строка, для хранения полного строкового имени формы)МетаданныеФормы (тип Строка, для хранения имени объекта метаданных, к которому относится форма, например, "Справочник.Номенклатура")СсылкаНаОбъект (тип Характеристика.ВидыОбъектовАдресации или составной тип данных, включающий все нужные ссылки на документы и справочники)КонтекстОткрытия (тип Строка, для записи дополнительных сведений, например, сериализованного отбора для форм списков).В обработчике подписки мы будем создавать новую запись в этом регистре, заполняя ее полученными данными.
Пример кода для обработчика (обобщенный):
// Общий модуль: АудитОткрытияФорм (Серверный)
Процедура ОбработатьПолучениеФормы(ИмяФормы, Параметры, СтандартнаяОбработка) Экспорт
Если Не ОтключенАудитОткрытияФорм() Тогда // Проверка для возможности временного отключения
// Получаем информацию о текущем пользователе
ТекущийПользователь = Пользователи.ТекущийПользователь();
Если ТекущийПользователь.Пустая() Тогда
// Если пользователь не определен (например, фоновое задание без пользователя),
// можно пропустить запись или записать специальное значение.
Возврат;
КонецЕсли;
// Получаем объект метаданных формы для более детальной информации
// МетаданныеФормы может быть Неопределено, если форма не связана напрямую с объектом метаданных
МетаданныеФормы = Метаданные.НайтиПоПолномуИмени(ИмяФормы);
// Определяем ссылку на объект, если форма открывается для конкретного элемента
СсылкаНаОбъект = Неопределено;
КонтекстОткрытия = "";
Если ТипЗнч(Параметры) = Тип("Структура") Тогда
Если Параметры.Свойство("Ключ") И ЗначениеЗаполнено(Параметры.Ключ) Тогда
СсылкаНаОбъект = Параметры.Ключ;
КонтекстОткрытия = "Открыта форма объекта по ключу: " + СсылкаНаОбъект;
ИначеЕсли Параметры.Свойство("КлючОбъекта") И ЗначениеЗаполнено(Параметры.КлючОбъекта) Тогда
СсылкаНаОбъект = Параметры.КлючОбъекта;
КонтекстОткрытия = "Открыта форма объекта по ключу объекта: " + СсылкаНаОбъект;
ИначеЕсли Параметры.Свойство("Отбор") И ТипЗнч(Параметры.Отбор) = Тип("ОтборКомпоновщикаДанных") Тогда
// Для форм списков с отбором можно сериализовать отбор или получить его основные параметры
КонтекстОткрытия = "Открыта форма списка с отбором: " + СериализаторXDTO.Записать(Параметры.Отбор);
ИначеЕсли Параметры.Свойство("Вид") И Параметры.Свойство("Ключ") Тогда // Для форм планов видов характеристик
СсылкаНаОбъект = Параметры.Ключ;
КонтекстОткрытия = "Открыта форма элемента плана видов характеристик: " + СсылкаНаОбъект;
КонецЕсли;
КонецЕсли;
// Записываем данные в регистр сведений
Попытка
НоваяЗапись = РегистрыСведений.ЖурналОткрытияФорм.СоздатьМенеджерЗаписи();
НоваяЗапись.Пользователь = ТекущийПользователь.Ссылка;
НоваяЗапись.ДатаВремя = ТекущаяДатаСеанса();
НоваяЗапись.ИмяФормы = ИмяФормы;
// Имя метаданных будет более читабельным, чем полное имя формы
НоваяЗапись.МетаданныеФормы = ?(МетаданныеФормы <> Неопределено, МетаданныеФормы.ПолноеИмя(), "");
НоваяЗапись.СсылкаНаОбъект = СсылкаНаОбъект;
НоваяЗапись.КонтекстОткрытия = КонтекстОткрытия;
НоваяЗапись.Записать();
Исключение
// В случае ошибки записи (например, проблемы с БД или неверный тип данных)
// записываем информацию в журнал регистрации, чтобы не прерывать работу пользователя
ЗаписатьВЖурналРегистрации("Ошибка записи в журнал открытия форм: " + ОписаниеОшибки(), УровеньЖурналаРегистрации.Ошибка);
КонецПопытки;
КонецЕсли;
КонецПроцедуры
// Пример вспомогательной функции для возможности временного отключения аудита
Функция ОтключенАудитОткрытияФорм() Экспорт
// Можно реализовать механизм настройки, например, константу или параметр сеанса
// Например, если есть константа "ИспользоватьАудитОткрытияФорм" типа Булево
// Возврат Не Константы.ИспользоватьАудитОткрытияФорм.Получить();
Возврат Ложь; // По умолчанию аудит включен
КонецФункции
Преимущества этого подхода:
Недостатки и особенности:
ОбработкаПолученияФормы происходит до того, как форма реально отобразится пользователю и загрузит все данные. Это означает, что мы фиксируем попытку открытия, а не факт полного просмотра всей информации в форме или взаимодействия с ней.Другой подход заключается во внедрении кода аудита непосредственно в процедуры инициализации форм. Этот метод полезен, если нам нужен более детальный контроль, если мы хотим охватить формы, которые по каким-то причинам не обрабатываются подпиской на ОбработкаПолученияФормы, или если нам нужна информация, доступная только внутри контекста самой формы.
Суть метода: Мы ищем общие точки входа при создании форм на сервере и добавляем туда свой код, который будет записывать информацию об открытии.
Во многих типовых конфигурациях существует практика вызова общих процедур из общих модулей при создании форм. Например, часто используется вызов ПодключаемыеКоманды.ПриСозданииНаСервере(Форма) или подобные механизмы инициализации. Если такая централизованная точка есть и она охватывает большинство интересующих нас форм, мы можем внести изменения именно в эту процедуру (например, через замещение в расширении).
Для форм, которые не используют общие модули или для которых требуется особая логика, можно добавлять код непосредственно в процедуру формы ПриСозданииНаСервере или ПриОткрытии. ПриСозданииНаСервере предпочтительнее, так как она выполняется на сервере, что удобно для записи в регистры сведений.
В процедуре ПриСозданииНаСервере мы имеем доступ к объекту формы (например, параметр Форма). Используя свойство Форма.ИмяФормы, мы можем получить уникальное строковое имя формы. Также доступны свойства Форма.Объект (если форма является формой объекта) или Форма.Отбор (для списков), что дает нам более глубокий контекст.
Аналогично предыдущему методу, мы собираем информацию о текущем пользователе, имени формы, связанном объекте и записываем ее в регистр сведений ЖурналОткрытияФорм.
Пример кода (в процедуре общего модуля, вызываемого из `ПриСозданииНаСервере`, или напрямую в `ПриСозданииНаСервере` формы):
// Общий модуль: ДополнительныйАудитФорм (Серверный)
// Процедура может быть вызвана из ПриСозданииНаСервере форм
Процедура ПриСозданииНаСервереДополнительныйАудит(Форма) Экспорт
Если Не ОтключенАудитОткрытияФорм() Тогда // Проверка для возможности временного отключения
// Получаем информацию о текущем пользователе
ТекущийПользователь = Пользователи.ТекущийПользователь();
Если ТекущийПользователь.Пустая() Тогда
Возврат;
КонецЕсли;
// Имя формы
ИмяФормы = Форма.ИмяФормы;
// Объект метаданных формы
МетаданныеФормыОбъект = Форма.Метаданные();
МетаданныеФормыИмя = МетаданныеФормыОбъект.ПолноеИмя();
// Ссылка на объект (если форма объектная или списочная)
СсылкаНаОбъект = Неопределено;
КонтекстОткрытия = "";
Если Форма.Свойство("Объект") И ТипЗнч(Форма.Объект) <> Тип("РегистрСведенийНаборЗаписей") Тогда
// Для объектных форм - сама ссылка на объект
СсылкаНаОбъект = Форма.Объект;
КонтекстОткрытия = "Форма объекта: " + СсылкаНаОбъект;
ИначеЕсли Форма.Свойство("Отбор") И ТипЗнч(Форма.Отбор) = Тип("ОтборКомпоновщикаДанных") Тогда
// Для форм списка можно анализировать отбор
КонтекстОткрытия = "Форма списка с отбором: " + СериализаторXDTO.Записать(Форма.Отбор);
ИначеЕсли Форма.Свойство("ПараметрыДанных") Тогда // Для некоторых форм отчетов или обработок
КонтекстОткрытия = "Форма с параметрами данных: " + СериализаторXDTO.Записать(Форма.ПараметрыДанных);
КонецЕсли;
// Записываем данные в регистр сведений
Попытка
НоваяЗапись = РегистрыСведений.ЖурналОткрытияФорм.СоздатьМенеджерЗаписи();
НоваяЗапись.Пользователь = ТекущийПользователь.Ссылка;
НоваяЗапись.ДатаВремя = ТекущаяДатаСеанса();
НоваяЗапись.ИмяФормы = ИмяФормы;
НоваяЗапись.МетаданныеФормы = МетаданныеФормыИмя;
НоваяЗапись.СсылкаНаОбъект = СсылкаНаОбъект;
НоваяЗапись.КонтекстОткрытия = КонтекстОткрытия;
НоваяЗапись.Записать();
Исключение
ЗаписатьВЖурналРегистрации("Ошибка записи в журнал открытия форм из ПриСозданииНаСервере: " + ОписаниеОшибки(), УровеньЖурналаРегистрации.Ошибка);
КонецПопытки;
КонецЕсли;
КонецПроцедуры
// Вспомогательная функция, такая же как и для подписки
Функция ОтключенАудитОткрытияФорм() Экспорт
// Возврат Не Константы.ИспользоватьАудитОткрытияФорм.Получить();
Возврат Ложь;
КонецФункции
Преимущества этого подхода:
ПриСозданииНаСервере мы имеем полный доступ к свойствам и методам объекта формы, включая ее данные и элементы управления, что позволяет получить более точный контекст.ОбработкаПолученияФормы или требуют особой логики, этот подход позволяет точечно внедрить аудит.Недостатки и особенности:
Независимо от выбранного метода, существуют общие рекомендации, которые помогут нам создать эффективную, производительную и надежную систему аудита открытия форм.
ЖурналОткрытияФорм фоновым заданием. Это значительно снизит пиковую нагрузку на базу данных.СсылкаНаОбъект используйте составной тип данных, который включает все справочники и документы, формы которых мы хотим отслеживать. Если объектов много, можно использовать Характеристика.ВидыОбъектовАдресации, но это может усложнить прямые связи в запросах.Предусмотрите возможность быстрого включения/отключения аудита через константу, параметр сеанса или отдельную форму настроек. Это позволит временно остановить запись данных при возникновении проблем с производительностью, при выполнении регламентных работ или для целенаправленного тестирования, не затрагивая основной код.
Мы настоятельно рекомендуем реализовывать все доработки по аудиту через расширения конфигурации. Это значительно упрощает поддержку системы, позволяет обновлять типовую конфигурацию без конфликтов и сохраняет возможность работы системы аудита даже после обновлений. Для подписок на события и замещения процедур ПриСозданииНаСервере расширения являются идеальным инструментом.
Хотя технологический журнал 1С:Предприятия не предназначен для прямого аудита открытий форм в реальном времени, он может служить вспомогательным инструментом для диагностики. В нем фиксируются различные события, включая вызовы серверных процедур (CALL), что может косвенно указывать на активность форм. Однако парсинг и анализ этих данных для детального и оперативного аудита будет достаточно сложным и ресурсоемким, и обычно используется для диагностики производительности, а не для аудита действий пользователей.
Для клиент-серверных вариантов работы, особенно на Microsoft SQL Server, существуют механизмы аудита СУБД (например, SQL Server Audit). Они позволяют отслеживать обращения к таблицам базы данных. Но этот подход очень низок, сложен в настройке и интерпретации, поскольку напрямую не связывает действия с пользовательскими формами 1С, а лишь фиксирует обращения к данным. Понимание того, какой именно пользователь 1С и какую именно форму открыл, основываясь только на SQL-запросах, практически невозможно без глубокого знания внутренней структуры БД 1С и сопоставления сессий.
Сообщение 5 поднимает важный вопрос: "вот вообще не совсем верно отслеживать то, какой отчет кто и когда открывал. Важнее - с какими настройками он его формировал!!!" Этот подход можно расширить и на формы. Помимо факта открытия формы, возможно, вам потребуется отслеживать:
Однако, реализация такого уровня детализации значительно усложняет задачу и требует еще более глубокой интеграции в логику форм, возможно, с использованием дополнительных подписок на события элементов формы или изменением их обработчиков.
Выбор конкретного метода зависит от требуемой детализации аудита, объема информационной базы, количества пользователей и готовности к изменениям в конфигурации. На наш взгляд, подписка на событие ОбработкаПолученияФормы, реализованная через расширение, с записью данных в оптимизированный регистр сведений, часто является наиболее сбалансированным и рекомендуемым решением, обеспечивающим хороший компромисс между функциональностью, производительностью и простотой поддержки.