Top.Mail.Ru
Мастера DELPHI, Delphi programming community Титульная страница Поиск, карта сайта Написать письмо 
| Новости | Поиск | FAQ | Статьи | Книги | Новости VCL |
| Форумы ЧАТ | Орешник | KOL и MCK |
Форумы -» Основная («Начинающим»)/ Базы / WinAPI / Компоненты / Сети / Media / Игры / Corba и COM / KOL / FreePascal / .Net / Прочее / RSDN.ORG

« предыдущая ветвь | форум | следующая ветвь »

Логгирование исключений с последующим их перевозбуждением.


Страницы:   1 2 3

>|< ©   (2010-11-26 16:33:00) 

Прошу мастеров просмотреть данный код на предмет подводных камней.
Будет ли он правильно работать и какие могут быть нюансы.
procedure TDMMain.KMSExceptionHandler(Sender: TObject; E: Exception);
var fn:string;
   LOB: TLOBLocator;
   MyBlobStream: TMemoryStream;
   ComponentName:string;

begin
 try
   try
     Fn := ExtractFilename(Application.ExeName)+'_'+
           Screen.ActiveForm.Name+
           FormatDateTime('_ddmmyyyy_hhnnss',now)+
           '_debug';
     MyBlobStream:= TMemoryStream.Create;
     DoGenerateScreenshot;

     LOB:= TLOBLocator.CreateTemporary(DMMain.OracleSession, otBLOB, True);
     try
       oqInsertError.ClearVariables;
       oqInsertError.SetVariable('V_FORM_NAME',Screen.ActiveForm.Name+':'+Screen.ActiveForm.Caption);
       oqInsertError.SetVariable('V_ERROR_TEXT',E.Message);
       oqInsertError.SetVariable('V_ID_USER',DMMain.UserID);
       oqInsertError.SetVariable('V_DATETIME',Now);
       oqInsertError.SetVariable('V_OSUSER_NAME',CollectUserName);
       oqInsertError.SetVariable('V_COMP_NAME',CollectComputerName);
       oqInsertError.SetVariable('v_exception_name', E.ClassName);
       if Sender is TComponent then
         ComponentName := TComponent(Sender).Name
         //TODO: Попытаться вытянуть из сендера текстовое заглавие  компонента, который возбудил ошибку
       else
         ComponentName := '<имя неизвестно>';
       oqInsertError.SetVariable('v_object_name',Sender.ClassName+':'+ ComponentName);
       MyBlobStream.Position := 0;
       LOB.CopyFrom(MyBlobStream, MyBlobStream.Size);
       oqInsertError.SetComplexVariable('V_SCREENSHOT', LOB);
       oqInsertError.Execute;
       DMMain.OracleSession.Commit;
     finally
       LOB.Free;
       MyBlobStream.Free;
     end;
   except
     on E_local:Exception do
     begin
         raise E_local.Create('Ошибка логгирования:'#10#13+ E_local.Message);
         MyBlobStream.SaveToFile(fn+'.jpg');
     end;
   end;
 finally
   Application.OnException := nil;
   raise E.Create('Произошло исключение, которое было записано в БД.');
   Application.OnException := KMSExceptionHandler;
 end;
end;


_oxffff   (2010-11-29 09:29:00) [21]

Также в новых версиях есть поддержка вложенных исключений

см. Exception.InnerException (specifies the inner exception)

Read the value of the InnerException property to obtain the inner exception. If there are no exceptions that triggered the current one, InnerException returns nil; otherwise InnerException returns the inner exception that triggered a specific exception.


_oxffff   (2010-11-29 08:48:00) [22]


> в принципе, это ключевое место
> тогда вопрос, как перевозбудить исключение, которое содержится
> в объекте E?


Reraising ExceptionsFrom RAD Studio
Go Up to Exception handling Index

Sometimes when you handle an exception locally, you want to augment the handling in the enclosing block, rather than replace it. Of course, when your local handler finishes its handling, it destroys the exception instance, so the enclosing block's handler never gets to act. You can, however, prevent the handler from destroying the exception, giving the enclosing handler a chance to respond. You do this by using the raise command with no arguments. This is called reraising or rethrowing the exception. The following example illustrates this technique:

try
{ statements }
 try
{ special statements }
 except
   on ESomething do
   begin
{ handling for only the special statements }
      raise;{ reraise the exception }
   end;
 end;
except
 on ESomething do ...;{ handling you want in all cases }
end;


Leonid Troyanovsky   (2010-11-28 22:27:00) [23]


> >|<   (26.11.10 18:14) [22]

> Это неприемлемо, поскольку произойдет ошибка, а пользер
> будет и дальше работать как ни в чем не бывало.

Хорошее имя - пользер. Записал себе в мемо.

> KSergey ©   (26.11.10 21:49) [30]

Про перевозбуждение тоже понравилось.

--
Regards, LVT.


KSergey   (2010-11-26 21:49:00) [24]

> >|<   (26.11.10 18:14) [22]
> Идея даже не в том, чтобы погасить все исключения.
> Это неприемлемо, поскольку произойдет ошибка, а пользер
> будет и дальше работать как ни в чем не бывало.
>
> Вот для этого я и перевозбуждаю его, чтобы пользер не думал,  что все в порядке.

Я понял в чем ошибка вашего миропонимания. Вы просто перевозбудились! (сугубо каламбур, ничего личного)

Ваше миропонимание с "перевозбуждением" (исключений) базируется на том, что вы в обычном Windows дельфи-приложении всегда имеете защитную оболочку в виде перехватчика всех исключений, который выводит окно с информцией, глушитисключение, после чего цикл выборки Windows-сообщений продолжается.

Однако Application.OnException - это как раз и есть та самая последняя подушка, которая собственно эти исключения перехватывает. Ну т.е. перехватывает, конечно, блок try/exception вокруг кода обработки собщения, но при задании пользователем обработчика для Application.OnException собственно этому обработчику и отдается на откуп вся логика по обработке факта возникновения исключения.

Но давайте от словоблудия- к коду.
Вот что есть с VCl:

procedure TApplication.Run;
begin
 FRunning := True;
 try
   AddExitProc(DoneApplication);
   if FMainForm <> nil then
   begin
     case CmdShow of
       SW_SHOWMINNOACTIVE: FMainForm.FWindowState := wsMinimized;
       SW_SHOWMAXIMIZED: MainForm.WindowState := wsMaximized;
     end;
     if FShowMainForm then
       if FMainForm.FWindowState = wsMinimized then
         Minimize else
         FMainForm.Visible := True;
     repeat
        try
         HandleMessage;
       except
         HandleException(Self);
       end;

     until Terminated;
   end;
 finally
   FRunning := False;
 end;
end;

Это как раз тот метод объекта Application, который тупо крутит цикл выборки сообщений repeat/until все время существования Windows-приложения.
Обратите внимание на выделенный фрагмент. Именно внутри метода HandleMessage выполняются решительно весьтот код, который вы собственно и пишите. Потому как любой код есть лишь реакция на какое-либо сообщение. Ни в какое другое место (кроме Initialization/finalization и событий создение/уничтожение главной формы) свой код вы внедрить не можете. Только внутрь обработчика какого-либо сообщений Windows.
И что же мы видим? а мы видим, что все необработанные исключения здесь тупо гасятся, при этом вызывается HandleException, который весьма прост:

procedure TApplication.HandleException(Sender: TObject);
begin
 if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
 if ExceptObject is Exception then
 begin
   if not (ExceptObject is EAbort) then
     if Assigned(FOnException) then
       FOnException(Sender, Exception(ExceptObject))
     else
       ShowException(Exception(ExceptObject));
 end else
   SysUtils.ShowException(ExceptObject, ExceptAddr);
end;

Что же мы видим в выделеном фрагменте? если установлен свой обработчки исключений для приложения - то вызовется он. Если не установлен - вызовется просто отображение информационного окна. Все! Т.е. перевозбуждать в том месте исключение просто не имеет смысла, его попросту некомубудет обработать! дальше только run-time error и принудительное завершение процесса.

Так что все, что вы можете здесь сделать - это сохранить лог и отобразить что-то пользователю. Причем по-хорошему исключения из OnException выпускать наружу нельзя. (Проверьте что будет, если внутри OnException возбудить исключение; я думаю прога тут же завершится с "программа выполнила недопустимую операцию и будет закрыта", проверять лень).

Надеюсь, объяснил понятно.


_Юрий   (2010-11-26 19:07:00) [25]


> >|<   (26.11.10 18:56) [28]


кстати начиная с 2009 наконец то появились InnerException, что позволяет при определенных усилиях получить весь "логический стек" ,начиная с момента возникновения ошибки.
Если используется такая версия, рекомендую посмотреть статью:
http://www.gunsmoker.ru/2010/04/exception-delphi-2009.html


&gt;|&lt;   (2010-11-26 18:56:00) [26]


> Совсем гасить текст оригинального исключения - тоже нехорошо,
>   потому что может оказаться так, что пользователь в состоянии
> самостоятельно разрулить проблему, не дергая саппорт.
> например, если в настройках путь к БД неверный, вывод даже
> сообщения типа
> "Cannot open file c:\myPath\MyFile.db"  может оказаться
> полезным.

По этому поводу буду выводить следующее сообщение:
ErrorDlg('Возникла ошибка:'+#10#13+E.Message+
            #10#13+'Для ее устранения обратитесь к разработчикам.'+
            #10#13+'Желательно повторить действия, которые привели к ее возникновению и'+
            #10#13+'убедиться, что причина ошибки определена.');


_Юрий   (2010-11-26 18:52:00) [27]

Совсем гасить текст оригинального исключения - тоже нехорошо,  потому что может оказаться так, что пользователь в состоянии самостоятельно разрулить проблему, не дергая саппорт.
например, если в настройках путь к БД неверный, вывод даже сообщения типа
"Cannot open file c:\myPath\MyFile.db"  может оказаться полезным.


&gt;|&lt;   (2010-11-26 18:47:00) [28]


> Сергей М. ©   (26.11.10 18:26) [25]

В общем, Вы меня убедили.
Я отказался от перевозбуждения исключений.

Всем спасибо за помощь и внимание к моему вопросу.)


Сергей М.   (2010-11-26 18:26:00) [29]


> для этого я и перевозбуждаю его, чтобы пользер не думал,
>  что все в порядке


А он и не подумает что все в порядке)

Как бы ты не вуалировал текст оригин.сообщения об исключении, для юзера он все равно будет матерными словами.

Почему бы не выдать нечто вида "Возникла непредвиденная ошибка. Выполнение программы будет аварийно прервано, с претензиями обратитесь конкретно туда-то" ?

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


&gt;|&lt;   (2010-11-26 18:19:00) [30]

Возможно, дальше будет анализ типов исключений и в зависимости от их значений гасить или исправлять ситуацию вручную.
Например, если приложение было включено целую ночь, но к базе не было обращений, то коннект обрывается.
В этом случае, если возникнет, исключение "Session Undefined", просто переподключиться к БД.


Сергей М.   (2010-11-26 18:18:00) [31]

все что нужно - это

//логируем исключение,
//код логгера должен гарантировать невозможность возбуждения в нем необраб.иск-й
Log(..);

//покажем юзеру, если так уж надо, оригинальное исключение
ShowException(E);


&gt;|&lt;   (2010-11-26 18:14:00) [32]

В чем идея? Идея не в том, чтобы пользователь пугался страшных окон с ошибками и не знал куда бежать. Мне не нужно выводить универсальное окно с понятными словами.
Идея даже не в том, чтобы погасить все исключения.
Это неприемлемо, поскольку произойдет ошибка, а пользер будет и дальше работать как ни в чем не бывало.
Идея состояла только в том, чтобы вклиниться в очередь возникновения ошибок и перед ее возникновением отправить скриншот(другие данные) в БД, чтобы можно было проанализировать причину.
Вот для этого я и перевозбуждаю его, чтобы пользер не думал, что все в порядке.


KSergey   (2010-11-26 18:11:00) [33]

К стати, а зачем именно исключение возбуждать с текстом 'Произошло исключение, которое было записано в БД.'? достаточно это в виде диалогового окна отобразить - да и все. Ну раз уж взяли на себя работу стандартного обработчика - ее надо делать до конца.


KSergey   (2010-11-26 18:09:00) [34]

> Сергей М. ©   (26.11.10 18:03) [19]

Ну тогда надо делать как обычно: код обработки исключений не должен исключения генерировать
Т.е. тупо try/except по краям в него - и все.
Как вариант - в except вставить диалоговое окно "была ошибка, но сохранить в базу не сумели, делайте скриншот". Если уж диалоговое окно вызовет исключение - ну это явно совсем не повезло юзеру, на карму его и свалить :)


Сергей М.   (2010-11-26 18:03:00) [35]


> KSergey ©   (26.11.10 17:59) [18]


> Прав ли он


Прав.

Но перевозбуждать (или возбуждать новое) исключение в обработчике, СПЕЦИАЛЬНО предназначенном для обработки необработанных исключений, - это ничем не оправданная блажь.

Чтобы уронить свой процесс великого ума не требуется)


KSergey   (2010-11-26 17:59:00) [36]

> clickmaker ©   (26.11.10 17:40) [14]
> мне тоже непонятно...

Автор опасается, что исключение, возникшее в Application.OnException приведет к вызову того же Application.OnException, где опять исключение - и по кругу.

Прав ли он - надо в код VCL смотреть или натурный эксперимент ставить.


Юрий Зотов   (2010-11-26 17:53:00) [37]


> >|<   (26.11.10 17:16) [8]
> Каким образом перевозбудить исключение после записи его
> в лог?


Например, так:

type
 EMyException = class(Exception)
 public
   constructor Create(E: Exception);
 end;

constructor EMyException.Create(E: Exception);
begin
 inherited Create(
  Format('Произошло и записано в БД исключение класса %s: %s',
     [E.ClassName, E.Message])
end;


Вызов такой:

Application.OnException := nil;
try
 raise EMyException.Create(E)
finally
 Application.OnException := KMSExceptionHandler;
end;


Но вместо всех этих плясок с перевозбуждением исключения можно просто вызвать ShowMessage и выдать сообщение:
Format('Произошло и записано в БД исключение класса %s: %s', E.ClassName, E.Message])


Сергей М.   (2010-11-26 17:45:00) [38]


> Если не выключать обработчик, полагаю, возникнет циклическое
> исключение


А зачем его перевозбуждать-то ?


Rouse_   (2010-11-26 17:41:00) [39]

местами строчки поменять и всего делов, но все равно не понятно, зачем все так сложно? Возьми джеди - там хороший обработчик SEH фреймов имеется.


clickmaker   (2010-11-26 17:40:00) [40]

мне тоже непонятно...

Log(E_local.Message);
raise E_local;

и всё


Страницы:   1 2 3

Наверх

Титульная страница Поиск, карта сайта Написать письмо