Jump to content
HalfBlindCoder

How to handle delphi exception elegantly with logging feature.

Recommended Posts

I am new to Delphi. Presently learning error handling. I want to know what is the best way to handle error and exception in Console app a s well as VCL.

Thank you.

Share this post


Link to post

This question is way too broad to be able to be answered correctly. In general, have Try..Except blocks.

What you do inside the exception handler completely depends on the kind of application you are writing.

 

If you don't want your application to crash / misbehave after an exception happened you have to make sure you "reset it to a default state". Roll back transactions, close datasets, free up objects, closing TCP connections, etc. You might want to make a note of it too for debugging purposes so write details to a log file and let your user know that something went south.

It's also good to have stack traces so look into MadExcept / DebugEngine - both are really easy to install and can be used for free - with restrictions of course.

  • Like 2

Share this post


Link to post

hi, one solution from the infinity.

// on login form.
procedure TFormLogin.Log(uiMessage: String);
var
  TimeStamp : TDateTime;
begin
  TimeStamp := Now;
  TMemo_Log.lines.add('--'+DateTimeToStr(TimeStamp)+sLineBreak
                +uiMessage);
end;

// write to file..
procedure TFormLogin.FormDestroy(Sender: TObject);
var
  BOM: WideChar;
  FS: TFileStream;
  WS: WideString;
  I: Integer;
begin
  log(']---[Program Close]---['+sLineBreak
     +'-----------------------------'+sLineBreak
	 +'Server='+f_ip+sLineBreak
	 +'Port='+f_port+sLineBreak
	 +'RoleName='+rolename+sLineBreak
	 +'SharedGlobals.DataBase='+SharedGlobals.DataBase+sLineBreak
	 +'Protocol='+Protocol+sLineBreak
	 +'CharacterSet='+CharacterSet+sLineBreak
	 +'ExtendedMetadata='+ExtendedMetadata+sLineBreak
	 +'-----------------------------'+sLineBreak
	 +'DB name='+LOGONNAME+sLineBreak
	 +'DB password='+LOGONPASSWORD+sLineBreak
	 +'ui name='+LOGINNAME+sLineBreak
	 +'ui password='+LOGINPASSWORD+sLineBreak
	 +'-----------------------------');

  // write out to file!
  FS := TFileStream.Create('.\ini\Log.txt', fmCreate);
  try
    BOM := WideChar($FEFF);
    FS.WriteBuffer(BOM, SizeOf(BOM));
    For I := 0 to  mLog.Lines.Count-1 do
    begin
      WS := WideString( mLog.Lines[I] + sLineBreak);
      FS.WriteBuffer(PWideChar(WS)^, Length(WS) * SizeOf(WideChar));
    end;
  finally
    FS.DisposeOf;
  end;
end;


/// then every time you use Showmessage('fcuk.. it is wrong..');
/// cal FormLogin.Log('nope.. error here.. or something');

{$region 'Business Logic'}

// check every success and failed database talc.. like.:

function TdmWork19.GetMGR_WP(aList: dmTombMGR_WP; out Error: String): Boolean;
var
    fSor  : dmSorMGR_WP;
    Qcon  : TFDConnection;
    Q : TFDQuery;

    calc : Float32;

    n:integer;
begin
  Error := '';

  Qcon  :=  TFDConnection.Create(Self);
  try
    FormLogin.DBCon(QCon);
    Q :=  TFDQuery.Create(Self);
    try
      Q.Connection  :=  Qcon;
      try
        if aList<>nil then
        begin


          Q.SQL.Text  :=  ('select '+sLineBreak
                          +' extract(year from ITEM_DONE) as ForYear, '+sLineBreak
                          +' extract(month from ITEM_DONE) as ForMonth, '+sLineBreak
                          +' extract(day from ITEM_DONE) as ForDay, '+sLineBreak
                          +' Count(ITEM_Z) as db, '+sLineBreak
                          +' sum(ITEM_Z) as fm '+sLineBreak
                          +'from MGR_WP '+sLineBreak
                          +'group by ForDay'+sLineBreak
                          +'order by ForYear desc, ForMonth desc, ForDay desc');



          Q.Open;
          try
            if Q.IsEmpty then
            begin
              Error := Error+'Database empty';
              FormLogin.Log(Error); //here save out
              Result  :=  Boolean(0);

            end
            else
            begin
              aList.Clear;


              n:=0;
              while not Q.Eof do
              begin

                fSor.FORYEAR := Q.FieldByName('FORYEAR').AsInteger;
                fSor.FORMONTH := Q.FieldByName('FORMONTH').AsInteger;
                fSor.FORDAY := Q.FieldByName('FORDAY').AsInteger;

                fSor.DB :=  Q.FieldByName('DB').AsInteger;
                calc:=  Q.FieldByName('FM').AsInteger;
                fSor.FM := trunc(calc/10);



                aList.Add(fSor);
                Q.Next;
                inc(n,1);
              end;
              Error:=Error+sLineBreak+'records number.: '+n.Tostring;
              FormLogin.Log(Error);
              Result  :=  Boolean(1);
            end;
          finally
            Q.Close;
          end;

        end;
      except
        on E : Exception do
        begin
          Error := Error+sLineBreak+'n='+n.Tostring+sLineBreak+('GetMGR_WP'+sLineBreak+'Exception class name =  		'+E.ClassName+SLineBreak+'Exception message = '+E.Message);
          FormLogin.Log(Error);
          Result := Boolean(0);
        end;

      end;
    finally
      Q.DisposeOf;
    end;
  finally
    Qcon.DisposeOf;
  end;
end;



{$endregion}

{$region 'user interface'}

//here check if your user build-up interface object is good or wrong.


procedure TMunkaAllomas_19.GetListView1;
var
  uiError : String;
  item  :   TListViewItem;
  fSor  :  dmSorMGR_WP;
  f : Float32;
  dt : TDateTime;
  DayName : String;

  ev,honap,nap : word;
begin
  if GetData.GetMGR_WP(FdmTombMGR_WP,uiError)=Boolean(1) then
  begin

    ListView1.ApplyStyleLookup;
    ListView1.Items.Clear;
    ListView1.BeginUpdate;
    try
      try
        for fSor in FdmTombMGR_WP do
        begin
          item  :=  ListView1.Items.Add;
          try
            ev := Word(fSor.FORYEAR);
            honap := Word(FSor.ForMonth);
            nap := Word(FSor.ForDay);
          finally
            dt := EncodeDate(ev, honap,nap);  
          end;
          if SharedGlobals.WeekEndCheck(dt,DayName)=Boolean(1) then
          begin
            item.Objects.FindObjectT<TListItemText>('DATUM').Text := DateTimeToStr(DT);
            item.Objects.FindObjectT<TListItemText>('NAP').Text := DayName
          end
          else
          begin
            item.Objects.FindObjectT<TListItemText>('DATUM').Text := DateTimeToStr(DT);
            item.Objects.FindObjectT<TListItemText>('NAP').Text := DayName
          end;
          item.Objects.FindObjectT<TListItemText>('DB').Text := fSor.DB.ToString+' [db] alkatrész.';
          f:= FSor.FM;
          item.Objects.FindObjectT<TListItemText>('FM').Text := Format('%.3f', [(f/1000)])+' [fm] anyag.';
        end;
      finally
        ListView1.EndUpdate;
      end;
    except
      on E : Exception do
      begin
        uiError:=uiError+sLineBreak+('ListView1'+sLineBreak+'Exception class name = '+E.ClassName+SLineBreak+'Exception message = '+E.Message);
        FormLogin.Log(uiError);
      end;
    end;
  end
  else ShowMessage(uiError);
end;


{$endregion}


 

Share this post


Link to post

Be careful, the above code is NOT thread safe. If you have multiple threads using the same logger you'll end up crashing said thread due to unhandled exception in the exception handler or simply corrupting your log file.

It's good for single threaded applications, though.

 

Edit: Having a second look I'd vote against this method in general. If you know you will dump everything in a file no matter what it's better to write to that file and read it back all up if you want to display it in the UI. This way you are simply wasting resources, especially if your log file grows large.

Also, appending to a memo has it's performance penalty which is extremely painful if items are arriving rapidly.

Edited by aehimself
  • Like 1

Share this post


Link to post
Result := Boolean(0);

I found that code hard to read mostly due to code formatting and naming. But this stood out when I scanned down. Why would you do this? why would you do this in Delphi? 

 

Result := false;

Result := true;

 

Surely this is more readable. Is there a performance thing going on underneath with a Boolean cast? 



On error/exception handling, as said above, far too broad to answer - more details would be needed or an example to help with this.

Share this post


Link to post
9 minutes ago, Martin Sedgewick said:

Why would you do this?

You shouldn't. It makes absolutely no sense. 

  • Thanks 1

Share this post


Link to post

⚠️ Disclaimer: Micro optimization advice ahead 😉

You only do it the other way around (interpret a bool as a number) to embed conditionals into your algorithm to avoid branching.

Edited by Stefan Glienke
  • Like 1
  • Thanks 1

Share this post


Link to post
On 6/23/2021 at 2:20 AM, Stefan Glienke said:

⚠️ Disclaimer: Micro optimization advice ahead 😉

You only do it the other way around (interpret a bool as a number) to embed conditionals into your algorithm to avoid branching.

How would "if smth = Boolean(0)" help in this compared to "if not smth"?

The only code I've seen where this could benefit is turning "if Condition then i := i + 1" to "i := i + Ord(Condition)"

Share this post


Link to post
On 6/23/2021 at 12:20 AM, Stefan Glienke said:

⚠️ Disclaimer: Micro optimization advice ahead 😉

You only do it the other way around (interpret a bool as a number) to embed conditionals into your algorithm to avoid branching.

Isn't that what Ord() is for? 

Share this post


Link to post
24 minutes ago, Fr0sT.Brutal said:

The only code I've seen where this could benefit is turning "if Condition then i := i + 1" to "i := i + Ord(Condition)"

Isn't that what I wrote?

20 minutes ago, David Heffernan said:

Isn't that what Ord() is for? 

Guess what I meant when I wrote "interpret" a bool as a number.

  • Like 1

Share this post


Link to post
3 minutes ago, Stefan Glienke said:

Isn't that what I wrote?

Seems like it is. I misunderstood your point

Share this post


Link to post
On 6/22/2021 at 10:03 PM, Martin Sedgewick said:

Why would you do this?

procedure TForm2.Button2Click(Sender: TObject);
var
  Bool : Boolean;
  Bool01 : Boolean;
begin

  Bool := false;
  Bool01 := Boolean(0);

  Button2.Text:=Bool.ToString+' - '+Bool01.ToString;
end;

// Boolean(0) equivalent False; 
// Boolean(1) equivalent True; 
// the reason is simple.. I am too lazy to change true-to false , write 1-to 0 is much faster.
// 

 

Share this post


Link to post
41 minutes ago, Stefan Glienke said:

Yeah, writing Boolean(0/1) is really much faster than false/true

nope... i mean change!!

ctrl+c - ctrl+v copy code sample, then change it... ^^

Edited by skyzoframe[hun]

Share this post


Link to post
2 hours ago, skyzoframe[hun] said:

the reason is simple.. I am too lazy to change true-to false , write 1-to 0 is much faster.

Change "true" to "not true" )))

  • Thanks 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×