Howto log source line causing exception and add custom information? - delphi

Our application log the source line causing exception with JCL and it works great.
I use D2007. I have a TApplicationEvents.OnException event that do the actual logging.
Consider this:
function MyFunc: String;
begin
// Codelines that may raise exception.
// Call functions that also may raise exception
end;
procedure ComplexFunc(aVariable: String);
begin
// also here can it be exceptions....
// Code here that is the cause of exception
end;
procedure foo;
var
myVar: String;
begin
myvar := MyFunc;
ComplexFunc(myvar);
end;
procedure TMainForm.ApplicationEvents1Exception(Sender: TObject; E: Exception);
begin
LogLastException(E, 'Unhandled Exception (%s)', [E.Message], 20);
end;
I have 3 methods and my onException event.
LogLastException log the callstack when an exception occurs. The problem is that I cannot add information to the E.Message without loose the sourceline that cause exception. Pretend it is the second line in ComplexFunc that raise exception. I also want to log the value of myvar variable. So I change the code to:
function MyFunc: String;
begin
// Codelines that may raise exception.
// Call functions that also may raise exception
end;
procedure ComplexFunc(aVariable: String);
begin
// also here can it be exceptions....
// Code here that is the cause of exception
end;
procedure foo;
var
myVar: String;
begin
try
myvar := MyFunc;
ComplexFunc(myvar);
except
on E: Exception do
raise TException.CreateFmt('myvar = %s', [myvar]);
end;
end;
procedure TMainForm.ApplicationEvents1Exception(Sender: TObject; E: Exception);
begin
LogLastException(E, 'Unhandled Exception (%s)', [E.Message], 20);
end;
Now the value of myvar is logged, BUT at the price of I loose the original sourceline of the exception. Instead the line with raise TException.CreateFmt is logged. Any suggestion of how to do both ?
Regards

In addition to Marjan Vennema's answer (which I would follow), you can raise your new exception and make it look like it came from the address of the old exception.
except
on E: Exception do
raise Exception.CreateFmt('myvar = %s', [myvar]) at ExceptAddr;
end;

You are losing the original source line of the exception, because
except
on E: Exception do
raise TException.CreateFmt('myvar = %s', [myvar]);
end;
effectively handles the original exception (making it go away) and raising a new one. Which, of course then will have its own "source line of exception."
#balazs' solution preserves the source line of the original exception in the message of the new exception. #Stephane's solution comes close to the one I would use. Unfortunately he is replacing the original message with only the value of myvar. What I would do is add a line on top of the original message and then just re-raise the exception:
except
on E: Exception do
begin
E.Message := Format('%s'#13#10'%s', [Format('MyVar: %s', [MyVar]), E.Message]);
raise;
end;
end;

I've you tried something like that ?
try
myvar := MyFunc;
ComplexFunc(myvar);
except
on E: Exception do
begin
e.message := format('myvar = %s', [myvar]);
raise ;
end;
end;

I don't know what LogLastException do, but if you can redirect it's result into a string rather than your log, you can reraise the exception like this:
except
on E: Exception do
begin
str := LogLastExceptionToString(E, 'Unhandled Exception (%s)', [E.Message], 20);
raise TException.CreateFmt( str + 'myvar = %s message so far:' , [myvar]);
end;
end;

A simple approach would be to define a global variable, assign the extra information to it, and then add its contents to your log when you log the exception information.

Related

Exception in finally block

If exception is raised in finally block of code, is the rest of finally block executed, or not?
try
statementList1
finally
command;
command_that_raises;
critical_command;
end
Will critical_command be executed?
Manual talks only about exceptions, not execution of code:
If an exception is raised but not handled in the finally clause, that exception is propagated out of the try...finally statement, and any exception already raised in the try clause is lost. The finally clause should therefore handle all locally raised exceptions, so as not to disturb propagation of other exceptions.
See the following confirmation:
procedure TForm6.Button1Click(Sender: TObject);
begin
try
ShowMessage('begin');
finally
ShowMessage('enter');
raise Exception.Create('raise');
ShowMessage('end');
end;
end;
And now for this case:
procedure RaiseAndContinue;
begin
try
raise Exception.Create('raise');
except
end;
end;
procedure TForm6.Button1Click(Sender: TObject);
begin
try
ShowMessage('begin');
finally
ShowMessage('enter');
RaiseAndContinue;
ShowMessage('end');
end;
end;
The short answer is: unless you handle that exception then No the code will not be executed.

What is the correct way to nest exceptions? - Using Delphi

Assume I have three (or more) procedures, some of which call each other, as shown below, any one of which can fail.
If any one of them do fail I want the 'main' program to immediately log the failure and terminate the program.
What is the correct syntax to use in Delphi to 'pass back' an exception to each preceeding procedure call?
Even better if someone can help me to get the main program's Try/except block to identify which bit failed!
Sample pseudo code of the three procedures and the main program might look like below.
(I think I understand the principle, something to do with 'raise', but would like some help with the actual syntax and what code I should use)
//////////////////////////////////////
Procedure DoProcA
begin
try
begin
{stuff}; //stuff that might fall
end;
except
on E : Exception do
begin
LogError ('error in A');
end //on E
end;//try
//////////////////////////////////////
Procedure DoProcB
begin
try
begin
Do ProcC; //another proc that might fail
{other stuff}
end;
except
on E : Exception do
begin
LogError ('error in B');
end //on E
end;//try
//////////////////////////////////////
Procedure DoProcC
begin
try
begin
{Do stuff} //even more stuf fthat might fail
end;
except
on E : Exception do
begin
LogError ('error in C');
end //on E
end;//try
//////////////////////////////////////
//Main programo
begin
try
DoProcA;
DoProcB;
{other stuff}
except
{here I want to be able to do something like
if failure of A, B or C then
begin
LogError ('Failure somewhere in A, B or C');
application.terminate;
end;}
end; //try
end.
The best way to deal with this is to remove all of these exception handlers. Use an library like madExcept, EurekaLog, JCL Debug etc. to log any exceptions that make it all the way back to the top level exception handler.
It's simply untenable for you to attempt to add an exception handler to each and every function in your program. That's not at all how exceptions are meant to be used. As a broad rule, you should regard exceptions as things that should not be caught. They represent exceptional behaviour, and as such, it is usually the case that the function where they are raised does not know how to deal with them.
So, stop trying to handle exceptions. Don't handle them, as a guiding principle. If they make it all the way to the top level exception handler, deal with them there. And if you use one of the libraries mentioned above you will be able to obtain rich debugging information to help you understand why the exception was raised in the first place.
Have each function re-raise the caught exception after logging it, eg:
Procedure DoProcA;
begin
try
{stuff}; //stuff that might fall
except
on E : Exception do
begin
LogError ('error in A');
raise; // <-- here
end;
end;
end;
Procedure DoProcB;
begin
try
DoProcC; //another proc that might fail
{other stuff}
except
on E : Exception do
begin
LogError ('error in B');
raise; // <-- here
end;
end;
end;
Procedure DoProcC;
begin
try
{Do stuff} //even more stuff that might fail
except
on E : Exception do
begin
LogError ('error in C');
raise; // <-- here
end;
end;
end;
begin
try
DoProcA;
DoProcB;
{other stuff}
except
on E: Exception do
begin
LogError ('Failure somewhere in A, B or C');
//Application.Terminate; // this is not useful unless Application.Run is called first
end;
end;
end.
If you want the main procedure to identify WHICH function failed, you need to pass that information down the exception chain, eg:
type
MyException = class(Exception)
public
WhichFunc: String;
constructor CreateWithFunc(const AWhichFunc, AMessage: String);
end;
constructor MyException.CreateWithFunc(const AWhichFunc, AMessage: String);
begin
inherited Create(AMessage);
WhichFunc := AWhichFunc;
end;
Procedure DoProcA;
begin
try
{stuff}; //stuff that might fall
except
on E : Exception do
begin
raise MyException.CreateWithFunc('DoProcA', E.Message); // <-- here
end;
end;
end;
Procedure DoProcB;
begin
try
DoProcC; //another proc that might fail
{other stuff}
except
on E : MyException do
begin
raise; // <-- here
end;
on E : Exception do
begin
raise MyException.CreateWithFunc('DoProcB', E.Message); // <-- here
end;
end;
end;
Procedure DoProcC;
begin
try
{Do stuff} //even more stuff that might fail
except
on E : Exception do
begin
raise MyException.CreateWithFunc('DoProcC', E.Message); // <-- here
end;
end;
end;
begin
try
DoProcA;
DoProcB;
{other stuff}
except
on E: MyException do
begin
LogError ('Failure in ' + E.WhichFunc + ': ' + E.Message);
end;
on E: Exception do
begin
LogError ('Failure somewhere else: ' + E.Message);
end;
end;
end.
Or:
type
MyException = class(Exception)
public
WhichFunc: String;
constructor CreateWithFunc(const AWhichFunc, AMessage: String);
end;
constructor MyException.CreateWithFunc(const AWhichFunc, AMessage: String);
begin
inherited Create(AMessage);
WhichFunc := AWhichFunc;
end;
Procedure DoProcA;
begin
try
{stuff}; //stuff that might fall
except
on E : Exception do
begin
raise MyException.CreateWithFunc('DoProcA', E.Message); // <-- here
end;
end;
end;
Procedure DoProcB;
begin
try
DoProcC; //another proc that might fail
{other stuff}
except
on E : Exception do
begin
Exception.RaiseOuterException(MyException.CreateWithFunc('DoProcB', E.Message)); // <-- here
end;
end;
end;
Procedure DoProcC;
begin
try
{Do stuff} //even more stuff that might fail
except
on E : Exception do
begin
raise MyException.CreateWithFunc('DoProcC', E.Message); // <-- here
end;
end;
end;
var
Ex: Exception;
begin
try
DoProcA;
DoProcB;
{other stuff}
except
on E: Exception do
begin
Ex := E;
repeat
if Ex is MyException then
LogError ('Failure in ' + MyException(Ex).WhichFunc + ': ' + Ex.Message)
else
LogError ('Failure somewhere else: ' + Ex.Message);
Ex := Ex.InnerException;
until Ex = nil;
end;
end;
end.
Developers from other languages spend a lot of time and energy worrying about "unhandled exceptions" but in a typical Delphi forms application, if you look at the code behind Application.Run you will see that all exceptions will get handled if you let them bubble all the way to the top. (Which is the preferred behaviour, unless you have good reason to interfere)
Quite often, it is a good idea to add more information to the exception, and then re-raise it, ie. let it go. Something went wrong, your calling function needs to know, and that is the purpose of an exception in the first place.
If you are wanting to log each and every error, then a good place to do that would be in the Application.OnException event. NB. your example is a DOS style command line application, not a typical Delphi Windows forms application, not sure if that is what you intended. If this was just to try keep the example simple, you have actually created more work for yourself, as you don't have access to the Application object and all the functionality that goes with that.
Eg.
procedure TForm1.FormCreate(Sender: TObject);
begin
Application.OnException := AppException;
end;
procedure TForm1.AppException(Sender: TObject; E: Exception);
begin
if RunningInAutomationMode then
begin
LogError(E.Message);
Application.Terminate;
end
else
Application.ShowException(E);
end;
To answer your question directly though:
Procedure DoProcA;
begin
try
{stuff}; //stuff that might fall
except
on E : Exception do
begin
//LogError ('error in A'); will get logged later, don't want to log twice
E.Message := 'The following error occurred while trying to do whatzit to a whozit: '+E.Message;
raise;
end;
end;
end;
Procedure DoProcB;
begin
try
DoProcC; //another proc that might fail
{other stuff}
except
on E : Exception do
begin
//LogError ('error in B');
E.Message := E.Message + ' (Proc B)';
raise;
end;
end;
end;
Procedure DoProcC;
begin
try
{Do stuff} //even more stuff that might fail
except
on E : Exception do
begin
//LogError ('error in C');
E.Message := 'The following error occurred during procedure C: '+E.Message;
raise; //Note: do not use raise Exception.Create(E.Message); as you will then lose the exception's type, which can be very useful information to have
end;
end;
end;
begin
try
DoProcA;
DoProcB;
{other stuff}
except
on E: Exception do
begin
LogError (E.Message); //this will end up logging all the exceptions, no mater which procedure they occurred in
//Exception has not been re-raised, so code will continue from this point
Exit;
end;
end;
{Some more code} //Called exit above, so that this code won't get called, although it is unlikely you'll have more code outside the try..except block
end.

Try/Except/Finally not working properly in Delphi XE

I'm having this funny issue with Delphi XE where I create a try/except/finally statement and when the application generate an exception the except block is never called it jump straight to the finally block, I tried few things like invert the try/except/finally to try/finally/except, try to change the try blocks to different places, clean the code and recompile in case was a Delphi issue but noting seems to work.
What I'm trying to accomplish here is to show a dialog message to the user and after clean up the code in case of a crash.
procedure CallbackExport(Sender: TObject);
var
SaveDlg: TSaveDialog;
FileName: string;
begin
SaveDlg := TSaveDialog.Create (nil);
try
try
SaveDlg.Title := 'Export';
SaveDlg.InitialDir := GetSystemPath(CSIDL_DESKTOP);
SaveDlg.Options := [ofOverwritePrompt, ofEnableSizing];
case (Sender as TMenuItem).Tag of
cnExcel: begin
SaveDlg.Filter := 'Excel File (*.xls)|*.xls';
end;
cnHtml: begin
SaveDlg.Filter := 'HTML File (*.html)|*.html';
end;
cnTxt: begin
SaveDlg.Filter := 'Text File (*.txt)|*.txt';
end;
cnCsv: begin
SaveDlg.Filter := 'Comma Seperated File (*.csv)';
end;
cnXml: begin
SaveDlg.Filter := 'XML file (*.xml)|*.xml';
end;
end;
if not SaveDlg.Execute(self.Handle) then
Exit;
FileName := SaveDlg.FileName;
case (Sender as TMenuItem).Tag of
cnExcel: begin
ExportGridToExcel(FileName, tvdGrid);
end;
cnHtml: begin
ExportGridToHTML(FileName, tvdGrid);
end;
cnTxt: begin
ExportGridToText(FileName, tvdGrid);
end;
cnCsv: begin
ExportGridToText(FileName, tvdGrid, true, true, ',', '', '', 'CSV');
end;
cnXml: begin
ExportGridToXML(FileName, tvdGrid);
end;
end;
except
on e: exception do
begin
ShowMessage('An error occurred while saving the file ' + FileName + #13#10 + 'With a message: ' + E.Message);
StvdAudit.tvdAudit('Error saving file, reason: ' + E.Message);
end;
end;
finally
SaveDlg.Free;
end;
end
If an exception is raised inside the try/except, and not handled by code further down the call stack, it will be caught by your exception handler.
You are claiming the ExportGridToXXX is raising an exception that is not caught by the exception handler in your code. But that claim cannot be true. Either no exception is raised, or ExportGridToXXX already handles the exception.
On the more general subject of exception handling, the general policy should be not to handle them if at all possible. You should only handle them in case you need to stop the exception propagating, and need to deal with the exception at this point in the code. Normally, particularly in a UI program, you would simply let the exception be handled by the top-level exception handler.
As well as that point, you code swallows all exceptions, irrespective of their type. That's bad practice. Supposing that you do want to handle exceptions raised by ExportGridToXXX, you should only handle the exception classes that are expected. For instance, you might encounter EAccessViolation for which your app's policy is to terminate. But since you swallowed it, treating it in the same handler used to trap sharing violations, you cannot apply that policy. Always be discerning in your handling of exceptions.
Do the Export(smth) functions reside in a separate DLL? Then your application's Exception class is not the same as external DLL's Exception class.
Your exception handler is swallowing the exception, try re-raising instead:
on e: exception do
begin
StvdAudit.tvdAudit('Error saving file, reason: ' + E.Message);
raise exception.create('An error occurred while saving the file ' + FileName + #13#10 + 'With a message: ' + E.Message);
end;

How to pass call stack information to an exception using EurekaLog?

I have a threaded application and for some purpose I want to pass call stack information of a catched exception to a new custom exception:
try
//here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
raise EMyException.Create(E, CallStackOfExceptionEAsString);
end;
end;
What is the best way to do this, preferably using EurekaLog? I am using Delphi 2006 btw.
EurekaLog exposes several event handlers like OnExceptionNotify.
You can implement these in your code. For example: procedure EurekaLogExceptionNotify(
EurekaExceptionRecord: TEurekaExceptionRecord; var Handled: Boolean);
Here you can see a TEurekaExceptionRecord which is defined in ExceptionLog.pas. But you maybe just own the non-source version which works just fine.
The record has a EurekaExceptionRecord.CallStack list. This proprietary list can be converted to TStringsusing the CallStackToStrings method which is also defined in the ExceptionLog unit.
Here is an example where I write the CallStack into a StringList.
CallStackList := TStringList.Create;
try
CallStackToStrings(EurekaExceptionRecord.CallStack, CallStackList);
LogMessage := 'An unhandled exception occured. Here is the CallStack.' + #13#10
+ CallStackList.Text;
finally
CallStackList.Free;
end;
At least from this starting point you should be able to investigate the exposed functions, records etc.. All information is accessible.
EurekaLog provides a function GetLastExceptionCallStack() (defined in unit ExceptionLog.pas).
Using this I have written the following function (based on example code here):
function GetLastEurekalogCallStackAsString(): string;
{$IFDEF EUREKALOG}
var
Stack: TEurekaStackList;
Str: TStringList;
{$ENDIF}
begin
{$IFDEF EUREKALOG}
Stack := GetLastExceptionCallStack();
try
Str := TStringList.Create;
try
CallStackToStrings(Stack, Str);
Result := Str.Text;
finally
FreeAndNil(Str);
end;
finally
FreeAndNil(Stack);
end;
{$ELSE}
Result := '';
{$ENDIF}
end;
So you can write:
try
//here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
raise EMyException.Create(E, GetLastEurekalogCallStackAsString());
end;
end;
EurekaLog 7 has Chained Exception support, which is specifically designed for this task. Just enable it in options (it is enabled by default) and use:
try
// here an unknown exception is rissen
except
on E: Exception do
begin
if ... then
Exception.RaiseOuterException(EMyException.Create(E.Message));
// for old IDEs:
// raise EMyException.Create(E.Message);
end;
end;

How should I re-raise a Delphi exception after logging it?

Do you know a way to trap, log, and re-raise exception in Delphi code?
A simple example:
procedure TForm3.Button1Click(Sender: TObject);
begin
try
raise Exception.Create('Bum');
except
on E: Exception do
begin
MyHandleException(E);
end;
end;
end;
procedure TForm3.MyHandleException(AException: Exception);
begin
ShowMessage(AException.Message);
LogThis(AException.Message);
// raise AException; - this will access violate
end;
So I need to re-raise it in the except block but I was wondering if there is a better way to write my own method to handle and (on specific conditions) to re-raise exceptions.
If you want to re-raise the exception only under certain conditions, write
procedure TForm3.Button1Click(Sender: TObject);
begin
try
raise Exception.Create('Bum');
except
on E: Exception do
begin
if MyHandleException(E) then
raise;
end;
end;
end;
function TForm3.MyHandleException(AException: Exception): boolean;
begin
ShowMessage(AException.Message);
result := true/false;
end;
Following on from Craig Young's post, I've used something along the lines of the following code successfully. You can preserve the original exception location by using the "at" identifier with the ExceptAddr function. The original exception class type and information is also preserved.
procedure MyHandleException(AMethod: string);
var
e: Exception;
begin
e := Exception(AcquireExceptionObject);
e.Message := e.Message + ' raised in ' + AMethod;
raise e at ExceptAddr;
end;
try
...
except
MyHandleException('MyMethod');
end;
The following will work, but is of course not ideal for 2 reasons:
The exception is raised from a different place in the call stack.
You don't get an exact copy of the exception - especially those classes that add attributes. I.e. you'll have to explicitly copy the attributes you need.
Copying custom attributes can get messy due to required type checking.
.
procedure TForm3.MyHandleException(AException: Exception);
begin
ShowMessage(AException.Message);
LogThis(AException.Message);
raise ExceptClass(AException.ClassType).Create(AException.Message);
end;
The benefits are that you preserve the original exception class, and message (and any other attributes you wish to copy).
Ideally you'd want to call System._RaiseAgain, but alas that is a 'compiler-magic' routine and can only be called by raise;.
You could try to use (system.pas):
function AcquireExceptionObject: Pointer;
AcquireExceptionObject returns a pointer to the current exception object and prevents the exception object from being deallocated when the current exception handler exits.
Note: AcquireExceptionObject increments the exception object's reference count. Make sure that the reference count is decremented when the exception object is no longer needed. This happens automatically if you use the exception object to re-raise the exception. In all other cases, every call to AcquireExceptionObject must have a matching call to ReleaseExceptionObject. AcquireExceptionObject/ReleaseExceptionObject sequences can be nested.
You should be able to just use the Raise command by itself to re-raise the exception:
begin
MyHandleException(E);
Raise;
end;
Old topic but, what about this solution?
procedure MyHandleException(AException: Exception);
begin
ShowMessage(AException.Message);
AcquireExceptionObject;
raise AException;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
try
raise Exception.Create('Bum');
except
on E: Exception do
MyHandleException(E);
end;
end;
It's based on the first code posted by Eduardo.
This way was working for me!
procedure RaiseExceptionFmt(const AFormat: string;
const AArgs: array of const);
begin
raise Exception.CreateFmt(AFormat, AArgs) at ExceptAddr;
end;
I rewrote my method, and now the raise will be the same before.
procedure RaiseInternalExceptionFmt(const AFormat: string;
const AArgs: array of const);
var
LExceptionPtr: Pointer;
begin
LExceptionPtr := AcquireExceptionObject();
try
Exception(LExceptionPtr).Message := Format(AFormat, AArgs);
raise Exception(LExceptionPtr) at ExceptAddr;
finally
ReleaseExceptionObject();
end;
end;
You could acquire the exception object before calling your handler and keep the handler itself one liner. However, you still have a lot of "Try/Except/Do/End" burden.
Procedure MyExceptionHandler(AException: Exception);
Begin
Log(AException); // assuming it accepts an exception
ShowMessage(AException.Message);
raise AException; // the ref count will be leveled if you always raise it
End;
Procedure TForm3.Button1Click(Sender: TObject);
Begin
Try
Foo;
Except On E:Exception Do
MyExceptionHandler(Exception(AcquireExceptionObject));
End;
End;
However, if what you only want to do is to get rid of repetitive error handling code in event handlers, you could try this:
Procedure TForm3.ShowException(AProc : TProc);
Begin
Try
AProc;
Except On E:Exception Do Begin
Log(E);
ShowMessage(E.Message);
End; End;
End;
Reducing your event handler code to this:
Procedure TForm3.Button1Click(Sender: TObject);
Begin
ShowException(Procedure Begin // anon method
Foo; // if this call raises an exception, it will be handled by ShowException's handler
End);
End;
You can also make it work for functions, using parametrized functions:
Function TForm3.ShowException<T>(AFunc : TFunc<T>) : T;
Begin
Try
Result := AFunc;
Except On E:Exception Do Begin
Log(E);
ShowMessage(E.Message);
End; End;
End;
And making ShowException return a value (acting as a passthru):
Procedure TForm3.Button1Click(Sender: TObject);
Var
V : Integer;
Begin
V := ShowException<Integer>(Function : Integer Begin // anon method
Result := Foo; // if this call raises an exception, it will be handled by ShowException's handler
End);
End;
Or even making the anon procedure touch directly the outer scope variable(s):
Procedure TForm3.Button1Click(Sender: TObject);
Var
V : Integer;
Begin
ShowException(Procedure Begin // anon method
V := Foo; // if this call raises an exception, it will be handled by ShowException's handler
End);
End;
There are some limitations on the interaction of variables from inside the body of the anonymous function and the ones defined in the outer scope, but for simple cases like these, you will be more than fine.

Resources