В предыдущий раз (Автоматизация восстановления последовательности. Часть 2) я определил способ хранения логов, руководствуясь основным свойством любого ИТ процесса: сотрудник ИТ должен в любое время точно и однозначно уметь сказать, что именно делает система, за которую он отвечает. И мой выбор остановился на хранении логов работы системы 1С в выделенной базе SQL Server, для данного примера – это будет SQL Server 2008 R2.
На стороне SQL Server хранимая процедура достаточно простая – внутри транзакции в блоке TRY-CATCH вставка в таблицу записи в соответствии с переданными параметрами. Единственная особенность – обработка значения XML: при конвертации с помощью средств SQL (cast(@ErrorXML as XML)) все недопустимые для XML символы автоматически заменяются, что делает ненужным дополнительные замены на стороне 1С. Процедура в случае успешного завершения возвращает 0 – иначе текст ошибки.
ALTER PROCEDURE dbo.Log_AddError (
@UserName varchar(64),
@ErrorTypeID int,
@ErrorMessage varchar(max),
@StackPath varchar(max),
@ErrorLog varchar(max),
@ErrorXML varchar(max)
)
AS
set nocount on
set transaction isolation level repeatable read
BEGIN TRANSACTION;
BEGIN TRY
insert into dbo.Log_Errors (ErrorDate, UserName, ErrorTypeID, ErrorMessage, StackPath, ErrorLog, ErrorXML)
values
(GETDATE(), @UserName, @ErrorTypeID, @ErrorMessage, @StackPath, @ErrorLog, case when @ErrorXML = '' then null else cast(@ErrorXML as XML) end)
END TRY
BEGIN CATCH
IF @@TRANCOUNT > 0
ROLLBACK TRANSACTION;
SELECT ERROR_MESSAGE()
END CATCH;
IF @@TRANCOUNT > 0 BEGIN
COMMIT TRANSACTION;
select '0'
END
Переходя к вызову приведенной выше процедуры, необходимо сказать о некоторых особенностях платформы 1С, которые наложат определенные ограничения на реализацию записи логов системы в базу данных SQL:
- Нет полноценной поддержки механизма ADO, поэтому вся транзакционная логика будет на SQL Server – задача 1С будет состоять только в вызове необходимой процедуры и передачи в нее параметров. При этом драйвер используемый 1С достаточно старый и не поддерживает многих особенностей последних версий SQL Server.
- В блок Try-Catch нет секции Finally, что добавляет определенную специфику в закрытии за собой соединений с базой данных.
- Нет человеческой поддержки формата XML. То, что в плане XML реализовано в 1С – пугает ))).
- 1С является трехзвенной системой, только при работе со своей базой, во всех остальных случаях – средний слой как такой исчезает. Поэтому доступ к SQL Server под Integrated Security невозможен – так как в этом случае доступ к базе придется давать всем пользователям 1С, что достаточно опасно.
Прежде чем привести код вызова процедуры на стороне SQL из 1С – кратко опишу ее основные особенности и логику работы:
- Строку соединения лучше хранить в константах – это позволит получить управляемое решения для разных сред без необходимости корректировать исходный код.
- Timeout для работы с SQLServer должен быть выставлен максимум в 2 секунды – блокировки или проблемы на базе логов не должны остановить работу системы 1С и негативно сказаться на пользователях.
- В процедуру на стороне 1С передается параметр – извещать письмом в случае ошибки во время логирования. По умолчанию выставлен в ЛОЖЬ, но для критических процессов, где логирование важно, его нужно выставлять в ИСТИНА.
- В конце процедуры пытаемся закрыть RecordSet и Connection – оборачивая обе активности в блок Try-Catch.
В остальном код достаточно стандартный.
Процедура ЗаписатьОшибку (
UserName, // Имя пользователя
ErrorTypeID, // Тип ошибки (Целое число)
ErrorMessage, // Сообщение об ошибке
StackPath, // Стэк ошибки
ErrorLog, // Полный лог ошибки
ErrorXML, // XML параметры ошибки ("" - в базу пишется NULL)
SendEMail = FALSE // Отправлять/Не отправлять письмо в случае сбоя логирования
) Экспорт
// Получаем строчку соединения
stConnectionString = ПолучитьСтрокуСоединения();
Попытка
Если (stConnectionString <> Неопределено и stConnectionString <> "") Тогда
// Открываем соеинения с базой ITWarehouse
Соединение = Новый COMОбъект("ADODB.Connection");
Соединение.Provider = "SQLOLEDB";
Соединение.ConnectionString = stConnectionString;
Соединение.Open();
// Запуск процедуры
Command = Новый ComОбъект("ADODB.Command");
RecordSet = Новый COMОбъект("ADODB.RecordSet");
Command.ActiveConnection = Соединение;
Command.CommandText = "dbo.Log_AddError";
Command.CommandType = 4;
// Консаты для типов SQL
adInteger = 3;
adVarChar = 200;
adParamInput = 1;
// Имя пользователя
Param1 = Command.CreateParameter("UserName", adVarChar, adParamInput, 64, Лев(UserName, 64));
Command.Parameters.Append(Param1);
// Тип ошибки
Если (ТипЗнч(ErrorTypeID) <> Тип("Число")) Тогда
ErrorTypeID = 2; // Если передали не числовой тип - то сбрасываем в 2 - Ошибка
КонецЕсли;
Если (Цел(ErrorTypeID) <> ErrorTypeID) Тогда
ErrorTypeID = 2; // Если передали не целое число - то сбрасываем в 2 - Ошибка
КонецЕсли;
Param2 = Command.CreateParameter("ErrorTypeID", adInteger, adParamInput, , ErrorTypeID);
Command.Parameters.Append(Param2);
// Сообщение об ошибке
Param3 = Command.CreateParameter("ErrorMessage", adVarChar, adParamInput, 2048, Лев(ErrorMessage, 2048));
Command.Parameters.Append(Param3);
// Стэк ошбки
Param4 = Command.CreateParameter("StackPath", adVarChar, adParamInput, 2048, Лев(StackPath, 2048));
Command.Parameters.Append(Param4);
// Детальное описание ошибки
Param5 = Command.CreateParameter("ErrorLog", adVarChar, adParamInput, 2048, Лев(ErrorLog, 2048));
Command.Parameters.Append(Param5);
// XML c параметрами об ошибке
Param6 = Command.CreateParameter("ErrorXML", adVarChar, adParamInput, 36000, Лев(ErrorXML, 36000));
Command.Parameters.Append(Param6);
RecordSet = Command.Execute();
Если RecordSet.Fields.Item(0).Value <> "0" Тогда
Если SendEMail Тогда
ОтправитьПисьмоОбОшибке(
"Ошибка записи в Log_Errors",
"Произошла ошибка во внутренней процедуре на SQL Server! " + RecordSet.Fields.Item(0).Value
);
КонецЕсли;
КонецЕсли;
КонецЕсли;
Исключение
Если SendEMail Тогда
]ОтправитьПисьмоОбОшибке("Ошибка записи в Log_Errors", ОписаниеОшибки());
КонецЕсли;
КонецПопытки;
// Закрываем RecordSet
Если RecordSet <> Неопределено Тогда
Попытка
RecordSet.Close()
Исключение
Если SendEMail Тогда
ОтправитьПисьмоОбОшибке("Ошибка записи в Log_Errors", ОписаниеОшибки());
КонецЕсли;
КонецПопытки;
КонецЕсли;
// Закрываем соединение с базой данных
Если Соединение <> Неопределено Тогда
Попытка
Соединение.Close()
Исключение
Если SendEMail Тогда
ОтправитьПисьмоОбОшибке("Ошибка записи в Log_Errors", ОписаниеОшибки());
КонецЕсли;
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Вызов процедуры ЗаписатьОшибку имеет некоторые особенности в плане передаче XML строки. В общем виде вызов выглядит следующим образом:
//--LOG_BEGIN-------------------------------------------------
// Логируем работу восстановления последовательности
ДатаВформатеXML = "<Parameters><DocParameters
| SequenceDate='" + Формат(ДатаВосстановления, "ДФ='yyyy-MM-dd HH:mm:ss'") + "'
| WorkSeconds ='" + Формат (WorkSeconds, "ЧГ="). + "'
| NumberOfErrors = '" + Формат (NumberOfErrors, "ЧГ="). + "'
| MaxWorkSeconds = '" + Формат (MaxWorkSeconds, "ЧГ="). + "'
|/></Parameters>";
Логирование.ЗаписатьОшибку(
глЗначениеПеременной("глТекущийПользователь"),
1, // Тип = Восстановление последовательности
"Информационное сообщение",
"Внешняя обработка/restore_robot.epf",
"Идет восстановление последовательности",
ДатаВформатеXML,
FALSE
);
//--LOG_END------------------------------------------------
При этом, чтобы потом можно было на стороне SQL вытащить из XML записанные данные, то необходимо помнить о следующих особенностях:
- Дату время необходимо передавать следующим образом: Формат(Дата, “ДФ=’yyyy-MM-dd HH:mm:ss'”).
- Числа необходимо передавать следующим образом (чтобы избавиться от специального проблема между разрядами): Формат(Число, “ЧГ=”).
На сегодня все – в следующий раз разберем саму процедуру, которая восстанавливает последовательность.