Root > Solving bugs in your code > Logging

Logging

Previous pageReturn to chapter overviewNext page   

Often you need to collect additional data in order to successfully identify the bug in your application. You write ("log") additional useful information (events) into a single file ("log"), which is then attached to bug report. It is called "logging" (the act of keeping a log).

 

There are two engines in EurekaLog which you may use for logging purposes:

Routines in EMemLeaks unit can be used to mark (name) objects and other memory blocks. This is useful for memory leaks and other memory problems (name can help to identify object or otherwise anonymous memory block).
Routines in ELogging unit can be used to log any data into a single log file. This is useful to get idea of overall flow of your application.

 


 

EMemLeaks

You can set a name for any memory block allocated via EurekaLog memory manager (when leaks checking is active). MemLeaksName is a basic function for this task. For example:

 

GetMem(P, 1024);
MemLeaksName(P, 'P variable');
....

 

Here is how anonymous P memory block looks in leak and dump reports:

 

$06961F90 x 1 DATA [Data at $06961F90] 1024 bytes (total of 1024 bytes)

  (00318491){Project70.exe} [00718491] Unit62.TForm62.Button1Click (Line 33, "Unit62.pas") + $5

  (00247C57){Project70.exe} [00647C57] Vcl.Controls.TControl.Click (Line 7442, "Vcl.Controls.pas") + $8

  ... 

 

 

It is hard to understand what this leak is

 

And here is how it looks after naming it with MemLeaksName:

 

$06F91F90 x 1 DATA [[P variable] Data at $06F91F90] 1024 bytes (total of 1024 bytes)

  (00318491){Project70.exe} [00718491] Unit62.TForm62.Button1Click (Line 33, "Unit62.pas") + $5

  (00247C57){Project70.exe} [00647C57] Vcl.Controls.TControl.Click (Line 7442, "Vcl.Controls.pas") + $8

  ...

 

 

Now leak will show assigned name ("P variable")

 

You can convert any of the dynamically allocated types to a pointer. For example:

 

MemLeaksName(Pointer(fmMain), 'Main Form');
fmDialog := TDialogForm.Create(nil);
try
  MemLeaksName(Pointer(fmDialog), 'Dialog Form');
  ...
  Stream := TMemoryStream.Create;
  MemLeaksName(Pointer(Stream), 'Stream for Dialog Form');
  ....

 

The exception to this rule: strings and dynamic arrays. These types have headers at negative offsets - therefore you can not pass these types to MemLeaksName function directly. Use:

 

S := 'Message';
MemLeaksNameStr(Pointer(S), 'String For Message');
 
SetLength(Arr, 2);
MemLeaksNameDynArray(Pointer(Arr), 'Arr Dynanic Array');
Arr[0] := 5;
Arr[1] := 7;

 

EurekaLog is capable to extract some information about variables (for example, content of the string, dimensions of dynamic array, class name of objects, names of components). Therefore, it is not necessary to specify all tiny details.

 

BTW, you can change name again at any time.

 

 

You can also define a parent-child relationship between two memory blocks. These relationships will be using for grouping and merging of memory blocks during leaks checking or dumping (see CheckHeap or DumpAllocationsToFile function). For example:

 

MemLeaksOwn(Pointer(fmDialog), Pointer(Stream));

 

This makes Stream a child of fmDialog. This means that Stream will not appear in reports. Instead - its size will be added to fmDialog.

 

Here is how fmDialog and Stream will look in leak and dump reports before grouping:

 

 

Two independent leaks will be shown

 

And here is when you indicate that fmDialog is the owner of the stream:

 

 

Only parent block is shown, sizes are summed

 

 

Such construct is used to indicate ownership. Basically, it says that fmDialog is responsible for deleting Stream. If you want to give ownership to external code, simply use:

 

MemLeaksOwn(nil, Pointer(Stream));

 

This code will remove parent-child relationship.

 

You can also assign and name at the same time:

 

MemLeaksOwn(Pointer(fmDialog), Pointer(Stream), 'Stream for Dialog Form');

 

Of course, there are separate functions for strings and dynamic arrays:

 

MemLeaksOwnStr(Pointer(fmDialog), Pointer(S), 'Message for Dialog');
MemLeaksOwnDynArrayChild(Pointer(fmDialog), Pointer(Arr), 'Arr for Dialog');

 

 

ELogging

ELogging unit writes log into CodeSite-compatible format (.csl file format). CodeSite format is mostly textual. You can view it in advanced text editors (such as Notepad++) or use free CodeSite File Viewer tool. You can obtain it either via installing CodeSite (it is available via GetIt in latest RAD Studio IDEs) or download standalone installer.

 

Note: EurekaLog is not a replacement for CodeSite (or any other logging framework). EurekaLog has no log dispatcher, no live logging, no TCP/HTTP logging, no complex logging methods, etc. EurekaLog simply writes local report file. Only simple logging methods are available.

 

First, if you want a permanent log file - you have to create a new log file by calling ELogOpen function:

 

ExeName         := ParamStr(0);
LogFileName     := ChangeFileExt(ExtractFileName(ExeName), '.csl');
LogFolder       := ExpandEnvVars('%APPDATA%'); // ExpandEnvVars is declared in ESysInfo unit
FullLogFileName := IncludeTrailingPathDelimiter(LogFolder) + LogFileName;
 
ELogOpen(FullLogFileName);

 

This code will create new log file in the APPDATA folder (with the same name as .exe file). You can create log file in any location. We recommend to use .csl file extension.

 

Creating log file should be the first action by your code, before you call any other routines in ELogging unit. If you do not call ELogOpen function - logging will be performed into temporal file, which will be deleted when application exists. Always call ELogOpen function to get permanently saved log. Do not call ELogOpen function if you only want log file as attach to bug report (see below).

 

 

You can write to log using various ELog functions:

 

ELog('Say Hello');
ELog('I', I);
ELogMemoryAsHex('Stream', Stream.Memory, Stream.Size);

 

The result will be:

 

 

Resulting log being viewed in CodeSite File Viewer

 

See ELogging unit for more information on possible constructs.

 

You can alter created entries by assigning a category or colors:

 

ELog.FontColor(clRed).Log('Say Hello');
ELog.Color(clRed).Log('I', I);
ELog.Category('My Category').LogMemoryAsHex('Stream', Stream.Memory, Stream.Size);

 

 

As well as chain several modifiers together:

 

ELog.Category('My Category').Color(clBlack).FontColor(clWhite).Log('Say Hello');

 

 

You can also log entering and exiting functions:

 

procedure Test;
begin
  ELogEnter('Test').D;
 
  // your code here. 

  // Including other logging code, 

  // such as: ELog('Some log inside function');

end;

 

This code will log entering and exiting from the function, as well as indents log to the right, so any nested calls to ELog inside Test function will be written indented to the right.

 

 

You can log function parameters as well:

 

procedure Test(const AMsg: Stringconst AValue: Integer);
begin
  ELogEnter('Test').P('AMsg', AMsg).P('AValue', AValue).D;
 
  // your code here
end;

 

 

Please note, that you have to always call either D or C methods as the last action in chain - to indicate that function has no more arguments. Otherwise (if you forgot to close function call) indentation for nested log entries will be wrong. The difference between D and C is that D creates a normal (expanded) log entry, and C creates closed (collapsed) entry.

 

If you want to log a call to a method - simply specify an object/class as the first argument:

 

procedure TMyObject.Test(const AMsg: Stringconst AValue: Integer);
begin
  ELogEnter(Self, 'Test').P('AMsg', AMsg).P('AValue', AValue).D;
 
  // your code here
end;

 

If you want to log a returned value too:

 

function TMyObject.Test(const AMsg: Stringconst AValue: Integer): Integer;
var
  L: IEurekaLoggerParams; 
begin
  L := ELogEnter(Self, 'Test').P('AMsg', AMsg).P('AValue', AValue).D;
 
  // your code here, for example:

  Unit1.Test(AMsg, AValue);
  if AValue = 0 then
    Result := 0
  else
    Result := 1;
 
  L.Ret(Result);
end;

 

 

 

The existing log file will automatically be added to bug report during sending. When you receive bug report - the log will be shown as file attach:

 

 

Log file inside bug report

Double-click log to view

 

 

If you do not want to use EurekaLog logging, you can use any other logging framework - such as CodeSite, SmartInspect, or any other framework. You can use OnAttachedFilesRequest or OnZippedFilesRequest events (depending on if you want to send log as separate file or as packed inside EurekaLog report). For example:

 

// Example for CodeSite
 
var
  GDestFileName: String;
 
procedure SomeInitializationRoutine;
var
  Dest: TCodeSiteDestination;
begin
  GDestFileName := GetFolderTemp + // ESysInfo

    ChangeFileExt(ExtractFileName(ParamStr(0)), '.csl'); 
 
  Dest := TCodeSiteDestination.Create;
  Dest.LogFile.FilePath := ExtractFilePath(GDestFileName);
  Dest.LogFile.FileName := ExtractFileName(GDestFileName);
  Dest.LogFile.Active := True;
end;
 
procedure PackLogFile(const ACustom: Pointer; 

  AExceptionInfo: TEurekaExceptionInfo { EException }

  const ATempFolder: String

  AAttachedFiles: TStrings; 

  var ACallNextHandler: Boolean);
var
  LFileName: String;
  LStream: TFileStream;
begin
  { Get a temporary filename }
  LFileName := ATempFolder + ExtractFileName(GDestFileName);
 
  { Copy the log file }
  CopyFile(PChar(GDestFileName), PChar(LFileName), False);
 
  { Pack the file to EurekaLog's crash report }
  AAttachedFiles.Add(LFileName);
end;
 
initialization
  RegisterEventZippedFilesRequest(nil, PackLogFile); // EEvents
end.

 

 

// Example for SmartInspect

 

procedure PackLogFile(const ACustom: Pointer; 

  AExceptionInfo: TEurekaExceptionInfo; 

  const ATempFolder: String

  AAttachedFiles: TStrings; 

  var ACallNextHandler: Boolean);
var
  LFileName: String;
  LStream: TFileStream;
begin
  { Get a temporary filename }
  LFileName := ATempFolder + 'CrashLog.sil';
 
  { Save the log file }
  LStream := TFileStream.Create(LFileName, fmCreate);
  try
    Si.Dispatch('mem', 0, LStream);
  finally
    FreeAndNil(LStream);
  end;
 
  { Pack the file to EurekaLog's crash report }
  AAttachedFiles.Add(LFileName);
end;
 
initialization
  RegisterEventZippedFilesRequest(nil, PackLogFile);
end.




Send feedback... Build date: 2018-11-26
Last edited: 2018-06-14
PRIVACY STATEMENT
The documentation team uses the feedback submitted to improve the EurekaLog documentation. We do not use your e-mail address for any other purpose. We will remove your e-mail address from our system after the issue you are reporting has been resolved. While we are working to resolve this issue, we may send you an e-mail message to request more information about your feedback. After the issues have been addressed, we may send you an email message to let you know that your feedback has been addressed.


Permanent link to this article: https://www.eurekalog.com/help/eurekalog/logging.php