Log exceptions

delphi package - automated exception handling
Post Reply
Berocoder
Posts: 9
Joined: Fri May 03, 2019 11:25 am

Log exceptions

Post by Berocoder »

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
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Log exceptions

Post by madshi »

The IMEException interface has a property called "Callstacks", see here:

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;
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.
Berocoder
Posts: 9
Joined: Fri May 03, 2019 11:25 am

Re: Log exceptions

Post by Berocoder »

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:

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;
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.
Ok thanks for the answer. And how do I know how many Stackitems I have ?

I tried

Code: Select all

High(exceptIntf.Callstacks)
but I got compiler error

[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;
Then the global exceptionhandler take care of the rest.

Regards
Roland
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Log exceptions

Post by madshi »

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.
Berocoder
Posts: 9
Joined: Fri May 03, 2019 11:25 am

Re: Log exceptions

Post by Berocoder »

Ok have you some kind of demo how to access the callstack of the main thread ?
I still don't manage to get it... :)
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Log exceptions

Post by madshi »

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;
Berocoder
Posts: 9
Joined: Fri May 03, 2019 11:25 am

Re: Log exceptions

Post by Berocoder »

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

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;
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
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Log exceptions

Post by madshi »

Length(exceptIntf.Callstacks) or High(). I know you think you already tried that, but you tried it on "exceptIntf.Callstacks" not on "exceptIntf.Callstacks".
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Log exceptions

Post by madshi »

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... :D
Berocoder
Posts: 9
Joined: Fri May 03, 2019 11:25 am

Re: Log exceptions

Post by Berocoder »

Of course :)

Thanks for the help!
Berocoder
Posts: 9
Joined: Fri May 03, 2019 11:25 am

Re: Log exceptions

Post by Berocoder »

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:

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;
So idea is that even when an exception happen the loop is not aborted
/Roland
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Log exceptions

Post by madshi »

NewException(etNormal).
Berocoder
Posts: 9
Joined: Fri May 03, 2019 11:25 am

Re: Log exceptions

Post by Berocoder »

Thanks for the answer. The problem is that High(vException.Callstacks[0]) is -1.

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;
madshi
Site Admin
Posts: 10753
Joined: Sun Mar 21, 2004 5:25 pm

Re: Log exceptions

Post by madshi »

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.
Berocoder
Posts: 9
Joined: Fri May 03, 2019 11:25 am

Re: Log exceptions

Post by Berocoder »

Yes, you was correct. Thanks!
Post Reply