Question

I use a function to log application errors to a file.I have it on my main form.How do I tell my other forms, in case of an error,to use this function? Just by chance I added this to my query on some other form:

......
try
ClientDataSet1.Execute;
ClientDataSet1.Close;
except       
on E:Exception do begin
ShowMessage('Error : ' + E.Message);
LogError(E);  
....

My LogError(E); is not recognized (gives error). Tried adding the function :procedure LogError(E:Exception); to public uses but wont work.

Was it helpful?

Solution

Structured Exception Handling

I'd like to point out that your approach to exception handling is very tedious and wasteful coding. Not to mention quite dangerous. You don't want to be littering your code with individual handlers to log your exceptions when there's a much more practical approach.

Fortunately Delphi provides the means to make this much easier. First a little background about structured exception handling... Most developers make the mistake of trying to deal with errors by writing code like the following:

try
  //Do Something
except
  //Show Error
  //Log Error
end;

The problem with the above code is that it swallows the error. Even though the user sees an error message, the error itself is hidden from the rest of the program. This can result in other parts of the program doing things they should not do because of the error.

Imagine a program that calls the following routines: LoadAccount; ApplyDiscount; ProcessPayment;. But the the first routine raises an error. Which a programmer (mistakenly thinking they're being diligent) decided to "handle" as above. The problem is that the next two routines will still be called as if nothing is wrong! This could mean that a discount is applied to and payment processed for the wrong account!

The point is that structured exception handling saves us from these headaches (provided we don't break the way it works). If the LoadAccount routine didn't try "handle" the exception, then while the application is in an "exception state" the code would simply keep jumping out of routines until it finds a finally / except handler.

This suits an event driven program very nicely.
Suppose a user clicks a button that will cause your program to start a multi-step task: with loops, calls to child methods, creating objects etc. If anything goes wrong, you want to abandon the entire task and wait for the next input. You don't want to stubbornly keep trying to complete the task; because you'll either just get more errors, or worse: later changes will do the wrong thing because earlier required steps did not complete successfully.

Easy Logging of Errors

As mentioned earlier, if you simply leave an exception alone, the error will "bubble up" to an outer exception handler. And in a standard GUI application, that will be the default Application exception handler.

In fact at this stage, doing nothing special: if an exception is raised in the middle of the button click task described earlier, the default exception handler will show an error message to the user. The only thing missing is the logging. Fortunately, Delphi makes it easy for you to intercept the default Application exception handler. So you can provide your own implementation, and log the error as you desire.

All you need to do is:

  • Write a method using the TExceptionEvent signature.
    E.g. procedure MyHandleException(ASender: TObject; E: Exception);.
  • Then assign your custom handler using: Application.OnException := MyHandleException;

General Logging

Of course, this only covers logging of exceptions. If you want to do any other ad-hoc logging, you want to be able to reuse the code. This basically requires you to move your logging code into a separate unit that all your other units can call as needed.

So putting these things together you might do something as follows:

TLogger = class
public
  procedure WriteToLog(S: string);  //Performs actual logging of given string
  procedure HandleException(ASender: TObject; E: Exception); //Calls WriteToLog
end;

Then you might set it up in your program unit as follows:

begin
  Logger := TLogger.Create(...);
  Application.OnException := Logger.HandleException;
  Logger.WriteToLog('Custom exception handler has been assigned.');
  //Rest of application startup code
end;

Final Thoughts

I mentioned that if an unexpected exception occurs, you want to abandon the current task. But what happens if you've already done some things that should now be undone as a result of the error. Then you would simply implement it as follows:

try
  //Do something
except
  //Undo
  raise; //NOTE: more often than not, one should re-raise an 
         //exception so that callers are aware of the error.
end;

Note that this is the primary use for exception handlers. Yes they can also be used to swallow exceptions. But they should only swallow an exception if it has been truly resolved.

OTHER TIPS

While Delphi is object-oriented language, it can deal with non-OO entities as well.

Think, your main form is property of which form ?

The answer is that main form is not the property of ANY other form, it is a global object. Same exactly thing should be done about your function.

unit Main;

interface uses .......;

type TMainForm = class (TForm)....
....
     end;


Procedure LogError(const message: string);

implementation uses .....;

Procedure LogError(const message: string);
begin
...
end;

(**********)

procedure TMainForm.FormCreate(...);
begin
....
end;

...
end.

Now even better option would be to totally decouple LogError from ANY form at all.

Unit MyLogger;

interface uses ....; // but does not uses Main or any other form

procedure SetupLog(....); 
// to initialize it and change any settings you may wish
// maybe you would choose to save messages to file
// or use Windows debug interface - OutputDebugString
// or to a network SysLog server
// or just WriteLn it to a text window
// or to some TMemo in a graphic window
// or to file AND to a memo - that also might make sense.

// just keep it extensible

Procedure LogWarning(const message: string);
Procedure LogError(const message: string);

implementation uses ...;

function GenericLoggingSink(.....);
begin
...
end;

Procedure LogError(const message: string);
begin
  GenericLoggingSink( Log_Message_Type_Error, message, ... );
end;

Procedure LogWarning(const message: string);
begin
  GenericLoggingSink( Log_Message_Type_Warning, message, ... );
end;

And your Main form should just use this unit and this function on the same terms as all other forms in your application.

As part three I suggest you think what you want from your logging procedures. Actually doing this only makes sense for very simplistic logging needs. Very simplistic.

Otherwise just take any existing Delphi logging frameworks and utilize a lot of functionality implemented there. For example can you just log an object of any class you would write?

The only reason to make your own logging library is "my program is so simplistic, that it does need only the most primitive logging. It would be faster to improvise my own system, than to copy-paste library initialization and setup from examples to a ready-made libraries".

Log4Delphi and Log4D are well-known FLOSS libraries, though they look somewhat abandoned. Maybe there just is nothing left to be extended. Those are most old ones, but there also are some newer FLOSS libraries, an easy example being http://blog.synopse.info/post/2013/02/03/Log-to-the-console

If anything, you can read their texts and learn from them.

There are also commercial libraries like those listed at Is there a way to do normal logging with EureakLog? and Delphi itself is shipped with a limited version of CodeSite logging framework.

Delphi IDE enhancements like CnWizards and GExperts also do come with a simplified debug-oriented logging interface.

Google would bring you even more options if you'd dare :-)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top