EurekaLog
Menu

The EurekaLog Developer Guide

23. System logging from code

Use Windows event logging when an administrator needs to find application failures alongside other machine events, especially for a service with no interactive desktop. EurekaLog's System log reporting pseudo-dialog (edtService) adds an event without showing a window. It otherwise behaves like the no-dialog choice. Services may also use ordinary local bug reports; event logging is an option, not a requirement.

An event and a report are different artifacts. The default event contains the exception message and the full path to the bug report file. It does not embed that file or deliver it to your support system. Configure report storage and delivery separately (chapter 3), and ensure that the service account can write the report folder and the investigating administrator can read it.

To get a readable event, keep three values in agreement:

Artifact What it supplies
Message resource linked into an executable or resource DLL The template text and full message identifier
Installed event-source registration A source name and the path to the binary containing that resource
EurekaLog's System log reporting settings The same source name and full message identifier

A visible event whose description cannot be found usually means this agreement is broken. Do not mistake an event record for proof that the message resource or the separate report is available.

23.1 Plan the installed artifacts

The example in this chapter writes to the local machine's Application log. Choose a stable, application-specific source name; SampleService below is illustrative, not a required value. An installation may have more than one source for distinct components, but do not create a new source for every exception. Windows limits the number of registered sources to 16,384.

You need:

  1. Messages.mc, maintained with your project source.
  2. Microsoft's Message Compiler (mc.exe) and a resource compiler (rc.exe) on the build machine. The Windows SDK supplies these tools; use their installed paths or a build environment whose PATH contains them. Delphi/C++Builder's resource compiler can also compile the generated .rc.
  3. A linked executable or resource DLL deployed at a stable path on the target machine.
  4. An installer action with permission to create the event-source key under HKEY_LOCAL_MACHINE. Perform this as an elevated installation task, not while handling the first exception.
  5. EurekaLog configured with the matching source and event ID, followed by a test under the actual service/application account.

An event stores its insertion strings and a reference to a template, not a private copy of the whole template. Moving or removing the resource binary can therefore make old events unreadable. Plan resource retention and upgrades as well as initial installation.

Usually one error event is enough for all exceptions. Define additional informational or warning events only if your application needs distinct event semantics. This example defines one event with English and Italian text and two insertion strings: %1 for the exception message, %2 for the report path.

Save the following as Messages.mc in UTF-16LE. Keep the final newline after the last line containing a dot. Each language's message body ends with a dot on its own line.

MessageIdTypedef=DWORD

SeverityNames=(Success=0x0:STATUS_SEVERITY_SUCCESS
  Informational=0x1:STATUS_SEVERITY_INFORMATIONAL
  Warning=0x2:STATUS_SEVERITY_WARNING
  Error=0x3:STATUS_SEVERITY_ERROR
)

FacilityNames=(System=0x0:FACILITY_SYSTEM
  Runtime=0x2:FACILITY_RUNTIME
  Stubs=0x3:FACILITY_STUBS
  Io=0x4:FACILITY_IO_ERROR_CODE
)

LanguageNames=(English=0x409:MSG00409)
LanguageNames=(Italian=0x410:MSG00410)

MessageId=0x1
Severity=Error
Facility=Runtime
SymbolicName=mcEurekaLogErrorMessage
Language=English
There was an exception "%1" in the service.
See %2 file for more information.
.
Language=Italian
Si e verificata un'eccezione "%1" nel servizio.
Consultare il file %2 per ulteriori informazioni.
.

Run these commands from the directory containing the file, stopping if a command fails:

mc -u -U Messages.mc
rc -r Messages.rc

Lowercase -u selects UTF-16LE input; uppercase -U selects Unicode messages in the generated binary resource. Do not confuse either with the encoding of Delphi source files.

The first command generates Messages.h, Messages.rc and a .bin file for each language (MSG00409.bin and MSG00410.bin here). Inspect the generated header before configuring EurekaLog:

#define mcEurekaLogErrorMessage ((DWORD)0xC0020001L)

The full identifier is $C0020001, not 1. It combines severity, facility and message code: error severity contributes $C0000000, Runtime facility contributes $00020000, and the message code contributes 1. With those same severity/facility values, MessageId=0x2 would produce $C0020002. Use the generated value rather than assuming every event uses this layout's sample values; changing compiler flags or severity/facility can change the full ID.

The second command compiles Messages.rc and its referenced .bin files into Messages.res. For reference, the generated resource script for these two languages has the following structure:

LANGUAGE 0x10,0x1
1 11 "MSG00410.bin"

LANGUAGE 0x9,0x1
1 11 "MSG00409.bin"

Link the resource into the application. In a Delphi project source file, place this resource directive outside a procedure, alongside the project's other resource directives:

{$R Messages.res}

Alternatively, add the resource to the project using Project / Add to project. To have the Delphi IDE compile the resource script, use this directive instead and retain the .rc and all referenced .bin files:

{$R 'Messages.res' 'Messages.rc'}

For a repeatable command-line resource build, this batch file keeps the generated inputs and the header for inspection and stops on a failed compiler command:

@echo off
mc -u -U Messages.mc
if errorlevel 1 exit /b 1
rc -r Messages.rc
if errorlevel 1 exit /b 1

Have the parent build stop if this step fails; otherwise it could link a stale Messages.res. Do not delete every MSG*.bin in a shared directory as part of a generic cleanup step. When linking only the prebuilt .res, intermediate files are not runtime dependencies, but retain Messages.mc and the resource-build recipe. You may link the .res into a dedicated resource DLL instead of the main executable; registration must then point to that DLL.

23.3 Register the event source during installation

Create this key on the machine that will log the events:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\SampleService

Set these values:

Name Type Value in this example
EventMessageFile String Absolute path to the deployed executable containing Messages.res
TypesSupported DWORD $0007: error $0001, warning $0002, information $0004

If using a separate resource DLL, put its absolute path in EventMessageFile instead. The binary must contain the message resource; pointing to Messages.mc or the standalone .res is not enough.

The following helper belongs in your installer code or a service unit. It uses Windows, SysUtils and Registry. It registers one application-owned source and deliberately reports an installation failure instead of silently skipping a failed key creation. It assumes the installer has chosen a nonconflicting source name and has already deployed the resource binary.

uses
  Windows, SysUtils, Registry;

procedure RegisterApplicationEventSource(const SourceName,
  MessageBinary: String);
var
  Reg: TRegistry;
begin
  Reg := TRegistry.Create(KEY_WRITE);
  try
    Reg.RootKey := HKEY_LOCAL_MACHINE;
    if not Reg.OpenKey(
      'SYSTEM\CurrentControlSet\Services\Eventlog\Application\' + SourceName,
      True) then
      raise Exception.Create('Cannot create the application event-source key');
    Reg.WriteString('EventMessageFile', MessageBinary);
    Reg.WriteInteger('TypesSupported',
      EVENTLOG_ERROR_TYPE or EVENTLOG_INFORMATION_TYPE or EVENTLOG_WARNING_TYPE);
  finally
    Reg.Free;
  end;
end;

For a service with the resource in its own executable, wire the service's AfterInstall event to a handler that calls RegisterApplicationEventSource(Name, ParamStr(0)). That uses the service's Name as the event source; configure exactly that name in section 23.4. For a separate installer, pass the installed service executable's path, not the installer's ParamStr(0).

The reverse installer operation removes only the application's own source key. In a service this can be performed by its BeforeUninstall handler; a TRegistry with RootKey := HKEY_LOCAL_MACHINE can call DeleteKey with the same source path, and the installer must check its Boolean result. Do not remove the entire Application key or another application's source. Removing the source or its message binary affects later viewing of retained events: agree on archival/resource-retention policy before uninstalling them.

This chapter uses the standard Application log. The other standard logs are System and Security; custom logs are possible, but are a different deployment configuration, not a reason to change this example's registry path without also designing the corresponding registration.

23.4 Select System log reporting and prove the result

In Project / EurekaLog options / Dialogs, select System log reporting (edtService). Set the dialog's fields as follows:

Field Backing property Local example
Computer name .EventLogComputer Empty: current machine
Event source name .EventLogName SampleService, or the service Name used at registration
Category ID .EventLogCategory Empty: this example defines no categories
Message ID .EventLogEventID $C0020001, copied from Messages.h

Computer and category may remain empty for this workflow. Source name and message ID are required. These are the property names behind the settings, not a stand-alone Delphi assignment block. Rebuild and deploy the application with the resource and these settings together.

On a development or staging machine:

  1. Install/register the source using the deployed resource binary's path.
  2. Run the application under the account it will really use. Trigger a controlled exception through its normal handler, as in chapter 1; a service needs a development-only service action rather than an interactive GUI button.
  3. Open Windows Event Viewer / Windows Logs / Application. Refresh and filter by the configured source and the time of the test.
  4. Open the event. Confirm the exception text is substituted for %1 and the expected full report path for %2, without a missing-description warning.
  5. Open the separate report at that path. Then verify any configured sending independently using chapter 3. An event is not an upload receipt.
Observation Investigate next
No matching event Actual deployed settings, exception-handling route, source/computer name and the running account; first establish that EurekaLog processed the test exception.
Event exists, description cannot be found Registered source key, absolute EventMessageFile path, deployed message resource and full ID from the matching generated header.
Text appears but insertion values are wrong Order of FillData additions versus %1, %2, etc.; the template and override must be deployed together.
Event text is readable but report cannot be opened Separate report creation/path, service-account write rights, administrator read rights and retention.
Local event/report exists but nothing arrived remotely Delivery configuration and transport outcome, chapter 3.

Do not broaden service privileges just to hide a setup problem. Register at installation with the needed administrative permission, then diagnose runtime access using the intended service identity.

23.5 Put different information into the event

The numbered placeholders (called qualificators in the EurekaLog reference) are insertion strings. By default, TEventLogDialog.FillData supplies exactly two:

procedure TEventLogDialog.FillData(const AData: TStrings);
begin
  AData.Add(ExceptionInfo.ExceptionMessage); // %1
  AData.Add(LogFileName);                    // %2
end;

This illustrates the built-in method, not code you add alongside it. The first Add supplies %1, the second %2, and so on. Their order in the displayed sentence can differ from their numeric order. The dynamic data is stored in each event, so review its privacy and retention consequences.

For a custom event containing the message, exception class, compact stack and BugID, add this complete unit to the project. It registers a descendant of EDialogService.TEventLogDialog during unit initialization; it does not replace a send engine. The formatter is temporary and is freed inside the override. Select System log reporting as in section 23.4.

unit CustomizedSystemLog;

interface

implementation

uses
  SysUtils, Classes, ECallStack, EDialog, EDialogService;

type
  TEventLogDialog = class(EDialogService.TEventLogDialog)
  protected
    procedure FillData(const AData: TStrings); override;
  end;

procedure TEventLogDialog.FillData(const AData: TStrings);
var
  Formatter: TCompactStackFormatter;
  CallStack: String;
begin
  Formatter := TCompactStackFormatter.Create;
  try
    CallStack := CallStackToString(ExceptionInfo.CallStack, '', Formatter);
  finally
    Formatter.Free;
  end;
  AData.Add(ExceptionInfo.ExceptionMessage); // %1
  AData.Add(ExceptionInfo.ExceptionClass);   // %2
  AData.Add(CallStack);                     // %3
  AData.Add(ExceptionInfo.BugIDStr);         // %4
end;

initialization
  RegisterDialogClassFirst(TEventLogDialog);

end.

The dialog's ExceptionInfo exposes the exception being reported; BugReport exposes the report text if a different customization needs it. Do not put an entire report into the event merely because it is accessible: choose the data an administrator actually needs.

For the four-string override, keep the severity/facility definitions from section 23.2, keep only the English LanguageNames entry if you are supplying only English, and replace the event block starting at MessageId=0x1 with this one:

MessageId=0x1
Severity=Error
Facility=Runtime
SymbolicName=mcEurekaLogErrorMessage
Language=English
There was an exception #%4 of type %2 with message:%n%1%nCall stack:%n%3
.

%n inserts a line break; it does not consume another insertion string. There are still four strings, used in the order %4, %2, %1, %3 in the text. Recompile the resource and relink it; the full event ID remains $C0020001 because severity, facility and code are unchanged. Re-run the readability check after deploying the override and resource together. The new %2 is the exception class, not the report path: do not interpret it using the original two-string template.

23.6 Choose the right log for the investigation

Use System log reporting for an exception event administrators can find in Windows Event Viewer. Use ELogging for application breadcrumbs, values and timings while the program works: open a log with ELogOpen, write entries, and flush deliberately when appropriate. Those entries go to a log file and can accompany a packed report; they do not alter the event template. Chapter 13 supplies the signatures, defaults and examples rather than duplicating the logging API here.

Use internal diagnostics only when investigating EurekaLog's own behavior, such as a failure during exception processing or unexpected overhead (chapter 5). Although diagnostic tracing can use the logging infrastructure, opening an application log alone does not request that internal trace. Finally, none of these mechanisms replaces report delivery and its outcome checks (chapter 3).