Автоматизация восстановления последовательности. Часть 3: работа с SQL

В предыдущий раз (Автоматизация восстановления последовательности. Часть 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:

  1. Нет полноценной поддержки механизма ADO, поэтому вся транзакционная логика будет на SQL Server – задача 1С будет состоять только в вызове необходимой процедуры и передачи в нее параметров. При этом драйвер используемый 1С достаточно старый и не поддерживает многих особенностей последних версий SQL Server.
  2. В блок Try-Catch нет секции Finally, что добавляет определенную специфику в закрытии за собой соединений с базой данных.
  3. Нет человеческой поддержки формата XML. То, что в плане XML реализовано в 1С – пугает ))).
  4. 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'”).
  • Числа необходимо передавать следующим образом (чтобы избавиться от специального проблема между разрядами): Формат(Число, “ЧГ=”).

На сегодня все – в следующий раз разберем саму процедуру, которая восстанавливает последовательность.

Добавить комментарий

Ваш адрес email не будет опубликован.