Mega Code Archive

 
Categories / Delphi / Forms
 

Exception Handling in Delphi Framework to log the complete traverse information

Title: Exception Handling in Delphi - Framework to log the complete traverse information Question: One of the hiccups in huge applications developed in Delphi is tracing exceptions till the root. Delphi as such provides a lovely mechanism to trap exceptions by way of try..except blocks. But then, in order to utilize this mechanism we need to have a proper framework. In this write up I am explaining the framework designed by me for handling exceptions. In this mechanism we get the complete information like the method names, unit names and the traverse information of a particular exception. The only limitation being the line number for which we might need to provide the Debug information or Map files. This frame work is designed to work under all the compiling/linking conditions and so does not include the mechanism to provide the line numbers. Also, expecting opinions and responses to make it much better. Answer: To have a proper control of the exceptions in an Application, a new class is created - ECustomException class with Exception as its parent class. An array of Pointer is introduced in this class basically to store the error details that are thrown within the application. This class has two constructors, the need for both will be known when we implementation part. A record has been introduced which can be changed by the implementor based on his requirements. Currently the record stores the Unit Name, Method and Module name. Before we get down to the actual code the let me explain the guidelines kept in mind when designing the framework: a) A complete traverse information of the exception. b) A mechanism to display a meaningul information to the end user, but a detailed technical information to the developer. c) Centralized Exception displaying and logging mechanism (Application.OnException method). {******************************************************************************} { } { Custom Exception class } { Author: Sri Ram Ambuga Nandakumar } { } { Disclaimer } { ---------- } { } { THE FILES ARE PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND WHETHER } { EXPRESSED OR IMPLIED. } { } { In no event shall the author be held liable for any damages whatsoever, } { including without limitation, damages for loss of business profits, } { business interruption, loss of business information, or any other loss } { arising from the use or inability to use the unit. } {******************************************************************************} unit UExceptionObj; interface uses SysUtils; type PErrorDetails = ^TErrorDetails; //---------------------------------- TErrorDetails = record // | Error Detail record. sUnit: string; // |- Can be changed by the implementor sModule: string; // | To suit his requirement sMethod: string; // | end; //---------------------------------- TCallStack = array of PErrorDetails; // type ECustomException = class(Exception) private FCallStack: TCallStack; function GetStackLength: integer; function GetErrorDetails(Index: integer): TErrorDetails; protected public constructor Create(AMessage: string; AErrorDetails: TErrorDetails); overload; constructor Create(AMessage: string; AException: ECustomException); overload; destructor Destroy; override; procedure AddToCallStack(AErrorDetails: TErrorDetails); property StackLength: integer read GetStackLength; property ErrorDetails[Index: integer]: TErrorDetails read GetErrorDetails; default; end; implementation { ECustomException } constructor ECustomException.Create(AMessage: string; AErrorDetails: TErrorDetails); begin inherited Create(AMessage); AddToCallStack(AErrorDetails); end; procedure ECustomException.AddToCallStack(AErrorDetails: TErrorDetails); var ptrErrDet: PErrorDetails; begin New(ptrErrDet); ptrErrDet^ := AErrorDetails; SetLength(FCallStack, High(FCallStack) + 2); FCallStack[High(FCallStack)] := ptrErrDet; end; constructor ECustomException.Create(AMessage: string; AException: ECustomException); var iCount: integer; begin inherited Create(AMessage); for iCount := Low(AException.FCallStack) to High(AException.FCallStack) do AddToCallStack(AException.FCallStack[iCount]^); end; destructor ECustomException.Destroy; var iCount: integer; begin inherited; for iCount := Low(FCallStack) to High(FCallStack) do Dispose(FCallStack[iCount]); if Assigned(FCallStack) then Finalize(FCallStack); end; function ECustomException.GetStackLength: integer; begin Result := High(FCallStack); end; function ECustomException.GetErrorDetails(Index: integer): TErrorDetails; begin if (Index -1) and (Index Result := FCallStack[Index]^; end; end. //**************************************************************************************************************** As we can see the Exception class takes care of storing the error information that are given to it. Now coming to the interface function which should be called from all the procedures in the Application. //**************************************************************************************************************** procedure GlobalExceptionHandler(AException: Exception; sUnit, sModule, sProcedure: string); var recErrDet: TErrorDetails; begin recErrDet.sModule := sModule; recErrDet.sProcedure := sProcedure; recErrDet.sUnit := sUnit; if AException is ECustomException then begin { Here we can see if it is ECustomException then it adds the entry in the Exception object and then raises the same error. Note: since raise creates a new object the earlier exception's stack info has to be passed on to the newly created one. This is the purpose of one of the Constructor. } (AException as ECustomException).AddToCallStack(recErrDet); raise ECustomException.Create(AException.Message, (AException as ECustomException)); end else { If it is any other Error then create our exception class with the earlier exception's message. This happens only at the source of the error only that is for the first time. Additional code can be written here to change the message of certain Exceptions like if EAccessViolation then raise ECustomException.Create('An error occurred while accessing a resource', recErrDet); ;) } raise ECustomException.Create(AException.Message, recErrDet); end; //**************************************************************************************************************** Additionally, this global exception handler can be fine tuned as per the requirement of the application. In the Application.OnException method just a loop will give all the information about the exception. Now all the methods in my application would look like below: procedure TForm1.Button1Click(Sender: TObject); begin try CallAProcedure except on E: Exception do GlobalExceptionHandler(E, 'UnitName', Application.ExeName, 'Button1Click'); end; end; procedure CallAProcedure; begin try CallRaiseAnException; except on E: Exception do GlobalExceptionHandler(E, 'UnitName', Application.ExeName, 'CallAProcedure'); end; end; procedure CallRaiseAnException; begin try RaiseAnException; except on E: Exception do GlobalExceptionHandler(E, 'UnitName', Application.ExeName, 'CallRaiseAnException'); end; end; procedure RaiseAnException; begin try raise Exception.Create('This is the original error message'); except on E: Exception do GlobalExceptionHandler(E, 'UnitName', Application.ExeName, 'CallRaiseAnException'); end; end; Now in the above case when the exception object reaches the Application's OnException method, the information of procedures that it will contain will be: Button1Click CallAProcedure CallRaiseAnException RaiseAnException. Please let me know your opinions and views regarding the same Sri Ram Ambuga Nandakumar