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).

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 (inside TTLoggerThread.Execute)
while not FQueue.IsEmpty do
begin
  LItem := FQueue.Dequeue;
  Log(LItem);
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.

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.