EurekaLog
Menu

The EurekaLog Developer Guide

8. The exception manager: the object behind the processing

When automatic handling is not enough, use the exception manager to process a caught exception, inspect what EurekaLog recorded, or report a failure that did not raise an exception. The manager coordinates the dialog, report and sending; the options still decide which actions run.

Choose the entry point from what you have:

You have… Use…
A raised exception at a catch boundary Handle, or the EBase.HandleException wrapper in chapter 6.
An exception object that may never have been raised StandardEurekaNotify; it supplies a stack when needed.
An error message, without an exception to raise StandardEurekaError.
The currently active exception in this thread ShowLastExceptionData.
A need to inspect or modify recorded information Info; check its result before dereferencing it.

EExceptionManager declares TExceptionManager, its ExceptionManager accessor and TExceptionNotifyMode. EException declares TEurekaExceptionInfo, the record of an exception that the manager returns and callbacks receive.

8.1 Getting hold of the manager

ExceptionManager

function ExceptionManager: TExceptionManager;   // EExceptionManager

The global access method for the exception manager, and the one you will normally use: it returns the instance, whose methods work with exception information objects. The instance is created and deleted automatically, you do not control its lifetime, and you must not create instances of TExceptionManager.

TExceptionManager = class(TCheckedInterfacedObject,
  IInterface, IDLLExceptionManagerV1, IStackTracingSupportV1, IMemoryChecksV1,
  IThreadsSupportV1, IDestroyNotify, IInitOptions);

TExceptionManager.Instance

class function Instance: TExceptionManager;   // EExceptionManager

This class function is equivalent to the ExceptionManager accessor; use either, not a separately constructed manager.

8.2 Info: the information object for an exception

TExceptionManager.Info

public
function Info(const AInfo: TObject; const AAddr: Pointer = nil;
  const AForcedNew: Boolean = False; const ARespectActivation: Boolean = True;
  const ADoNotCreateNew: Boolean = False): TEurekaExceptionInfo; overload;

Returns the information about an exception, or nil if no information exists. The other overloads take a Pointer (with AAddr), a TExceptionData, a TExceptionPointers, a Windows.TExceptionRecord (with AContext) or a TObject with either of those, keeping the remaining parameters and defaults.

AInfo is whatever identifies the exception - object, existing information, exception data or one of the low-level structures. AAddr is the exception address, typically ExceptAddr. AContext is low-level CPU information, used when new information has to be created and explicitly not used for identification.

AForcedNew defaults to False: existing information is looked up, and a new instance is created only when none is found. True always creates one - recommended only when you are certain the exception is new, and what the hooks use. ARespectActivation defaults to True: the call checks whether EurekaLog is enabled, and Info returns nil if it is not.

ADoNotCreateNew appears in every overload but has no documented semantics. Leave it at its published default for the workflows here; its name alone is not a contract for lookup-only behavior.

8.3 Handle: the processing itself

TExceptionManager.Handle

public
function Handle(const AInfo: TObject; const AAddr: Pointer = nil;
  const AHandled: Boolean = False;
  const ARespectActivation: Boolean = True): TResponse; overload;

Processes the exception: showing its dialog, saving a report and sending according to the configured actions. There is no separate Send or Save method on this documented manager interface. Other overloads accept TEurekaExceptionInfo, TExceptionData, Pointer, Windows.TExceptionRecord or TExceptionPointers, with AAddr or AContext where applicable.

AHandled says whether the exception is handled or unhandled; the value is written into the exception information and decides how the exception is processed - handled exceptions are not processed by default. ARespectActivation works as in Info, except that Handle returns srRestart when EurekaLog is not enabled, and with False the exception is processed even then. The result is a TResponse record.

The method is a wrapper: prefer it to a direct call to ProcessException, because it invokes the OnExceptionNotify event, runs the exception filters if any are installed, calculates the statistics for the auto-restart feature, calls ProcessException, and converts any exception raised during that processing into a TResponse value.

The warning that matters: Handle processes only exceptions that were actually raised, that is, that have a real call stack; for anything else it returns srNoExceptionInfo and does nothing.

A newly constructed Exception.Create(...) passed directly to Handle has no recorded raise stack, so the documented result is srNoExceptionInfo. Use StandardEurekaNotify below instead.

Do not assume that manually building a stack with EurekaCallStackClass.Create(EurekaCallStackClass.Caller), assigning it to EI.CallStack, freeing the local stack and then calling Handle(E) is a safe ownership sequence. The CallStack property contract does not specify whether assignment copies the stack or retains a reference, and Info can return nil. Do not assume a non-nil information object or a transfer of ownership; use StandardEurekaNotify to avoid those uncertainties.

8.4 The three shortcuts

Three methods spare you from combining Info and Handle yourself. What they start from differs: the last exception of the thread, an exception object you name, or a message with no exception at all.

TExceptionManager.ShowLastExceptionData

public
function ShowLastExceptionData: TResponse;

Handles the last active exception, if one exists: it is the same as Handle(LastThreadException), and srNoExceptionInfo comes back when there is none. It is what the installed hooks call, for example from TApplication.ShowException, and it is EurekaLog's equivalent of TApplication.HandleException.

TExceptionManager.StandardEurekaNotify

public
function StandardEurekaNotify(const Obj: TObject; const Addr: Pointer = nil;
  const AOptions: TEurekaModuleOptions = nil): TResponse;

A "simple combination of Info and Handle": it takes the exception object, an optional exception address and optional EurekaLog options, and returns the result of the processing - of the options the page singles out csoShowRTLThreads, csoShowWindowsThreads and csoShowELThreads. Its documented purpose is adding EurekaLog to application types it does not support.

Unlike ShowLastExceptionData it lets you name the exception object; unlike Handle it creates a call stack for exceptions that were not raised, which makes it the call for an exception you constructed yourself:

Place this procedure in a Delphi unit's implementation section, with System.SysUtils and EExceptionManager in its uses list (SysUtils on older versions). Use it in a project already configured for EurekaLog. This example constructs, reports and frees its own exception; it does not raise it and does not describe ownership of an exception borrowed from an except block.

procedure ReportDetectedFailure;
var
  E: Exception;
begin
  E := Exception.Create('The operation cannot continue');
  try
    ExceptionManager.StandardEurekaNotify(E);
  finally
    E.Free;
  end;
end;

The captured stack is a reporting-time stack, not evidence of a throw that never occurred. The configured dialog, save and send actions still determine the visible result.

TExceptionManager.StandardEurekaError

public
function StandardEurekaError(const Error: String;
  const AOptions: TEurekaModuleOptions = nil): TResponse;

Invokes EurekaLog without raising an exception: Error becomes the message shown in the dialogs and written into the report, and the options play the same role as above. Internally it creates an EEurekaLogGeneralError (EBase) and passes it to Handle, so filters and event handlers can still alter the processing and your code can tell this "exception" apart; the claimed advantage is that no exception is raised at all, even though the object is created.

The following is a statement for a live application's procedure, with EExceptionManager in scope and EurekaLog configured. Use it when the message is all the information you need to supply:

ExceptionManager.StandardEurekaError('File does not exist, unable to continue');

8.5 Notify: for the hooks, not for you

TExceptionManager.Notify

protected
procedure Notify(const AInfo: TEurekaExceptionInfo;
  const ARespectActivation: Boolean = True;
  const AExternalThread: Boolean = False); overload;

The further overloads take a TExceptionData, a Pointer (with AAddr), a TObject (with AAddr), a Windows.TExceptionRecord (with AContext) or a TExceptionPointers; all take ARespectActivation, and none takes AExternalThread.

Do not call this method directly. The hooks use it to retrieve or create exception information, update counters and the last exception, build exception chains, remove obsolete information blocks and invoke OnRaise. It is a procedure and returns no value.

8.6 What the manager counts and remembers

TExceptionManager.LastThreadException

Information about the most recently raised exception in the current thread. It never returns information about other threads, reading it is thread-safe, and for an arbitrary exception the page points at Info instead. It is EurekaLog's equivalent of the RTL's ExceptObject.

TExceptionManager.ClearThreadExceptions

public
procedure ClearThreadExceptions; overload;

Clears the last occurred exception in the current thread: it unregisters it and deletes the information about it. Three consequences are listed - LastThreadException returns nil; ShowLastExceptionData does nothing and returns srNoExceptionInfo; and the information object of that exception is destroyed, so a reference you hold to it is no longer safe to use. Thread-safe, current thread only.

TExceptionManager.ExceptionCount and UnhandledExceptionCount

Two global counters, both >= 0, always increasing and never decreasing. ExceptionCount counts all exceptions since the application started - it grows by one for every exception the manager finds, "regardless of their kind (handled or not)" - and its value is saved as TEurekaExceptionInfo.ExceptionNumber. UnhandledExceptionCount counts the exceptions the manager processed, its value goes into TEurekaExceptionInfo.UnhandledExceptionNumber and into the report as "Exception/Count", and it is always less than or equal to the first.

Its tip: if the application terminates or restarts after the very first unhandled exception, the value is 0 the whole time - 1 during the processing, but you will never see a 2. Neither counter represents the number of active exceptions.

TExceptionManager.Mode

The working mode of the exception manager: "state of additional hooks". It says which kind of additional hooks were installed, the ones required for the "Catch handled exceptions" and "Handle safecall exceptions" options to work - not the state of the primary hooks.

8.7 TExceptionNotifyMode: how the manager is hooked in

The type declared by EExceptionManager specifies the "work mode of exception manager" - the value you find in Mode. The reference documents five identifiers, each a statement about what was possible to hook:

  • emUnknown - work mode is not set; the exception manager was not initialized yet;
  • emNull - no additional hooks and minimal functionality: extended memory manager not set, Delphi/C++ Builder 2007 or earlier, "Use Low Level Hooks" disabled or TObject.Destroy not hookable; this includes external (non-Delphi) exceptions;
  • emHook - extended memory manager not set, low-level hook set for TObject.Destroy; may be incompatible with exe protectors ("Use Low Level Hooks" enabled);
  • emD2009 - extended memory manager not set, hi-level hook set for the Exception class; Delphi 2009 and later, C++ Builder 2009 and later with the classic compiler, not with the LLVM compiler;
  • emFull - extended memory manager was set; full features.

8.8 The exception information object

TEurekaExceptionInfo holds what was recorded about one exception and what EurekaLog will use while processing it. You get one from ExceptionManager.Info, from ExceptionManager.LastThreadException, or as a parameter of the event handlers EurekaLog calls.

TEurekaExceptionInfo.ToString

public
function ToString: String; override;

Returns the exception message and a compact call stack, useful for a quick human-readable preview. For a full report, use BuildBugReport from ELogBuilder (section 6.3); ToString is not a substitute for the report-building path.

8.9 What the exception was

ExceptionObject

The pointer to the original exception object - the object "type-casted as pointer". The warning is why the entry comes first: an exception may come from an external executable module, and then the object may not be a Delphi object at all; do not cast it to Exception or TObject unless you are completely sure, and use Address, ExceptionClass and ExceptionMessage instead. The following is a function for a unit that imports System.SysUtils and EException. It returns a borrowed native exception, or nil; it neither acquires ownership nor extends the lifetime of either input or result:

function NativeException(AExceptionInfo: TEurekaExceptionInfo): Exception;
var
  EObj: TObject;
begin
  Result := nil;
  if AExceptionInfo = nil then
    Exit;
  if (AExceptionInfo.ExceptionObject = nil) or
     not AExceptionInfo.ExceptionNative then
    Exit;
  EObj := TObject(AExceptionInfo.ExceptionObject);
  if EObj is Exception then
    Result := Exception(EObj);
end;

ExceptionClass

The class name of the original exception object, if there is one; the examples are 'Exception', 'EAccessViolation' and 'EStreamError'. It is recorded text for the report, available even when the object it names is not usable.

ExceptionMessage

The human-readable error message from the exception object, if present; the examples include 'Error Message' and 'Stream read error'. The note has consequences: altering this property totally overwrites the message in the dialogs and in the reports - if only the dialog should say something else, use AdditionalInfo.

AdditionalInfo

Arbitrary user-supplied data for the exception; EurekaLog does not use the value. The note gives it its other purpose: it can override the exception message for dialogs, under the UseExceptionComments option.

Address and ExceptionLocation

Address is the exception address: a pointer to the code, to the CPU instruction that raised the exception. It is the top item of the call stack and appears in the report on the "CPU" page as the "EXP" field. The value is adjusted for software exceptions, so it points at the "call raise" instruction rather than inside the RTL. ExceptionLocation is the same as text, with the same note.

CallStack, Time, ExceptionCode and LastSystemErrorCode

CallStack is the call stack for the exception, which per the page includes the address of the exception as its first item - the object you assign to when handing EurekaLog an exception that was never raised (see Handle). Time is the date and time of the exception, in local time. ExceptionCode is the low-level error code ($C0000005 for an access violation, $0EEDFADE for Delphi exceptions), which can also be returned from a thread function or passed to TerminateThread/TerminateProcess. LastSystemErrorCode is the value GetLastError had when the exception was thrown, saved by EurekaLog at that moment.

ExceptionNumber and UnhandledExceptionNumber

ExceptionNumber is the global exception counter's value at the moment this exception was raised - updated when the exception is raised, whether it is later handled or processed does not matter - and corresponds to TExceptionManager.ExceptionCount. UnhandledExceptionNumber corresponds to UnhandledExceptionCount and is the opposite: it is updated when EurekaLog handles the exception, and until then it is not set.

8.10 BugID: the reference you can quote

BugID

A 4-byte unsigned integer (LongWord) used as a grouping key. EurekaLog treats equal BugIDs as duplicates for features such as "Do not save duplicate exceptions", and the Viewer uses that grouping during import. Equality does not prove that two reports have the same root cause.

The hi-word is EurekaLog's CRC16 of BugIDSource: application/module identification, exception class and two call-stack lines. The input uses human-readable unit, class and routine names, not the exception message or exact address. An address change from rebuilding alone therefore need not change the ID; a change to the identifying names or other inputs can. Different underlying failures can share those inputs, and a hash can collide.

The lo-word is reserved for your customization. BugIDSource exposes the source string; BugIDStr exposes a hexadecimal form suitable for logs and messages. Use the key to collect related reports, then compare their stacks, versions and circumstances (chapters 20 and 4).

CustomID

The low-word of BugID, exposed as the custom part you can set. Changing it changes the complete BugID and separates exceptions that EurekaLog would otherwise group together. Prefer this to replacing the entire key in OnCustomBugID; use a stable distinction that represents a meaningful failure category rather than a changing timestamp or an individual user's identity.

8.11 The flags: what kind of exception this is

A set of flags says how the exception came about. They describe the processing as much as the exception, so they matter for any handler that changes the dialog.

Handled and Processed

Handled is True when your code caught the exception and False when it did not and EurekaLog will process it; it starts False and may become True when the handling code runs. Handled exceptions are not processed by default - the "catch handled exceptions" option changes that - and AHandled sets the flag when you hand an exception over yourself.

Processed is True once EurekaLog has processed the exception: every exception starts unprocessed and becomes processed when EurekaLog handles it, through ExceptionManager.Handle and ProcessException. A processed exception no longer invokes EurekaLog and cannot be handled again, and setting Processed to True excludes it from the processing.

Expected, ExpectedContext, ExpectedURL

Expected is True for an expected exception - the page's term for one that does not represent a bug in your code, and any exception can be marked as expected through various methods. The mark buys a different processing: EurekaLog disables the bug report, the send, the restart and the display of technical details such as the call stack, so an expected exception is handled as a plain error dialog.

ExpectedContext is a help context number, initially empty, to be set to a valid topic id of your help file: setting it makes the exception "known" (expected) and shows the Help button, opening your help file at that topic; RaiseExpected sets it for you. ExpectedURL is an arbitrary URL describing the exception, with the same effect, and is displayed by the console dialog. Altering either property alters how EurekaLog handles the exception.

The remaining flags

Flag Documented meaning
Secondary Exception raised while another exception was being handled; EurekaLog can be configured to behave differently for these.
ThreadException Exception raised inside a background thread and taken over by the thread wrapper to be re-raised later in the main thread (saved into FatalException); never handled inside the thread, and always True when SynchronizeException is.
SynchronizeException Exception inside a TThread.Synchronize method, handled by Synchronize itself and re-raised in another thread.
SafeCallExpt Exception inside a safecall method, handled by the called object's SafeCallException.
InitializationException Exception inside the initialization or finalization block of a unit; always unhandled.
ExceptionExternal A handle rather than a flag: the handle of the external module (DLL) when the exception object was created there, 0 otherwise; always 0 with a single module or run-time packages.
ExceptionNative True when the object in ExceptionObject is compatible with the current module's RTL. ExceptionObject may be nil even then - for instance for a deleted inner exception.

8.12 Threads and chains

ThreadID and ExceptionThreadID

ThreadID identifies the thread that raised or re-raised the exception; ExceptionThreadID the thread that originally raised it, and it "never changes once set". Hence the first warning: an exception taken from a background thread and re-raised in the main thread reports the main thread in ThreadID. The second is a case that would look like a bug: a stack overflow exception carries the thread that raised it, while GetCurrentThreadID returns a different id, because those exceptions are handled in a standalone service background thread. The tip sums up the normal case: "in most cases, but not always: .ThreadID = .ExceptionThreadID = GetCurrentThreadID".

InnerException and BaseException

InnerException is the exception that triggered the current one, or nil when none exists. Unlike the RTL's inner-exception link, EurekaLog records this chain itself. BaseException walks to the innermost exception. Its empty-chain behavior is not consistently documented: the value description says nil, while the remarks say the current exception. Check for nil before use; do not build a caller contract around either empty-chain result.

8.13 The module, the stack and the low-level records

Module, ModuleName and ModuleVersion

Module is the HINST/HMODULE of the executable module containing the exception address. It can be 0 for dynamically generated code, and the module may have been unloaded after the exception, so the handle may no longer be valid when you read it. ModuleName (full file name) and ModuleVersion are cached and usable at any time; both can be empty strings for dynamically generated code.

StackPoint, StackTop and StackDump

StackPoint is the current position of the CPU stack for the exception thread - usually ESP/EBP. It is what the stack dump is taken from, and it appears in the report on the "CPU" page as the "STK" value. StackTop is the other end, the highest available address of that stack; both carry the "not recommended" tip. StackDump is raw memory copied from the stack at StackPoint, of SizeOf(Pointer) × EStackDumpDepth (EConsts) - 64 bytes on x86-32 and 128 on x86-64, no padding.

Context, ExceptionRecord, Info and Reader

Context is the low-level CPU information read from the hardware, ExceptionRecord the low-level system information read from the system. The pages call both "system data type (non-Delphi)" - low-level structures rather than Delphi exception objects, specific to the CPU architecture and to the operating system - and both carry the same tip: not recommended, use the properties that expose what was extracted from them. Info carries an even stronger version of that tip. Reader is the object that did the extracting, a descendant of TExceptionReader; readers exist for Delphi exceptions, C++ Builder exceptions and hardware exceptions, more can be added, and the property is likewise marked as not recommended: read the properties, not the reader.

8.14 The GUI at the moment of the exception

Six properties describe the interface as it was when the exception was raised. They all sit under the same remark - they "contain information about GUI elements when exception was raised" - and the same caveat.

Property Documented value
ActiveForm Window handle of the active window. Can be 0.
ActiveFormClass Window class name, such as "TForm1".
ActiveFormText Arbitrary text from the active window.
ActiveControl Window handle of the active control inside the active window. Can be 0.
ActiveControlClass Window class name, such as "EDIT".
ActiveControlText Arbitrary text from the active control.

For the two handles the page adds the same tip: the handle may no longer be valid when you read it, because the window or control may have been deleted right after the exception was raised - the class names and the texts carry no such tip. Cursor and CursorPos complete the group: the mouse cursor shape, and the mouse position in absolute desktop coordinates, at the moment of the exception.

8.15 What you fill in

ReproduceText

The text for the steps to reproduce, as entered by the user: it is initially empty, and it is set by the MS Classic or EurekaLog dialogs when the user enters something in the "steps to reproduce" field.

Options

The EurekaLog options for this exception, as a TEurekaModuleOptions object. Each exception gets its own TEurekaExceptionInfo and therefore its own options; they are isolated - not depending on CurrentEurekaLogOptions or on other exceptions - so a change here is visible only for this exception. Initially they equal CurrentEurekaLogOptions and may change during the processing, and the dialog, the sender and the log builder share this same instance, so a change in an event handler reaches all of them.

Handler

Which handler is processing the exception: one of the atXYZ constants, such as atVCL, atThread or atDataSnap, with atGeneric as the default. In an event handler you can use it to change the dialog from MS Classic to none when the exception was caught by atThread or atDataSnap, as opposed to atVCL.