Logging¶
TTLogger provides structured SQL logging for debugging and monitoring. It is defined in Trysil.Logger.pas.
Overview¶
Trysil logs all SQL operations -- transactions, parameters, generated syntax, executed commands, and errors -- through a global TTLogger singleton. Log items carry connection and thread identifiers for multi-threaded correlation.
Log Events¶
Each log item has a TTLoggerEvent type:
| Event | Description |
|---|---|
StartTransaction |
A transaction was started |
Commit |
A transaction was committed |
Rollback |
A transaction was rolled back |
Parameter |
A SQL parameter name and value |
Syntax |
Generated SQL syntax (before execution) |
Command |
Executed SQL command |
Error |
An error occurred during execution |
Log Item Structure¶
TTLoggerItem is a record carrying:
| Field | Type | Description |
|---|---|---|
ID |
TTLoggerItemID |
Correlation identifier |
Event |
TTLoggerEvent |
The event type |
Values |
TArray<String> |
Event-specific string values |
TTLoggerItemID¶
Each TTLoggerItemID contains:
- ConnectionID (
String) -- A UUID generated byTTGenericConnectionthat uniquely identifies the database connection. - ThreadID (
TThreadID) -- The ID of the thread that created the log item.
Together, these allow correlating all SQL operations from a single connection across threads in a multi-threaded server.
Registering a Logger¶
To enable logging, register a custom logger thread class with the global TTLogger.Instance:
Optionally specify the thread pool size:
The default thread pool size is 1. Increasing it distributes log processing across multiple threads using round-robin (TTRoundRobin).
Register once, at startup. A second RegisterLogger adds its threads to the pool; it does not replace the logger already there, so two calls with two classes leave both running, with the log lines divided between them.
Creating a Custom Logger¶
Extend TTLoggerThread and override the abstract methods:
type
TFileLoggerThread = class(TTLoggerThread)
strict protected
procedure LogStartTransaction(const AID: TTLoggerItemID); override;
procedure LogCommit(const AID: TTLoggerItemID); override;
procedure LogRollback(const AID: TTLoggerItemID); override;
procedure LogParameter(
const AID: TTLoggerItemID;
const AName: String;
const AValue: String); override;
procedure LogSyntax(
const AID: TTLoggerItemID;
const ASyntax: String); override;
procedure LogCommand(
const AID: TTLoggerItemID;
const ASyntax: String); override;
procedure LogError(
const AID: TTLoggerItemID;
const AMessage: String); override;
end;
procedure TFileLoggerThread.LogCommand(
const AID: TTLoggerItemID; const ASyntax: String);
begin
WriteLn(Format('[%s][%d] COMMAND: %s', [
AID.ConnectionID, AID.ThreadID, ASyntax]));
end;
// ... implement other methods similarly
TTLoggerQueue¶
TTLoggerThread uses an internal TTLoggerQueue -- a thread-safe queue for TTLoggerItem records:
// Internal consumer pattern (TTLoggerThread.DrainQueue, called by Execute
// and, once the thread has stopped, by the thread that frees the logger)
while not FQueue.IsEmpty do
try
Log(FQueue.Dequeue);
except
// a LogXxx that raises does not stop the drain
end;
The queue uses a critical section for thread safety. The logger thread waits on an event object and wakes up when new items are enqueued.
Execute is not the only consumer. When the logger is freed, Execute stops, and whatever is still queued is written by the thread that frees the logger, once the logger's own thread has stopped - during process finalization, the main thread. Your LogXxx methods can therefore run on either, and at process exit they can run after the unit that declares them has finalized: keep what they write through inside the thread instance, not in a class variable of that unit.
Global Singleton¶
TTLogger is a class-level singleton created in a class constructor and destroyed in a class destructor:
If no logger thread is registered, log calls are silently ignored (the round-robin returns nil).
Log Methods¶
The TTLogger instance provides convenience methods:
TTLogger.Instance.LogStartTransaction(LConnectionID);
TTLogger.Instance.LogCommit(LConnectionID);
TTLogger.Instance.LogRollback(LConnectionID);
TTLogger.Instance.LogParameter(LConnectionID, 'Lastname', 'Smith');
TTLogger.Instance.LogSyntax(LConnectionID, 'SELECT * FROM Persons WHERE ID = :ID');
TTLogger.Instance.LogCommand(LConnectionID, 'SELECT * FROM Persons WHERE ID = 42');
These are called automatically by TTGenericConnection and its subclasses during normal ORM operations. You typically do not need to call them manually unless you are logging custom SQL executed outside the ORM.