Log exceptions
Log exceptions
I have been using JCL as exceptionhandler for years. But I think that later versions have not good quality of callstack.
So I download and evaluate MadExcept instead.
I use a global exceptionhandler ApplicationEvents.OnException
This determine if the exception would be shown for enduser, save to to log-file etc.
So now I try to use MadExcept to save exception to log-file as before.
I have registred an event to be called.
procedure TServerData.HandleApplicationException(const exceptIntf: IMEException; var handled: Boolean);
So I have working code that can log the callstack to file. I am using GetCrashStackTrace for that.
I have two questions
1. I want to modify the callstack and log that to file.
It is like this now:
02bb0591 AttracsXE.exe FrmClientMainCommon 1211 TClientMainCommonForm.Logon
02bb22c1 AttracsXE.exe FrmClientMainCommon 1585 TClientMainCommonForm.FormShow
006e7a69 AttracsXE.exe Vcl.Forms 4104 TCustomForm.DoShow
And I want it like this
[00] TClientMainForm.Logon (frmClientMainForm.pas:1211)
[01] TClientMainCommonForm.FormShow (FrmClientMainCommon.pas:1585)
[02] TCustomForm.DoShow (Vcl.Forms.pas:4104)
So basically
[callstack number] classname.methodname (unitname:linenumber)
2. How can I get the interface of IMEException in try except
I want to do this
try
// some code with a bug
except
LogLastException(GetIMEException, 'Some custom description');
end
Now I send a standard Delphi E: Exception to LogLastException.
I want to change that to send IMEException instead.
Thanks in advance
Regards Roland
So I download and evaluate MadExcept instead.
I use a global exceptionhandler ApplicationEvents.OnException
This determine if the exception would be shown for enduser, save to to log-file etc.
So now I try to use MadExcept to save exception to log-file as before.
I have registred an event to be called.
procedure TServerData.HandleApplicationException(const exceptIntf: IMEException; var handled: Boolean);
So I have working code that can log the callstack to file. I am using GetCrashStackTrace for that.
I have two questions
1. I want to modify the callstack and log that to file.
It is like this now:
02bb0591 AttracsXE.exe FrmClientMainCommon 1211 TClientMainCommonForm.Logon
02bb22c1 AttracsXE.exe FrmClientMainCommon 1585 TClientMainCommonForm.FormShow
006e7a69 AttracsXE.exe Vcl.Forms 4104 TCustomForm.DoShow
And I want it like this
[00] TClientMainForm.Logon (frmClientMainForm.pas:1211)
[01] TClientMainCommonForm.FormShow (FrmClientMainCommon.pas:1585)
[02] TCustomForm.DoShow (Vcl.Forms.pas:4104)
So basically
[callstack number] classname.methodname (unitname:linenumber)
2. How can I get the interface of IMEException in try except
I want to do this
try
// some code with a bug
except
LogLastException(GetIMEException, 'Some custom description');
end
Now I send a standard Delphi E: Exception to LogLastException.
I want to change that to send IMEException instead.
Thanks in advance
Regards Roland
Re: Log exceptions
The IMEException interface has a property called "Callstacks", see here:
http://help.madshi.net/madExceptUnit.ht ... Callstacks
The type TStackTrace is defined like this:
This should give you the "power" to format the callstacks whichever way you prefer.
Do you *have* to do all this in a try..except block? It would be easier to simply do all this in your registered exception handler, because then you already get an "exceptIntf" interface delivered to you. If you insist on needing to do this in a try..except block, you can call "NewException(etNormal)" to get an IMEException interface for the "current" crash.
http://help.madshi.net/madExceptUnit.ht ... Callstacks
The type TStackTrace is defined like this:
Code: Select all
type
TStackItem = record
Addr : pointer; // code address
relAddr : dword; // relative address to function entry point
ModuleName : UnicodeString;
UnitName : UnicodeString;
Line : integer; // line number (0 = unknown)
relLine : integer; // relative line number to function entry point
FunctionName : UnicodeString; // function/method name
end;
TStackTrace = array of TStackItem;
TPStackTrace = ^TStackTrace;
Do you *have* to do all this in a try..except block? It would be easier to simply do all this in your registered exception handler, because then you already get an "exceptIntf" interface delivered to you. If you insist on needing to do this in a try..except block, you can call "NewException(etNormal)" to get an IMEException interface for the "current" crash.
Re: Log exceptions
Ok thanks for the answer. And how do I know how many Stackitems I have ?madshi wrote:The IMEException interface has a property called "Callstacks", see here:
http://help.madshi.net/madExceptUnit.ht ... Callstacks
The type TStackTrace is defined like this:
This should give you the "power" to format the callstacks whichever way you prefer.Code: Select all
type TStackItem = record Addr : pointer; // code address relAddr : dword; // relative address to function entry point ModuleName : UnicodeString; UnitName : UnicodeString; Line : integer; // line number (0 = unknown) relLine : integer; // relative line number to function entry point FunctionName : UnicodeString; // function/method name end; TStackTrace = array of TStackItem; TPStackTrace = ^TStackTrace;
Do you *have* to do all this in a try..except block? It would be easier to simply do all this in your registered exception handler, because then you already get an "exceptIntf" interface delivered to you. If you insist on needing to do this in a try..except block, you can call "NewException(etNormal)" to get an IMEException interface for the "current" crash.
I tried
Code: Select all
High(exceptIntf.Callstacks)
[dcc32 Error] AttracsErrorMgr.pas(209): E2029 '[' expected but ')' found
And yes a global exceptionhandler is the prefered way of handling exceptions.
Unfortunately there are many calls to LogLastExceptions with E: Exception as parameter and additional information for just that case as a string.
But I think it could be replaced with
Code: Select all
try
// Exception may be raised
except
on E: Exception do
begin
E.Message := Format('%s Error in ID: %s', [E.Message, ObjectID]);
raise;
end;
end;
Regards
Roland
Re: Log exceptions
There is no callstack "count" information available. You need to simply loop through "exceptIntf.ThreadIDs" until you get 0. And for each such ThreadID there's a matching callstack.
Re: Log exceptions
Ok have you some kind of demo how to access the callstack of the main thread ?
I still don't manage to get it...
I still don't manage to get it...
Re: Log exceptions
Nope, no demo available. Something like this (not tested):
Code: Select all
i := 0;
while exceptIntf.ThreadIds[i] > 0 do
begin
doSomething(exceptIntf.Callstacks[i]);
inc(i);
end;
Re: Log exceptions
Actually everything works now as I want, except one thing.
I still don't understand how I can read amount of Stackitems in callstack...
This is my code so far
So the line I want to change
for j := 0 to 10 do
Btw it is not I that have the wallet at company but I will push for a change to MadExcept as exceptionhandler.
Thanks
Roland Bengtsson
I still don't understand how I can read amount of Stackitems in callstack...
This is my code so far
Code: Select all
// MadExcept exception handling
class procedure TATErrorManager.LogLastException(const exceptIntf: IMEException);
var
s, vActiveForm: string;
vCallStack: TStringBuilder;
vST: TStackTrace;
i, j: Integer;
vThreadID: Cardinal;
begin
if AfterInitAndBeforeFinalization then
begin
if Assigned(gGetActiveFormEvent) then
vActiveForm := gGetActiveFormEvent
else
vActiveForm := '';
vCallStack := TStringBuilder.Create;
try
if vActiveForm = '' then
vCallStack.AppendFormat('[EXCEPTION] %s %s%s', [exceptIntf.ExceptClass, exceptIntf.ExceptMessage, sLineBreak])
else
vCallStack.AppendFormat('[EXCEPTION] %s %s. Active form %s', [exceptIntf.ExceptClass, exceptIntf.ExceptMessage, vActiveForm, sLineBreak]);
vCallStack.AppendLine('');
vCallStack.AppendLine('Call Stack:');
i := 0;
while True do
begin
vThreadID := exceptIntf.ThreadIds[i];
if vThreadID = 0 then
break;
if vThreadID = GetCurrentThreadId then
begin
vST := exceptIntf.Callstacks[i];
for j := 0 to 10 do // Avoid hardcode count. Where can I stop this loop ?
begin
vCallStack.AppendFormat('%s[%d] %s (%s:%d)%s', [#9, j, vST[j].FunctionName,
vST[j].UnitName,
vST[j].Line,
sLineBreak]);
end;
break;
end;
inc(i);
end;
TraceLog.Trace(vCallStack.ToString, -100000);
finally
vCallSTack.Free;
end;
end;
end;
for j := 0 to 10 do
Btw it is not I that have the wallet at company but I will push for a change to MadExcept as exceptionhandler.
Thanks
Roland Bengtsson
Re: Log exceptions
Length(exceptIntf.Callstacks) or High(). I know you think you already tried that, but you tried it on "exceptIntf.Callstacks" not on "exceptIntf.Callstacks".
Re: Log exceptions
P.S: Also, if you want the callstack of the crashing thread, don't check for GetCurrentThreadId, instead look for exceptIntf.CrashedThreadId, because your global exception handler might not run in the context of the crashing thread, so GetCurrentThreadId is not guaranteed to be the thread ID you're looking for.
Or, to make it easier, just use index 0, which is always the crashing thread...
Or, to make it easier, just use index 0, which is always the crashing thread...
Re: Log exceptions
Of course
Thanks for the help!
Thanks for the help!
Re: Log exceptions
One more question, how can I get the callstack of main thread without raise exception and let generic exceptionhandler be called ?
And with my own custom formatting of callstack mentioned earlier.
GetCrashStackTrace is preformatted as a string.
Reason is that sometimes I have this situation:
So idea is that even when an exception happen the loop is not aborted
/Roland
And with my own custom formatting of callstack mentioned earlier.
GetCrashStackTrace is preformatted as a string.
Reason is that sometimes I have this situation:
Code: Select all
for i := 0 to list.count - 1 do
begin
try
vObj := list[i];
// Do something with vObj that might raise exception
except
on E: Exception do
begin
LogLastException(E, 'Something bad happened with ' + vObj.ID); // This log callstack with JCL and objects ID.
end;
end;
end;
/Roland
Re: Log exceptions
NewException(etNormal).
Re: Log exceptions
Thanks for the answer. The problem is that High(vException.Callstacks[0]) is -1.
The call is TATErrorManager.LogLastException(nil, 'Testing');
The call is TATErrorManager.LogLastException(nil, 'Testing');
Code: Select all
class procedure TATErrorManager.LogLastException(const exceptIntf: IMEException; const MsgText: String);
var
vException: IMEException;
vMsg, vActiveForm: string;
vCallStack: TStringBuilder;
vST: TStackTrace;
i: Integer;
begin
if AfterInitAndBeforeFinalization then
begin
if Assigned(gGetActiveFormEvent) then
vActiveForm := gGetActiveFormEvent
else
vActiveForm := '';
if Assigned(exceptIntf) then
vException := exceptIntf
else
vException := NewException(etNormal);
vCallStack := TStringBuilder.Create;
try
if vMsg <> '' then
vMsg := Format('%s %s', [vException.ExceptMessage, Msgtext])
else
vMsg := vException.ExceptMessage;
if vActiveForm = '' then
vCallStack.AppendFormat('[EXCEPTION] %s %s%s', [vException.ExceptClass, vMsg, sLineBreak])
else
vCallStack.AppendFormat('[EXCEPTION] %s %s. Active form %s', [vException.ExceptClass, vMsg, vActiveForm, sLineBreak]);
vCallStack.AppendLine('');
vCallStack.AppendLine(cnCallStack);
vST := vException.Callstacks[0]; // Exception thread´always at 0
for i := 0 to High(vException.Callstacks[0]) do
vCallStack.AppendFormat('%s[%2d] %s (%s:%d)%s', [#9, i, vST[i].FunctionName,
vST[i].UnitName,
vST[i].Line,
sLineBreak]);
TraceLog.Trace(vCallStack.ToString, -100000);
finally
vCallSTack.Free;
end;
end;
end;
Re: Log exceptions
Hmmmm... Maybe it does the stack tracing in the background and doesn't wait for that to complete, I'm not sure right now. Try calling "exceptIntf.BugReport". It returns a string, you can ignore that. Calling that should force the exceptIntf to block until all stack tracing is complete.
Re: Log exceptions
Yes, you was correct. Thanks!