Table of Contents

Logging

BCL includes a logging abstraction layer for emitting diagnostic messages from your application. The design separates two concerns: writing log messages and configuring where they go. When you write log messages, you use the ILogger interface and do not worry about the output destination. The output is configured separately at application startup.

uses {...}, Bcl.Logging;
var
  Logger: ILogger;
begin
  Logger := LogManager.GetLogger;
  Logger.Error('Something went wrong!');
end;

By default, log messages are discarded (no output is configured). You can quickly enable debug output during development with a single call to RegisterDebugLogger, or plug in the full TMS Logging framework for production use with multiple output targets.

Writing Log Messages

Retrieve an ILogger instance from the LogManager function and call one of its five methods, each corresponding to a severity level:

procedure BasicLogging;
var
  Logger: ILogger;
begin
  Logger := LogManager.GetLogger;
  Logger.Error('This is an error!');
  Logger.Warning('Pay attention to this');
  Logger.Info('Relevant information');
  Logger.Debug('Some debug output');
  Logger.Trace('Tracing information');
end;

All methods accept a TValue parameter, so you can pass not only strings but also integers, doubles, booleans, and other types:

procedure LogDifferentTypes;
var
  Logger: ILogger;
begin
  Logger := LogManager.GetLogger;
  Logger.Info('A string message');
  Logger.Info(42);
  Logger.Info(3.14);
  Logger.Info(True);
end;

The way complex values (such as objects) are rendered depends on the output handler. The built-in debug logger calls TValue.ToString, while TMS Logging output handlers provide richer formatting for objects by listing their properties.

The ILogger Interface

For reference, the complete ILogger interface is:

ILogger = interface
  procedure Error(const AValue: TValue);
  procedure Warning(const AValue: TValue);
  procedure Info(const AValue: TValue);
  procedure Trace(const AValue: TValue);
  procedure Debug(const AValue: TValue);
end;

Naming Loggers

TLogManager.​Get​Logger provides several overloads for retrieving loggers. You can assign a name to identify the source of log messages:

procedure NamedLoggerExample;
var
  Logger: ILogger;
begin
  Logger := LogManager.GetLogger('MyComponent');
  Logger.Info('Component initialized');
end;

Calling GetLogger without parameters creates a logger with an empty name. You can also pass a class reference, which uses the fully qualified class name (for example, MyUnit.TMyService):

  TMyService = class
  private
    FLogger: ILogger;
  public
    constructor Create;
    procedure DoWork;
  end;
constructor TMyService.Create;
begin
  FLogger := LogManager.GetLogger(Self);
end;

procedure TMyService.DoWork;
begin
  FLogger.Info('Starting work');
  // ... perform work ...
  FLogger.Debug('Work completed successfully');
end;

Loggers are cached by name. Subsequent calls with the same name return the same instance.

Naming loggers is useful when you want to route messages from different parts of your application to different outputs, or to filter out messages from specific sources. This is especially relevant when using multiple TMS Logging loggers.

Log Levels

BCL defines five log levels in ascending severity order:

Level Description
Trace Fine-grained diagnostic information, typically only useful during development.
Debug Diagnostic information useful for debugging.
Info General informational messages about application progress.
Warning Potentially harmful situations that deserve attention.
Error Error conditions that should be investigated.

The TLogLevels set type allows filtering which levels are active. For example, you can configure the debug logger to only output errors and warnings:

procedure EnableFilteredDebugLogging;
begin
  RegisterDebugLogger([TLogLevel.Error, TLogLevel.Warning]);
end;

Built-in Debug Logger

The quickest way to see log output during development is to call RegisterDebugLogger:

procedure EnableDebugLogging;
begin
  RegisterDebugLogger;
end;

This enables a built-in logger that outputs messages via OutputDebugString on Windows. Messages are formatted as:

[2026-02-17 14:30:45.123] [INFO] [Relevant information]

You can view the output in the Delphi IDE by opening the Messages window and selecting the Output tab.

To restrict which levels are logged, pass a TLogLevels set:

procedure EnableFilteredDebugLogging;
begin
  RegisterDebugLogger([TLogLevel.Error, TLogLevel.Warning]);
end;

Custom Logger Factories

The logging system is extensible through the ILoggerFactory interface. A factory has a single method that creates an ILogger for a given name:

ILoggerFactory = interface
  function GetLogger(const Name: string): ILogger;
end;

To change where log messages go, assign a new factory to LogManager.Factory:

type
  TMyLoggerFactory = class(TInterfacedObject, ILoggerFactory)
  private
    function GetLogger(const Name: string): ILogger;
  end;

function TMyLoggerFactory.GetLogger(const Name: string): ILogger;
begin
  // Return your custom ILogger implementation here
  Result := nil;
end;

procedure SetupCustomLogging;
begin
  LogManager.Factory := TMyLoggerFactory.Create;
end;

An important design detail: when you change the factory, all existing ILogger references that were previously obtained from LogManager automatically start using the new backend. You do not need to re-retrieve loggers. This is possible because TLogManager wraps each logger in a TWrapperLogger that delegates to the inner logger, and swaps the inner logger when the factory changes.

Integrating with TMS Logging

The built-in debug logger is convenient during development, but for production scenarios you typically need to output log messages to files, TCP/IP listeners, Windows Event Log, or other targets. This is accomplished by integrating with TMS Logging, a full-featured logging framework.

To plug TMS Logging into BCL, add the Bcl.TMSLogging unit and call RegisterTMSLogger:

Using the Global Logger

The simplest approach routes all log messages to the TMS Logging global logger (TMSDefaultLogger):

uses {...}, Bcl.TMSLogging, TMSLoggingCore, TMSLoggingEventLogOutputHandler;
var
  Logger: ILogger;
begin
  RegisterTMSLogger;

  // Configure output handler on the global TMS Logger
  TMSDefaultLogger.RegisterOutputHandlerClass(
    TTMSLoggerEventLogOutputHandler, ['My Application']);

  Logger := LogManager.GetLogger;
  Logger.Info('Application started');
end;

After calling RegisterTMSLogger, all BCL log messages are forwarded to TMSDefaultLogger, regardless of the logger name. You then configure output handlers on TMSDefaultLogger using the TMS Logging API.

Using Multiple Loggers

For more complex scenarios where you want different output handlers for different logger names, call RegisterTMSLogger with a callback:

uses {...}, Bcl.TMSLogging, TMSLoggingCore, TMSLoggingFileOutputHandler;

  RegisterTMSLogger(
    procedure(const Name: string; Logger: TTMSCustomLogger)
    begin
      if Pos('Database', Name) > 0 then
        Logger.RegisterOutputHandlerClass(
          TTMSLoggerFileOutputHandler, ['database.log']);
      if Pos('Http', Name) > 0 then
        Logger.RegisterOutputHandlerClass(
          TTMSLoggerFileOutputHandler, ['http.log']);
    end
  );

This creates a separate TTMSCustomLogger instance for each named logger. The callback receives the logger name and the newly created TMS logger, giving you the opportunity to configure output handlers based on the name. Loggers whose names do not match any condition will silently discard messages.

Note

TMS Logging supports a wide range of output handlers including text files, CSV files, TCP/IP listeners, browser output, Windows Event Log, and more. For full documentation on available output handlers and their configuration, refer to the TMS Logging documentation.

Logging in TMS Sparkle

TMS Sparkle uses this same logging mechanism for its Logging Middleware, which automatically logs HTTP requests and responses processed by Sparkle servers. The middleware writes to BCL loggers, so configuring BCL logging output as described above also controls Sparkle's diagnostic output.