2004. február 16., hétfő

Exception or Event Logger

Problem/Question/Abstract:

Each project has logical errors or by runtime. It would be fine to write these exceptions down to a file in order to find out, what's happened weeks ago ;) or to track component events

Answer:

Exceptions are mistakes and errors due to some run-time problem. This is obviously a wishy-washy definition, but generally run-time problems would be things like running out of memory whilst adding a data object or an index out of bounds. In our team we wrote a procedure (months ago), which intercepts those nasty things like exceptions by assigning a new event-handler in the main-unit:

{$IFDEF DEBUG}
Application.OnException := AppOnException;
{$ENDIF}

If DEBUG is not set the code runs at full speed, but I advise to set the handler all the time, cause then you can analyse each applications exception file. Normal testing should identify programming mistakes, whereas the other type of error are exceptions to the norm.
The event-handler goes like this:

procedure TCWForm.AppOnException(sender: TObject; E: Exception);
var
Addr: string[9];
FErrorLog: System.Text;
FileNamePath: string;
begin //writes errorlog.txt file
FileNamePath := extractFilePath(application.exeName) + 'errorlog.txt';
AssignFile(FErrorLog, FileNamePath);
try
System.Append(FErrorlog);
except
on EInOutError do
Rewrite(FErrorLog);
end;
Addr := IntToHex(Seg(ErrorAddr), 4) + ';' + IntToHex(Ofs(ErrorAddr), 4);
Writeln(ErrorLog, format('%s[%s]%s%s', [DateTimeToStr(Now),
getNetUserName, E.Message, Addr]));
System.Close(FErrorLog);
MessageDlg('CW5' + E.Message + '. occured at: ' + Addr, mtError, [mbOK], 0);
end;

To avoid scope conflicts, Assign File replaces the Assign procedure that was available in previous versions of Delphi. The Addr also depends on the OS. Note, that you want still see exceptions on the screen, you get it with the last MessageDlg in the AppOnException-routine.

Then you get an output in a well shaped manner:

*************************ERRORLOG************************************
26.09.99 12:09:16 [MAX] List index out of bounds 52FF;1226
26.09.99 13:05:28 [MAX] Database BezSpr not found 5F6F;1226
26.09.99 13:21:37 [THOMAS] List index out of bounds 69DF;1226
26.09.99 13:43:35 [MAX] GP fault in module CW5.EXE at 0002:3588 2A9F;1226
30.09.99 14:32:23 [SIMON] Cannot perform this operation on a closed dataset 320F;1254
30.09.99 14:35:36 [MAX] Record locked by another user. Table:GBK.DB

Maybe, the function getNetUserName has to be changed, it depends on the operating-system or the database you deal with:

function getNetUserName: string;
var
szVar: array[0..32] of char;
begin
DBIGetNetUserName(szVar);
result := StrPas(@szVar);
end;

Instead of Addr := by an Exception, use in a 32 - bit environment:

mem: TMemoryStatus;
mem.dwLength := sizeOf(TMemoryStatus);
GlobalMemoryStatus(mem);
edit3.text := intToStr((mem.dwAvailPageFile)  div 1024);
edit4.text := intToStr((mem.dwAvailPhys)  div 1024);

Component Event Logger

On the other side you want to know component events to track down user or system behavior. This is also usefull to show the events on runtime in a listbox or to store it in a file.
First your declare a procedure in your class:

events: TListBox;

procedure LogEvent(const EventStr: string; Component: TComponent = nil);

Second you define the procedure with a listbox as events:

procedure TransAct.LogEvent(const EventStr: string;
Component: TComponent = nil);
var
ItemCount: Integer;
begin
if (csDestroying in ComponentState) or not Events.Visible then
Exit;
if (Component <> nil) and (Component.Name <> '') then
Events.Items.Add(Format('%s(%s)', [EventStr, Component.Name]))
else
Events.Items.Add(EventStr);
ItemCount := Events.Items.Count;
Events.ItemIndex := ItemCount - 1;
if ItemCount > (Events.ClientHeight div Events.ItemHeight) then
Events.TopIndex := ItemCount - 1; //tracing
end;

Third you call the procedure LogEvent in your code as you want, e.g.:

LogEvent('OnDataChange', Sender as TComponent);
LogEvent('BeforeOpen', DataSet);
LogEvent('AfterClose', DataSet);

procedure TransAct.DataSetBeforeClose(DataSet: TDataSet);
begin
LogEvent('BeforeClose');
end;

procedure TransAct.DataSetError(DataSet: TDataSet;
E: EDatabaseError; var Action: TDataAction);
begin
LogEvent('OnDelete/OnEdit/OnPost Errors', DataSet);
end;

procedure TransAct.Disconnect(Connection: TADOConnection;
var EventStatus: TEventStatus);
begin
LogEvent('Disconnect', Connection);
end;

function TScanner.SaveLogData(const UserData: WideString; const CheckSum: DWORD):
Boolean;
var
SL: TStringList;
FileName: string;
begin
SL := TStringList.Create;
FileName :=
'D:\Scanner\LogData\' + FormatDateTime('yyyymmdd-hhnnsszzz', Now) + '.txt';
SL.Text := UserData;
SL.SaveToFile(FileName);
SL.Free;
Result := True;
end;



Nincsenek megjegyzések:

Megjegyzés küldése