Skip to content

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 by TTGenericConnection that 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:

TTLogger.Instance.RegisterLogger<TMyLoggerThread>();

Optionally specify the thread pool size:

TTLogger.Instance.RegisterLogger<TMyLoggerThread>(4);

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:

// Access from anywhere
TTLogger.Instance.LogCommand(LConnectionID, LSQL);

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.