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