Link to home
Start Free TrialLog in
Avatar of henryreynolds
henryreynoldsFlag for United Kingdom of Great Britain and Northern Ireland

asked on

Handling exceptions ( hide exception from users create log file of exception)

Good day

I am running a end of day process at a hotel every night, my problem is that my program return error messages when a error occurred and then I get a phone call late at night.

Typical error messages are the following.

1.I create a pdf file of the report and then I copy it to a server, sometimes a error occur when the file gets copied and the the user see the delphi exception and phone me.

2.Sometimes the sql returns a error message  ( this shoudnt actualy happen but it is my fault and I need to fix the bugs)

What can I do when a exception occur, to completely hide or avoid Delphi to show the message and rather in the background create a log file and then send the log file to me by e-mail.

This will help me not to worry every night, and the users wont have to deak with error messages.

Thank you

Avatar of MerijnB
MerijnB
Flag of Netherlands image

ASKER CERTIFIED SOLUTION
Avatar of Geert G
Geert G
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
the unit uLogging
unit uLogging;
 
interface
 
procedure AddLog(Msg: string; DateTimeLinePrefix: boolean = True; LogPrefix: string = '');
 
implementation
 
uses Classes, SysUtils, SyncObjs;
 
var cs: TCriticalSection;
 
procedure AddToLog(LOG_PATH: string; LOG_MAXSIZE: integer; LOG_LIFETIME: double;
  Msg: string; LogPrefix: string = ''; DateTimeLinePrefix: Boolean = True);
var
  sr: TSearchRec;
  LogFile: TextFile;
  ValidFile, SearchPath: string;
  valid: boolean;
  j: integer;
  TimeStr : string;
begin
  cs.Enter;
  try
    SearchPath := IncludeTrailingPathDelimiter(Trim(LOG_PATH));
    if not DirectoryExists(SearchPath) then
      ForceDirectories(SearchPath);
    ValidFile := '';
    if FindFirst(format('%s%s*.log', [SearchPath, LogPrefix]), faAnyFile, sr) = 0 then
    try
      repeat
        if sr.Size < LOG_MAXSIZE * 1024 then
          ValidFile := sr.Name
        else
          if FileDateToDateTime(sr.Time) < Now - LOG_LIFETIME then
            DeleteFile(Pchar(SearchPath + sr.Name));
      until FindNext(sr) <> 0;
    finally
      FindClose(sr);
    end;
    try
      if ValidFile <> '' then
      begin
        j := 0;
        repeat
          try
            AssignFile(LogFile,format('%s%s',[SearchPath, ValidFile]));
            Append(LogFile);
            valid := true;
          except
            Inc(j);
            valid := false;
            sleep(500);
          end;
        until valid or (j > 2);
      end
        else
      begin
        j := 0;
        repeat
          try
            Rewrite(Logfile, Format('%s%s_%s.log', [SearchPath, LogPrefix, FormatDateTime('ddmmhhnn',now)]));
            valid := true;
          except
            Inc(j);
            valid := false;
            sleep(500);
          end;
        until valid or (j > 2);
      end;
      j := 0;
      repeat
        try
          if DateTimeLinePrefix then
          begin
            DateTimeToString(TimeStr, 'DD/MM/YYYY HH:NN:SS', Now);
            Msg := TimeStr + ' - ' + Msg;
          end;
          Writeln(LogFile, Msg);
          valid := true;
        except
          Inc(j);
          valid := false;
          sleep(500);
        end;
      until valid or (j > 2);
    finally
      CloseFile(LogFile);
    end;
  finally
    cs.Leave;
  end;
end;
 
procedure AddLog(Msg: string; DateTimeLinePrefix: boolean = True; LogPrefix: string = '');
begin
  // Keep messages 1 day and max log file size = 20K
  AddToLog(ExtractFilePath(ParamStr(0)), 20, 1, Msg, LogPrefix, DateTimeLinePrefix);
end;
 
initialization
  cs := TCriticalSection.Create;
finalization
  cs.Free;
end.
 

Open in new window

>>MerijnB
you forgot
www.madshi.net


I don't have that much experience with madshi, so I don't know if it can do what henryreynolds asked :)
the above samples create 2 files, the daytime log and the nighttime log
if you use madshi or eurekalog then all you need to is set the options in the app to send the mails

question:
do you really check your mails at night ?
and if not, do you really have to send them ?

if so, then you could still implement following to send the log files:
https://www.experts-exchange.com/questions/10032527/Sending-Mail-Attachments-in-Delphi.html
or
https://www.experts-exchange.com/questions/21152145/How-to-send-a-mail-with-an-attachment-with-outlook-from-Delphi-7.html
Not sure if I understand Geert_Gruwez: eurekalog has everything you need included: hiding, logging, and mailing to you (optionally including screen shot, etc).
madshi has all that too.
I just showed how it can be done without 3rd part software
Avatar of henryreynolds

ASKER

Hi

Sorry for getting back to all now, I dont care much about the e-mails, the most important for me is to hide the error message and just create a log of the error.

Here is a sample of my code, at this stage I am using a try and except.
How can I implement it in the except part.

This sample a create a pdf file using fast report and copy the file to a server, sometimes the server has a problem and then I get a error message.
try
      dmReport.frxPDFExport.ShowDialog := False;
      dmReport.frxPDFExport.ShowProgress := false;
      exportFileName :=   ExportFileName+'\Night Audit Statements For '+FormatDateTime('dd-mm-yyyy',dm.sTradingDate)+'.pdf';
      dmReport.frxPDFExport.FileName :=  trim(ExportFileName);
 
      dmReport.frxReport.Export(dmReport.frxPDFExport);
      memLog.Lines.Add('Export Report Successful');
   except
      memLog.Lines.Add('Export Report FAILED');
   end;

Open in new window

you don't need to catch the exceptions all over

the solutions provided catches *ALL UNHANDLED* exceptions and logs them to file
the application has a event handler to catch all these exceptions:

Application.OnException
Hi Geert

Thank you very much, but will this also catch a sql error  for example

Must I always use try and in the except and in the except part call the Raise exception
with qryData do
begin
   close;
   sql.clear.
   sql.add('select * from table_does_not_exists');
   try
      execute;
   except
      Raise Exception.Create('TABLE DOES NOT EXISTS.....');
   end
end;

Open in new window

yup no exctinctions are made
i was gonna say exceptions first ...
Hi Geert

Then I am sure this will solve my problem, because this was a very big headache for me.

I spoke to the guy who is in charged of the network and server at the hotel now, and he said to me he checked  the windows log file, and windows did a update last night and automatically restarted the server.

I really dont know what to do to prevent errors such as last night, because the night audit that the hotel run can take some times +- 30min, when the printing is done then all the exporting happens and I commit all the transaction. What is a safe method to make sure the server does not go offline, or must I just test before exporting that the server is still online.
server restarted ?
your application server ?
or your database server ?
or your print server ?
or both ?
Hi Geert

Here is a screen shot of the log of windows, I still cant believe windows will restart especially a server
restart.JPG
No I am worried how I can protect my application and database, especially when there are open transaction like last night and windows are restarting.

And how must I handle such cases, because when the night audit start running I update a field in the database to say that the night audit is currently running, I use this to prevent users to log on and process documents.

But I think with this method of yours I will bring up a message and tell the user to restart the night audit process
what database are you using ?
night audit ???
what would you be doing then ?
Firebird 2
Night Audit, the process that create accommodation charges and print all the reports and move the date on to the next day
@Geert,
I don't agree with your statement that ou don't need to handle exceptions "all over".  If you have one universal exception handler, then it is, IMHO, considerably more difficult to determine what went wrong and where.  My preference is for wrapping the various potential exceptions (or, at the very least, the code in a procedure of function) in a TRY . . . EXCEPT and I quite frequently Wrap them in TRY TRY . . . EXCEPT . . . END FINALLY so that I can make sure that things get cleaned up even if there is an exception thrown.  
Admittedly, I often have a universal exception handler for those exceptions that float that far out.  However, relying only on that for handling all of your exceptions i, again IMHO, an unsound approach to coding in Delphi.
@ Geert

Maybe Diver has a point, is it possible to use this method on the  night audit routine only or what do you suggest
>>8080_Diver
this is just for unhandled exceptions

if you want to handle exceptions while your audit is run (see snippet)


this will create files like "auditxxxxx.log"

uses uLogging;
 
try
 
except
  on E: exception do 
    AddLog(E.Message, True, 'audit');
end;

Open in new window

@Geert,
you don't need to catch the exceptions all over

That was what I was responding to. ;-)
@all,
 
By the way, I have a small unit that I created (about 8 years ago now) that I am willing to provide for free to anyone who is interested.  It allows you to write to one or more log files from a Delphi app (although, all of the log files will have the same name as the App while having different "types, e.g. ".log", ".err").  It also has provisions for indenting and unindenting (outdenting?).  It will date and time stamp each entry in the log and can date stamp the loag file name (e.g. "appname_20090911.log").
If nothing else, the price is right. ;-)
>>8080_Diver
you may look at my earlier posts in this Q ...
-> geert

I am not 100% with you , what must I do to ignore your exception method and allow for normal exception handling in other modules.
In order to handle the exceptions in the rest of your code, you need to wrap code segments in TRY . . . EXCEPT . . . END blocks.  In the Except portion, you need to capture the exception, determine (during the creation of the code) what you can and cannot handle locally, how you will handle things locally, and whether you will reraise the exception for the next outer level.  As I mention above, I usually include TRY . . . FINALLY . . . END code so that I can make sure that things like database connections or transactions are handled and closed (or rolledback) gracefully.
try
  try
    MyAdoConnection.Connect;
      .
      . 
    try
      MyAdoQuery.Open;
      {do some work}
    except 
      on E:Exception do
        begin
          if {exception can be handled}
          then begin
            {handle the exception}
          end
          else begin
            {do whatever set up is required}
            raise Excpetion.Create('meaningful text for humans;' +
                                   #13#10 + E.Message);
          end; {if}
        end; {on exception}
    end; {try}
  finally
    {do any necessary housekeeping}
    if  (MyAdoConnection.Connected)
    then begin
      MyAdoConnection.Close;
    end; {if}
  end; {try}
 
 

Open in new window

elaborating further from your example


with qryData do
begin
   close;
   sql.clear.
   sql.add('select * from table_does_not_exists');
   try
      execute;
   except
      on E: Exception do 
        AddLog('TABLE DOES NOT EXISTS. (' + E.Message + ')', True, 'audit');
   end
end;

Open in new window

-> geert

I have worked with your smple code last night in my application, and it will work, but is it possible that I only use the exception method in my one unit, this will mean when the user enter that form I activates
Application.OnException := CatchException;
and when the user is finished in that unit I restore Application.OnException := " to default", this mean in all other units delphi will raise exceptions as normal.

what I ment from my example is , normally my exception come from sql sometimes I forget to assign privelages to all users or I forgot to compile a new procedure and delphi cant find that procedure.

I all my units except the one unit that runs the night audit, delphi my riase the normal exception, but in my night audit unit the exception must be catch by your method.

* i am increasing the points, because I think you and diver spend a lot of valuable time on my problem thank you.
with qryData do
begin
   close;
   sql.clear.
   sql.add('select * from table_does_not_exists');
   try
      execute;
   except
      Raise Exception.Create('TABLE DOES NOT EXISTS.....');
   end
end;
 

Open in new window

increasing points because of valueble help and I need to understand more to solve m y problem and other exceptions problems thanx @geert and @diver
if you only want to catch it in 1 unit
you don't use Application.OnException

you use :

try
  problemcode
except
   on E: Exception do
      AddLog('procedure X caused : ' + E.Message, True, 'audit');
end;

if you want different logs for other units just change 'audit' into something else
the line on E: Exception allways for further investigating of the error

as i read your code, it seems there are some major problems in your program
because of windows updates and server reboot

the one thing strikes me as odd:
the windows patches are automatically deployed on the server and automatically rebooted
i would strongly advise against this
they should be controlled and the reboot should be done in a controlled way
we do monthly reboots too because of the windows updates
we have the problem of working in a highly automated production facility
this means we have to warn production they will have no data for 30minutes while rebooting
obviously somebody has failed to see the significance of communicating with you about this rebooting
and patching

you should work out a policy with them so you know when the server is going down
it would save you a lot of headache

otherwise you would have to program n-tier where the application server buffers while the database is down
off course, you would be screwed if they bring down the app and db server at the same time
which looking at things they would do without a single thought as to why not
I forget to assign privelages to all users or ...
i dunno about firebird, but i use roles in the database
you assign all priviliges to a role (or more roles) and assign the roles to the user
make such a user for yourself and then test with that

should let you forget less priviliges

for the other parts ... google for dunit testing
-- geert

If my application has two forms, I want on the one form normal exceptions by delphi and on the other form I want exceptions been ignored (I want to use your code  ).

How can I implement this ?

I have include a sample app, I don't think you will be able to run it because I have components from devart on my form, but this will give you a idea.

Regards

Henry
unit formUseDelphiException;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, IBC, DB, MemDS, DBAccess;
 
type
  TfrmUseDelphiException = class(TForm)
    Button1: TButton;
    IBCConnection1: TIBCConnection;
    dsData: TIBCDataSource;
    qryData: TIBCQuery;
    IBCTransaction1: TIBCTransaction;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  frmUseDelphiException: TfrmUseDelphiException;
 
implementation
 
uses
  formIgnoreDelphi;
 
 
{$R *.dfm}
 
 
 
 
/////////////////////// THIS form must use delphi exception /////////////////////////////
 
 
procedure TfrmUseDelphiException.Button1Click(Sender: TObject);
var 
 sQryResult : string;
begin
  with qryData do
   begin
      close;
      sql.Clear;
      sQryResult := Format('Select oooo from holders',
                 [])  ;
      sql.Add(sQryResult);
      prepare;
      try
         execute;
      except
      end;
   end;
end;
 
procedure TfrmUseDelphiException.Button2Click(Sender: TObject);
begin
   Application.CreateForm(TfrmIgnoreDelphi, frmIgnoreDelphi);
   try
      frmIgnoreDelphi.ShowModal();
   finally
      frmIgnoreDelphi.Free;
   end;
end;
 
 
end.
 
 
 
 
//this form must ignore delphi exceptions and log it
 
unit formIgnoreDelphi;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, DB, MemDS, DBAccess, IBC, StdCtrls;
 
type
  TfrmIgnoreDelphi = class(TForm)
    Button1: TButton;
    dsData: TIBCDataSource;
    qryData: TIBCQuery;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
     procedure CatchException(Sender: TObject; E: Exception);
     procedure AddErrorLog(aMessage: string);
  public
    { Public declarations }
  end;
 
var
  frmIgnoreDelphi: TfrmIgnoreDelphi;
 
implementation
 
uses
   uLogging;
 
{$R *.dfm}
 
procedure TfrmIgnoreDelphi.AddErrorLog(aMessage: string);
 
var logPrefix: string;
begin
  logPrefix := 'daytime';
  if (Frac(Now) > 1/24*18) or (Frac(Now) < 1/24*7) then
    logPrefix := 'nighttime';
  AddLog(aMessage, True, logprefix);
end;
 
procedure TfrmIgnoreDelphi.CatchException(Sender: TObject; E: Exception);
begin
   AddErrorLog(E.Message);
end;
 
procedure TfrmIgnoreDelphi.FormCreate(Sender: TObject);
begin
   Application.OnException := CatchException;
end;
 
 
procedure TfrmIgnoreDelphi.Button1Click(Sender: TObject);
begin
   with qryData do
   begin
      close;
      sql.Clear;
      sql.Add('select Koos from Holders');
      try
         execute;
      except
        on E: exception do
           Raise Exception.Create('Hey, you got an exception !');
      end;
 
   end;
 
end;
 
 
end.

Open in new window

if you want to log specific exceptions for a specific task to a specific file
then ... you guessed it ... you'll have to get specific
and ... application.onException is not specific

using your code sample:

unit formUseDelphiException;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, IBC, DB, MemDS, DBAccess;
 
type
  TfrmUseDelphiException = class(TForm)
    Button1: TButton;
    IBCConnection1: TIBCConnection;
    dsData: TIBCDataSource;
    qryData: TIBCQuery;
    IBCTransaction1: TIBCTransaction;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
 
var
  frmUseDelphiException: TfrmUseDelphiException;
 
implementation
 
uses
  formIgnoreDelphi;
 
 
{$R *.dfm}
 
 
 
 
/////////////////////// THIS form must use delphi exception /////////////////////////////
 
 
procedure TfrmUseDelphiException.Button1Click(Sender: TObject);
var 
 sQryResult : string;
begin
  with qryData do
   begin
      close;
      sql.Clear;
      sQryResult := Format('Select oooo from holders',
                 [])  ;
      sql.Add(sQryResult);
      prepare;
      try
         execute;
      except
        // um ... you are throwing away the exceptions here 
        // or is this what you wanted ?
      end;
   end;
end;
 
procedure TfrmUseDelphiException.Button2Click(Sender: TObject);
begin
   Application.CreateForm(TfrmIgnoreDelphi, frmIgnoreDelphi);
   try
      frmIgnoreDelphi.ShowModal();
   finally
      frmIgnoreDelphi.Free;
   end;
end;
 
 
end.
 
 
 
 
//this form must ignore delphi exceptions and log it
 
unit formIgnoreDelphi;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, DB, MemDS, DBAccess, IBC, StdCtrls;
 
type
  TfrmIgnoreDelphi = class(TForm)
    Button1: TButton;
    dsData: TIBCDataSource;
    qryData: TIBCQuery;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
     procedure AddErrorLog(aMessage: string);
  public
    { Public declarations }
  end;
 
var
  frmIgnoreDelphi: TfrmIgnoreDelphi;
 
implementation
 
uses
   uLogging;
 
{$R *.dfm}
 
procedure TfrmIgnoreDelphi.AddErrorLog(aMessage: string);
begin
  AddLog(aMessage, True, 'audit');
end;
 
procedure TfrmIgnoreDelphi.Button1Click(Sender: TObject);
begin
   with qryData do
   begin
      close;
      sql.Clear;
      sql.Add('select Koos from Holders');
      try
         execute;
      except
        on E: exception do
           AddErrorLog('Hey, you got an exception :' + E.Message);
      end;
 
   end;
 
end;
 
 
end.

Open in new window

--Geert

the form "formUseDelphiException"  must use the normal Application exception, it must not use the addlog method.

Because I want my application to throw exception it there is maybe errors in my code, and I dont know anout it.

try
   execute;
except
   //the application must raise a exception on its own if there was a problem e.g maybe I have call my     procedure incorrect
end

this will throw away your exception

try
   execute;
except
end

if you want a exception, then don't put any try except around it
or at least reraise it

try
   execute;
except
  on E: Exception do
  begin
    AddLog(E.Message); // if you want to log it
    Raise; // this raises the same exceptions again
  end;
end;

Geert sorry to be so stupid now, if I understand you correctly the following code wont raise a exception

try
   execute;
except   // is it because there is nothing in the except part ???
end

but this will raise a exception
try
   execute;
except
  ShowMessage('ERROR IN APP');
end

You see my problem is, places where I call sql statements I do the following

with qryData do
begin
   close;
   sql.clear;
  sql.add('select * from ..... ');
  execute;
end;

* i dont use try and except, I think this is very wrong of me or what ?
there are different ways  of raising and handling exceptions:

following code:
try
   execute;
except   // is it because there is nothing in the except part ???
end
explanation:
yup, all exceptions are deleted because the except end is empty

following code:
but this will raise a exception
try
   execute;
except
  ShowMessage('ERROR IN APP');
end
explanation:
this raises an exception, you catch it, and you don't care which it is
you show a message to the user 'ERROR IN APP'

following code:
with qryData do
begin
   close;
   sql.clear;
  sql.add('select * from ..... ');
  execute;
end;
explanation:
the system will raise a exception on the execute
if you have not altered the default exception handler (Application.OnException) then a message is show to the user with exception.Message
otherwise your handler is used and you do what you want with it in there

you need to clearly make out the following in each piece of code:
1: do i catch any exceptions ?
2: do i log the exceptions ?
3: do i show a message to the user ?
4: do i reraise the exception ?
5: do i need to provide a overal exception handler to catch this exception ?

5 could be the eurekalog or madshi option

you seem not to be aware of the different ways of catching a exception.
the delphi help (at least the version 7) is a good place to start reading on this
read chapter 14 of this book:
http://veerle-en-geert.be/delphi/ebooks/Delphi%20-%20Teach%20Yourself%20Borland%20Delphi%204%20in%2021%20Days.pdf
* i dont use try and except, I think this is very wrong of me or what ?

that depends on what the code does
I refer to the following statement

following code:
with qryData do
begin
   close;
   sql.clear;
  sql.add('select * from ..... ');
  execute;
end;
explanation:

*********************************************************************************************
the system will raise a exception on the execute
*if you have not altered the default exception handler (Application.OnException) then a message is   show to the user with exception.Message
otherwise your handler is used and you do what you want with it in there

** If I have altered the default exception handler, how do I set it back to the default handler.
well the default handler for onexception = nil

this is how the application object handles exceptions

procedure TApplication.HandleException(Sender: TObject);
begin
  if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
  if ExceptObject is Exception then
  begin
    if not (ExceptObject is EAbort) then
      if Assigned(FOnException) then
        FOnException(Sender, Exception(ExceptObject))
      else
        ShowException(Exception(ExceptObject));
  end else
    SysUtils.ShowException(ExceptObject, ExceptAddr);
end;

so if you want to set it back to the default:
Application.OnException := nil;

basically the default handler comes down to

try

except  
  on E: Exception do  
    Application.ShowException(E);
end;

or if you want to be absolutely sure:
 
type
  TfrmIgnoreDelphi = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    fSaveExceptionHandler: TExceptionEvent;
    procedure CatchException(Sender: TObject; E: Exception);
  end;
 
var
  frmIgnoreDelphi: TfrmIgnoreDelphi;
 
procedure TfrmIgnoreDelphi.CatchException(Sender: TObject; E: Exception);
begin
  AddErrorLog(E.Message);
end;
 
procedure TfrmIgnoreDelphi.FormCreate(Sender: TObject);
begin
  fSaveExceptionHandler := Application.OnException;
  Application.OnException := CatchException;
end;
 
procedure TfrmIgnoreDelphi.FormDestroy(Sender: TObject);
begin
  Application.OnException := fSaveExceptionHandler;
end;

Open in new window

-- Hi Geert

I think I am finished with you now, thank you very , very much. I am not closing the question now, because I think it is fare that you should earn all 500 points, but I must just think about it for a second or two.

Thank you very much

Henry
if need be, you can allways open more questions ...
in a second or two.