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 orTObject.Destroynot hookable; this includes external (non-Delphi) exceptions;emHook- extended memory manager not set, low-level hook set forTObject.Destroy; may be incompatible with exe protectors ("Use Low Level Hooks" enabled);emD2009- extended memory manager not set, hi-level hook set for theExceptionclass; 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.