EurekaLog
Menu

The EurekaLog Developer Guide

24. Customising EurekaLog from code

Choose the narrowest change that solves the problem. A different report folder does not need a custom dialog; a special exception policy usually does not need a replacement exception manager.

What you need Use
Different defaults selected at startup CurrentEurekaLogOptions (section 24.1 and chapter 17)
A stable policy for an exception class or source location Filters or custom attributes (section 24.2)
Data or a decision for the exception being processed An event handler and its AExceptionInfo.Options (section 24.3)
Behaviour with no matching event, or a new sending method A registered descendant class (section 24.5)
An internal hook that has no public contract Not a normal integration route (section 24.7)

The code in this chapter belongs in the reporting executable or a standalone reporting module, not in a lightweight DLL. The lightweight profile deliberately excludes EClasses, EModules and the exception manager; make these customizations in its host (chapter 16).

24.1 Change a default before exceptions are raised

CurrentEurekaLogOptions in EModules returns the module's TEurekaModuleOptions object. Set defaults at startup, before starting workers or accepting requests. Do not free the returned object. This small procedure preserves local reports but stops sending:

uses
  EBase, EModules, ETypes;

procedure SelectLocalReporting;
begin
  if not IsEurekaLogActive then
    Exit;
  CurrentEurekaLogOptions.SaveLogFile := True;
  CurrentEurekaLogOptions.SenderClasses := esmNoSend;
end;

Call it from startup code. IsEurekaLogActive guards the accessor's fatal inactive-module case; it is a global status, not a per-thread status. Section 17.3 supplies the service/GUI variant, including account-dependent output paths and both dialog choices.

Before a runtime or filter-based switch to a different dialog, open Project / EurekaLog options / Advanced / Code / Dialogs and include that implementation. Do the equivalent under Advanced / Code / Send engines for every sender you may select, then rebuild. Selecting the default in the options normally includes its code automatically; alternatives selected only by your code need explicit inclusion. A class-name string cannot load missing code.

Build options cannot change after compilation. Hooks and debug-information providers are registered at startup, not dynamically switched by this options object. Leak/memory controls have separate APIs in chapter 12, and hang detection in chapter 14.

Every raised exception captures an options copy. Changing global defaults in an exception handler therefore affects future exceptions, not the one being processed. Use that exception's Options, as in section 24.3. The same ownership rule applies to dialogs, senders, builders and call stacks: their Options comes from their parent, or from the global default when no parent is supplied.

24.2 Behaviour for one exception type: filters and custom attributes

An exception filter alters EurekaLog's behaviour based on the properties of an exception. The documented ways to identify one are its class name (Exception, EAccessViolation, EStreamReadError, ...), its source location, the values of its properties, and whether it is handled or unhandled. Once identified, a filter can change its handler (none, RTL or EurekaLog), its dialog class, its error message, its action (restart or terminate), or mark it as expected. Handler none hides the exception completely

  • "No error dialog will be shown. No bug report will be created" - while handler RTL leaves it to the application's ordinary handling, "but no EurekaLog work will be done for it". Filters are applied before the exception is processed, from top to bottom, and "first matched filter is applied"; a filter "can also be created from code", through the ExceptionsFilters property of section 24.1.

To create a rule without code:

  1. Open Project / EurekaLog options / Advanced / Exception filters and enable Activate Exceptions Filters (ActivateFilters). With it disabled, the listed rules do nothing.
  2. Click Add. Identify the exception by its real class name and choose the required behaviour. Use RTL when your normal application handler should show the error without a EurekaLog report; use None only when deliberately suppressing both.
  3. Put specific rules above broader ones with Move up / Move down. The first match wins. By default a class-name match is exact; + includes descendants. [I] selects an inner chained exception where present. For message matching, = means exact text and % means contains.
  4. Save, rebuild and raise the target exception and a nearby nontarget case. Confirm the intended case changes and the other still reports normally.

Filters cannot alter final leak reporting (EMemoryLeak/EResourceLeak): their state has already been finalized when that check runs. Use the dedicated leak controls in chapter 12.

Custom attributes express the same thing at the exception class declaration, and their declarations are printed in full. They are declared in the EClasses unit:

type
  EurekaLogHandler = class(TCustomAttribute)
  strict private
    FHandler: TFilterHandlerType;
  public
    constructor Create(Value: TFilterHandlerType);
    property Handler: TFilterHandlerType read FHandler;
  end;

  EurekaLogMessage = class(TCustomAttribute)
  strict private
    FMessage: String;
  public
    constructor Create(Value: String);
    property Message: String read FMessage;
  end;

  EurekaLogAction = class(TCustomAttribute)
  strict private
    FAction: TFilterActionType;
  public
    constructor Create(Value: TFilterActionType);
    property Action: TFilterActionType read FAction;
  end;

  EurekaLogDialog = class(TCustomAttribute)
  strict private
    FDialog: TExceptionDialogType;
  public
    constructor Create(Value: TExceptionDialogType);
    property Dialog: TExceptionDialogType read FDialog;
  end;

  EurekaLogExpected = class(TCustomAttribute)
  strict private
    FURL: String;
    FContext: Integer;
    FBugID: TBugID;
  public
    constructor Create(const AContextNumber: Integer = -1;
      const AURL: String = ''; const ABugID: TBugID = 0);
    property URL: String read FURL;
    property Context: Integer read FContext;
    property BugID: TBugID read FBugID;
  end;

The examples attach them to the class declaration, and their comments say what each buys:

  • [EurekaLogHandler(fhtRTL)] - "ETestException will be ignored by EurekaLog and it always will be handled by your application (as if EurekaLog would be disabled)".
  • [EurekaLogMessage('Sorry, there was an error.')] - "Exception message will be replaced".
  • [EurekaLogDialog(edtEurekaLogDetailed)] - "Switch dialog to EurekaLog-Detailed style".
  • [EurekaLogExpected()] - "Mark exception as expected". With a context number, [EurekaLogExpected(1234)] also assigns help topic ID 1234, which shows the "Help" button in dialogs; [EurekaLogExpected(0, 'http://www.example.com/kb/low_memory.asp')] assigns an online help topic instead; [EurekaLogExpected(-1, '', $C4F10001)] assigns a fixed BugID.

For example, on a Delphi compiler with custom-attribute RTTI support, put this declaration in the unit that defines the application's exception classes:

uses
  SysUtils, EClasses, ETypes;

type
  [EurekaLogHandler(fhtRTL)]
  EUserCorrectableError = class(Exception);

Raise EUserCorrectableError for the specific condition you have chosen to handle through the ordinary application path. Do not attach this policy to the base Exception class merely to silence reports globally.

What to know before using them:

  • They are filters. "internally custom attributes just creates new filter rule" - the same behaviour, expressed at the declaration instead of in the options.
  • They need extended RTTI: "New Delphi and C++ Builder IDEs offer extended RTTI with support of custom attributes", and the code "will be ignored if there is no EurekaLog installed".
  • Aliases do not work. EMyException1 = class(Exception) can be named in a rule; EMyException2 = Exception cannot, "because there is no such class" - name Exception instead.
  • A message override is visual only. It affects the error message in dialogs; bug reports "always contain information about original exception (with original error message)".
  • The availability caveat of section 24.1 applies: a rule that selects a dialog class needs that dialog's code in the application.

24.3 Events: EurekaLog calling your code

Use a TEurekaLogEvents component when the handler genuinely depends on a form. Its registrations exist only while the form exists, so they do not cover failures before form construction or after destruction. For application-wide policy, register a procedure from a unit instead.

This complete unit keeps local reporting for EReportLocalOnly exceptions while disabling sending for those exceptions alone. Add it to the reporting project; no form or UI synchronization is needed because the callback only touches the supplied exception's options:

unit ReportPolicy;

interface

uses
  SysUtils;

type
  EReportLocalOnly = class(Exception);

implementation

uses
  EEvents, EException, ETypes;

procedure ApplyLocalPolicy(const ACustom: Pointer;
  AExceptionInfo: TEurekaExceptionInfo;
  var AHandle, ACallNextHandler: Boolean);
begin
  if AExceptionInfo.ExceptionClass = EReportLocalOnly.ClassName then
  begin
    AExceptionInfo.Options.SaveLogFile := True;
    AExceptionInfo.Options.SenderClasses := esmNoSend;
  end;
end;

initialization
  RegisterEventExceptionNotify(nil, ApplyLocalPolicy);
finalization
  UnRegisterEventExceptionNotify(nil, ApplyLocalPolicy);
end.

The incoming AHandle and ACallNextHandler values are deliberately left unchanged: the handler does not cancel reporting or claim exclusive control of the callback chain. Setting AHandle to False instead would suppress EurekaLog processing for the exception. Chapter 7 gives the event ordering, action flags and other event contracts.

For a real application policy, raise an ordinary exception and an EReportLocalOnly from the same controlled test path. Both should follow the normal report policy except that the latter does not send. Do not use this exception type for failures that your operational policy requires to be delivered automatically.

Match every dynamic registration with its corresponding UnRegisterEvent<Name> before destroying the object or state the callback uses. The unit above unregisters during finalization; an object method should unregister before its object is destroyed. Process-lifetime procedures with no finalized dependencies can remain registered, but that is a lifetime decision, not a general rule that unregistering is unnecessary. Stop possible callback producers before unloading code.

Three rules that apply to every handler.

  1. Never assume the thread. "NEVER assume thread in which your event handler will run. Your event handler may be called from a background thread even in single-threaded application. For example, a stack overflow exception will force all event handlers to run from a background service thread." Synchronise any access to global entities from a handler.
  2. Options inside a handler go to the exception. Use AExceptionInfo.Options.CustomizedTexts[mtMSDialog_ErrorMsgCaption] := ... for the current MS Classic dialog text, not the same assignment to CurrentEurekaLogOptions. The latter changes future defaults. Do not free AExceptionInfo or its Options: the callback receives them from EurekaLog.
  3. Do not treat ordinary events as leak callbacks. The documented ordinary event registrations are not called during final leak processing. Use the dedicated lifetime/setup rules of chapters 12 and 22 instead; ordinary form-bound state may already be gone.

Handlers give "unlimited ability for customizing EurekaLog, but only within certain points of interest (i.e. events). You can't alter whole processing logic by using only event handlers" - that is section 24.5.

24.4 What the EEvents unit offers

EEvents provides a registration routine per event and a matching UnRegister routine that removes the previously registered handler. Choose by processing stage:

OnExceptionNotify handles the general per-exception decision. OnExceptionAction gives a TEurekaActionType and additional text for a specific processing action; for example, atShowingExceptionInfo identifies the dialog-showing stage. Use the complete event examples in chapter 7 rather than assuming every event has the same signature.

For data that does not need an event, AddCustomDataToBugReport adds static report data and AddCustomWebField adds static web-request data (chapter 6). Request events supply the dynamic counterpart. Review both for private data before publishing reports (chapter 3).

24.5 Replacing EurekaLog's own classes

Subclass only when the change is not available through an option or event. Prefer overriding one virtual method and calling inherited to replacing the whole processing pipeline.

Registration is not the same as selection. Dialogs and send engines have registries; register the class first, then select its name in the appropriate options. RegisterDialogClassFirst and RegisterSenderFirst put a class at the beginning of the lookup list. Singleton extension points such as the report builder instead use a global class variable.

Class methods evolve between EurekaLog releases more often than event contracts. The examples below use the documented overrides; after upgrading, compile and exercise your subclass against the installed version rather than assuming binary or source compatibility.

Replace the MS Classic dialog's icon

Create an icon file named Report.ico and this resource script, ReportIcons.rc:

CUSTOMICON ICON "Report.ico"

Compile it with the resource compiler supplied with the IDE (brcc32 ReportIcons.rc), making ReportIcons.res available to the unit below. This example uses the protected LoadIcon and SetLogoIcon extension points present in EurekaLog 7.15.0.0; verify their availability before using it with another build. The Windows API returns a shared icon. SetLogoIcon creates the dialog-owned bitmap; inherited painting and cleanup keep responsibility for that bitmap.

unit ReportDialogIcon;

interface

implementation

{$R ReportIcons.res}

uses
  Windows, EBase, EModules, EDialog, EDialogWinAPIMSClassic;

type
  TMSClassicDialogCustom = class(TMSClassicDialog)
  protected
    procedure LoadIcon; override;
  end;

procedure TMSClassicDialogCustom.LoadIcon;
var
  CustomIcon: HICON;
  Size: TSize;
begin
  CustomIcon := Windows.LoadIcon(HInstance, 'CUSTOMICON');
  if CustomIcon = 0 then
  begin
    inherited;
    Exit;
  end;
  Size.cx := 32;
  Size.cy := 32;
  SetLogoIcon(CustomIcon, Size);
end;

initialization
  if IsEurekaLogActive then
  begin
    RegisterDialogClass(TMSClassicDialogCustom);
    CurrentEurekaLogOptions.ExceptionDialogType := TMSClassicDialogCustom.ClassName;
  end;
end.

If the named icon cannot be loaded, this descendant uses the inherited icon. The resource file must still exist at build time. Raise a controlled exception after startup and check the dialog; a successful compile does not prove that the configured application selected this class.

Overriding the original class instead of adding one

If the change should apply wherever an option already points at a built-in class, give the descendant the same class name - qualifying the ancestor by its unit, as in TMSClassicDialog = class(EDialogWinAPIMSClassic.TMSClassicDialog) - and register it first with RegisterDialogClassFirst, so that a lookup by name finds your class before the built-in one. No option has to change, so design-time settings keep working. For example, this technique can append ExceptionInfo.BugIDStr to the dialog caption.

The same lookup rule applies to senders: a descendant named TELTrackerMantisSender, with ancestor ESendAPIMantis.TELTrackerMantisSender, can override ComposeTitle: String and be registered with RegisterSenderFirst. Do not casually change that title: Mantis issue identification can depend on it. For extra version or deployment information, prefer configured custom fields and show them in the tracker list (chapter 3). That preserves the original sender's matching behaviour.

A log builder: replacing the report content

A singleton class is installed by assigning its class variable during initialization. The following complete unit demonstrates the documented EurekaLog 6-format compatibility route. Use it only when a legacy consumer requires that format, not to relabel a modern report as an older one for its own sake. It changes the general header and stack formatter together, and renames .el files to .elf; verify the output with the actual legacy consumer.

unit LegacyReportFormat;

interface

implementation

uses
  SysUtils, EConsts, ETypes, EClasses, ECallStack, EException, EEvents, ELogBuilder;

type
  TEurekaLog6Builder = class(TLogBuilder)
  public
    function CreateGeneralText: String; override;
    function CreateCallStackText: String; override;
  end;

function TEurekaLog6Builder.CreateGeneralText: String;
begin
  Result := inherited;
  Result := 'EurekaLog 6' + Copy(Result, Pos(sLineBreak, Result), MaxInt);
end;

function TEurekaLog6Builder.CreateCallStackText: String;
var
  Stack: TEurekaCallStack;
  Formatter: TEurekaBaseStackFormatter;
begin
  Stack := nil;
  try
    if CallStack <> nil then
    begin
      Stack := TEurekaCallStack.Create;
      Stack.Assign(CallStack);
      Stack.Formatter.Assign(Options);
      Stack.Formatter.CaptionHeader :=
        Options.CustomizedExpandedTexts[mtDialog_CallStackHeader] + EHeaderSuffix;
    end;
    Formatter := TEurekaStackFormatterV6.Create;
    try
      Formatter.Assign(Options);
      Formatter.CaptionHeader :=
        Options.CustomizedExpandedTexts[mtDialog_CallStackHeader] + EHeaderSuffix;
      Result := CallStackToString(Stack,
        Options.CustomizedExpandedTexts[mtDialog_CallStackHeader] + EHeaderSuffix,
        Formatter);
    finally
      Formatter.Free;
    end;
  finally
    Stack.Free;
  end;
end;

procedure UseLegacyExtension(const ACustom: Pointer;
  AExceptionInfo: TEurekaExceptionInfo; const AFileType: TEurekaLogFileType;
  var AFileName: String; var ACallNextHandler: Boolean);
begin
  if SameText(ExtractFileExt(AFileName), '.el') then
    AFileName := ChangeFileExt(AFileName, '.elf');
end;

initialization
  LogBuilderClass := TEurekaLog6Builder;
  RegisterEventCustomFileName(nil, UseLegacyExtension);
finalization
  UnRegisterEventCustomFileName(nil, UseLegacyExtension);
end.

This unit is for a process-lifetime reporting executable. Its builder class must remain available while EurekaLog can create reports; do not install it from an unloadable plugin. The stack copy and formatter created by this implementation are owned and freed here; the inherited CallStack and Options are not.

Add a sender that copies reports to a private folder

Use this when delivery means depositing the prepared files in a local folder or UNC share that another trusted process collects. It is a file-copy sender, not a network tracker integration or an automatic resend queue. Chapter 3 covers standard HTTP/mail/tracker setup and offline delivery policy.

Before using it:

  1. Provision a private, writable destination for the actual application/service account. Prefer a UNC path over a mapped drive for services. A local folder proves only a local copy, not that a developer received the report.
  2. In Advanced / Custom/Manual, add Custom_ReportDropPath with a full literal path. For example, the raw .eof representation is Custom_ReportDropPath="C:\\ProgramData\\MySoftware\\ReportDrop". This custom key is read literally by the example; do not put %VARIABLE% placeholders in it.
  3. Keep Save bug report to file enabled and do not delete the original after successful sending while evaluating the integration. Review report and attachment contents and destination permissions as described in chapter 3.
  4. Add this unit to the reporting executable. Its initialization registers the class before selecting it; selection replaces the configured sender list.
unit FolderReportSender;

interface

implementation

uses
  Windows, SysUtils, EBase, ESend, EModules, ETypes;

type
  TFolderReportSender = class(TELUniversalSender)
  public
    function SendMessage: TResponse; override;
  end;

function TFolderReportSender.SendMessage: TResponse;
var
  Root, Destination, Target: String;
  ID: TGUID;
  I: Integer;
begin
  Finalize(Result);
  FillChar(Result, SizeOf(Result), 0);
  Result.SendResult := srUnknownError;
  try
    Root := Options.CustomField['Custom_ReportDropPath'];
    if (Root = '') or not DirectoryExists(Root) then
      raise Exception.Create('Report destination does not exist');
    if AttachedFiles.Count = 0 then
      raise Exception.Create('No report files were supplied');
    if CreateGUID(ID) <> 0 then
      raise Exception.Create('Cannot create a report directory identifier');
    Destination := IncludeTrailingPathDelimiter(Root) + GUIDToString(ID);
    if not CreateDir(Destination) then
      RaiseLastOSError;
    for I := 0 to AttachedFiles.Count - 1 do
    begin
      Target := IncludeTrailingPathDelimiter(Destination) +
        ExtractFileName(AttachedFiles[I]);
      Win32Check(CopyFile(PChar(AttachedFiles[I]), PChar(Target), True));
    end;
    Result.SendResult := srSent;
  except
    on E: EOSError do
    begin
      Result.ErrorCode := Integer(E.ErrorCode);
      Result.IssueIDType := iitWin32;
      Result.ErrorMessage := E.Message;
    end;
    on E: Exception do
      Result.ErrorMessage := E.Message;
  end;
end;

initialization
  if IsEurekaLogActive then
  begin
    RegisterSender(TFolderReportSender);
    CurrentEurekaLogOptions.SenderClasses := TFolderReportSender.ClassName;
  end;
end.

AttachedFiles contains the files supplied by EurekaLog; do not assume there is exactly one. The sender borrows those paths and copies their contents; it does not delete the originals. Each invocation creates a new directory, and CopyFile(..., True) refuses to overwrite an existing filename. If two attachments have the same base filename, or any copy fails, the sender returns failure. A partially copied directory remains for inspection: this example is not an atomic multi-file handoff and must not be used with a collector that treats directory appearance alone as proof of a complete report.

Success is set only after all copies finish. Failure reports a message and, for OS errors, a Win32 error code. The Finalize/FillChar pair follows the documented initialization pattern for the response record's managed fields; do not clear a live response with FillChar alone.

Test with an actual exception and compare all prepared files with the destination, then deny write access or select a nonexistent folder and check that sending fails while the original local report remains. A built-in sender's Test command cannot prove that this custom sender was selected and exercised.

Other documented sender properties include BugID, BugType, BugText, BugReportText, BugCallStack, BugLocation, BugAppVersion, StepsToReproduce and UserEMail. They are populated as part of exception processing and may be empty for a manually initiated send. Work from the sender's Options and supplied exception context, not mutable global defaults.

Registered send classes cannot be unregistered. Keep their code loaded for as long as the registry can use it; this process-lifetime example must not be installed from an unloadable DLL. RegisterSenderFirst also permits a same-name replacement and puts it before the built-in class; ordinary RegisterSender is appropriate for this new, uniquely named sender.

The classes you can replace

The extension points and the units that declare them (as of EurekaLog 7.0.02) are: base dialog class TBaseDialog and RegisterDialogClass (both EDialog); base send engine class TELUniversalSender and RegisterSender (ESend); base debug information provider class TELDebugInfoSource (EClasses) and RegisterDebugInfoSource (EDebugInfo); base call stack class TEurekaBaseStackList, default call stack class EurekaCallStackClass and specific call stack classes TracerMethodsClasses (all ECallStack); base log builder class TBaseLogBuilder and default LogBuilderClass (ELogBuilder); base hung detection thread class TFreezeThread and default FreezeThreadClass (EFreeze).

24.6 What a dialog class has to do

Changing the dialog class can change far more than the window. The class participates in saving, sending and termination decisions, including nonvisual modes such as service and system-log reporting. Do not replace it merely to hide a window; use the appropriate existing dialog mode or the per-exception option first.

The abstract TBaseDialog is declared in EDialog; TWinAPIDialog in EDialogWinAPI is a base for native WinAPI dialogs. ShowModalInternal is the required abstract operation. The documented examples include a nonvisual TNullDialog, simple message-box dialogs and TRTLHandlerDialog. In a null dialog, a successful ShowModalInternal response means the dialog stage completed; it is not evidence that a report was delivered.

Section 24.5 changes only the icon and deliberately preserves the rest of this lifecycle. When choosing an override, keep the following responsibilities in view rather than copying a do-nothing dialog and assuming the inherited work is unimportant.

The default Execute method - the "real entry point" - shows that order: it saves the environment, sets up file names and detects duplicates; unless the dialog is being restarted it takes a screenshot, adds custom data, deletes the old log when DeleteLogAtVersionChange asks for it, clears the reproduce text, pre-saves the report when pre-saving and log saving are both on ("Pre-save to get log in case of crash in dialog") and beeps; then it calls ShowModal, where ShowModalInternal is reached. A dialog that answers srRestart ends the method there and hands the exception over to the RTL; otherwise the report is saved again "to update changed fields (like reproduce text)" when log saving is on and the response allows it, and sent when the response succeeded and sending is enabled - on failure the report is copied or packed only if CopyLogInCaseOfError or SaveCompressedCopyInCaseOfError asks for it, on success the log is deleted when DeleteLogAfterSuccessfulSend does - and the send result is shown to the user, and recorded in the report when the report is being saved. When sending is skipped instead, the OnExceptionAction event of section 24.4 is raised with atSendCancelled. The environment is restored at the end, CheckTermination runs, and only then does the difTerminateApplication custom field decide whether the application terminates; an exception raised anywhere inside becomes an srUnknownError response carrying ERROR_GEN_FAILURE and the exception's message. The advice that comes with it: "You rarely need to alter this method, rather you will override methods which are called by it."

The example's TRTLHandlerDialog is the documented bridge back to the RTL handler: it returns True from GetCallRTLExceptionEvent and sets Result.SendResult := srRestart in ShowModalInternal, a value the vendor's comment glosses as "means 'call RTL handler'". ThreadSafe is a threading contract rather than cosmetics: it "should be False for VCL/CLX/FMX dialogs", while the two dialogs the example documents as callable from any thread, TNullDialog and TMessageBoxDialog, return True.

24.7 Low-level handler variables

Some integration code refers to internal global handler variables. They are listed here so you can recognize them during maintenance, not as a supported recipe for replacing the runtime. The internal variables include:

  • EInject: EventUnhandledExceptionFilter, EventExceptProc, EventExceptClsProc, EventExceptObjProc, EventRaiseExceptionProc, EventGetExceptionStackInfoProc, EventGetStackInfoStringProc, EventCleanUpStackInfoProc, EventExceptionDispatcher, EventIndyThreadHandleException
  • EExceptionHook: _IsUnexpected
  • ELowLevel: _InitDoneErrorHandler, _SafeExecLog, _ConsoleLogger
  • EHook: _GetSymbolAddr
  • EBase: _InternalError, _AllowBypassInternalErrors, _RaiseExpected, _OnPanic
  • EAppType: _CustomMessageBox, _GetMainWnd
  • EMemLeaks and EResLeaks: "Almost all routines in EMemLeaks and EResLeaks units"
  • EThreadsManager: _GetRealAddresses

These internal hooks have no complete public procedural-type or behavior contract. An internal override must respect initialization order, must not replace routines during normal application work, and must save the previous handler and restore it during finalization. Those precautions are necessary, not sufficient to establish a safe implementation for an arbitrary version.

Prefer the options, events and public class extensions above. Do not guess the parameters of an internal handler variable.

Directly modifying EurekaLog source is a different maintenance commitment. It requires an edition with full source; upgrades or reinstalls can overwrite the changes. Keep such a patch outside the installation directory under version control and rebuild every affected artifact: merely building an application without packages does not rebuild runtime packages or IDE tools. Chapter 2 covers build ownership. This is a last resort, not a prerequisite for any example in this chapter.

24.8 Check a customization at its actual boundary

  • Options: verify a new exception after startup uses the selected value; verify an already-raised exception uses its own options copy.
  • Handlers: exercise failures before/after form lifetime if that coverage matters. Do not touch VCL/FMX controls from arbitrary callback threads. Chapter 7 covers ordering and callback contracts.
  • Classes: confirm the registered class is actually selected. Keep its code loaded, and preserve inherited behaviour outside the method you intended to change.
  • Sending: exercise both success and failure with the real prepared files and process account; retain a local report when delivery fails (chapter 3).
  • Upgrades: rebuild and exercise these paths against the installed EurekaLog version. Appendices A–D collect the documented API contracts; the internal variables in section 24.7 remain explicitly outside that public contract.

If the integration fails before your customization runs, use chapter 5 to diagnose EurekaLog's own startup/processing path instead of adding more callbacks blindly.