Table of Contents

Output Handlers

The logger provides the capability of logging to various other output handlers. An output handler is a class that provides a log output method with a parameter that contains output information. The output information contains the timestamp, the value, the log level and a set of pre-formatted strings (such as the name, type and value, based on the TMSLogger.Outputs property) and then saves the information to a file, or sends it to a browser, TCP/IP client. The output handlers support various formats such as plain text and HTML.

Each output handler except for the default TTMSLoggerConsoleOutputHandler class is available in separate units. Each unit name starts with TMSLogging and then specifies which output handler is implemented. To use an output handler, it needs to be registered first. The logger has a set of RegisterOutputHandler* methods that can be used to register or create an instance of an output handler.

An output handler has an Active property, which is true by default and has a set of constructor overloads. The RegisterOutputHandlerClass method will accept a list of parameters that should match the number of parameters in the constructor of the output handler class you wish to create. When you are not sure on the type of parameters, you can take a look at the create signature, or simply create a separate instance of the output handler. When creating a separate instance, it can be registered using the RegisterOutputHandler or RegisterManagedOutputHandler call instead. Using the latter will tell the logger to destroy the output handler instance when the logger instance itself is destroyed. The former will not destroy the output handler.

Each output handler has a procedure LogOutput(const AOutputInformation: TTMSLoggerOutputInformation); that contains the log information.

Some output handlers will automatically strip HTML/XML tags from the message. Mostly the ones which those tags are not relevant, like console output, file output, etc. If you want to prevent an output handler from stripping such tags, set the StripHtml property to false:

  TextOutputHandler.StripHtml := False;

Following are the output handlers that are currently available.

Console Output

Class: TTMSLoggerConsoleOutputHandler at TMSLoggingCore unit.

The TTMSLoggerConsoleOutputHandler outputs the log information to the console window of the IDE or the log monitor/console of the device (iOS, Android, Mac OSX). Note that this does not output to the regular Windows console. To do that, you should use the Windows Console Output handler.

logging output console

Windows Console Output

Class name: TTMSLoggerWindowsConsoleOutputHandler at TMSLoggingWindowsConsoleOutputHandler unit.

The TTMSLoggerWindowsConsoleOutputHandler outputs log messages to the Microsoft Windows console window. It also changes the output color according to the log level.

logging output windowsconsole

Text File Output

Class name: TTMSLoggerTextOutputHandler at TMSLoggingTextOutputHandler unit.

The TTMSLoggerTextOutputHandler outputs the information as plain text to a file.

Sample output:

logging output textfile 

Event Log Output

Class name: TTMSLoggerEventLogOutputHandler at TMSLoggingEventLogOutputHandler unit.

The TTMSLoggerEventLogOutputHandler will log the output information to the Windows event log. Creating an instance can be done with the following code:

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerEventLogOutputHandler);
Important

The TTMSLoggerEventOutputHandler requires administrator privileges on Windows in order to successfully write an event to the Windows event log. Starting the application without administrator privileges will also log to the event log but will display a message indicating it cannot find the event id linked to the specific event log.

Sample output:

logging output eventlog 

CSV File Output

Class name: TTMSLoggerCSVOutputHandler at TMSLoggingCSVOutputHandler unit.

The TTMSLoggerCSVOutputHandler outputs the information as plain text to a CSV file, separated by a delimiter, which can be configured with a separate property.

Sample output:

logging output csvfile 

TCP Output

Class name: TTMSLoggerTCPOutputHandler at TMSLoggingTCPOutputHandler unit.

The TTMSLoggerTCPOutputHandler is a server that sends the output information to each connected TCP/IP client. The client application can implement its own TCP/IP client read buffer instructions but an instance of TTMSLoggerTCPClient, which already implements this, can also be chosen. The TTMSLoggerTCPClient has an OnReceivedOutputInformation event is triggered whenever output information is received.

Registering a TTMSLoggerTCPOutputHandler is similar to the TTMSLoggerBrowserOutputHandler, but does not have an option to change the HTML formatting viewer.

The sample output screenshot is a TCP Client that implements this technique and serves as a viewer. The executable is available in the installation directory and is supported for Windows and Mac OSX.

Sample output:

logging output tcp

Memo Output

Class name: TTMSLoggerMemoOutputHandler at Vcl.TMSLoggingMemoOutputHandler unit.

The TTMSLoggerMemoOutputHandler outputs log messages to a TMemo VCL control, in a thread-safe way. It's useful to allow the end-user to quickly visualize log information in the VCL application.

The Create constructor requires you to pass the instance of TMemo that will receive the messages.

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerMemoOutputHandler, [Memo1]);
logging output memo

Browser Output

Class name: TTMSLoggerBrowserOutputHandler at TMSLoggingBrowserOutputHandler unit.

The TTMSLoggerBrowserOutputHandler is a server that sends the output information to each connected client browser. To register a TTMSLoggerBrowserOutputHandler, the following code can be used:

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerBrowserOutputHandler, [Self]);

After registering it, navigating to the IP-address of the server, or the localhost with the default port of 8888 will display an empty HTML table. As soon as the logger logs a value, the table will be updated. To change the port number, the parameter list can be modified as demonstrated in the code below.

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerBrowserOutputHandler, [Self, 1234]);

Optionally, the HTML table view can be changed to an HTML plain view by specifying an additional parameter:

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerBrowserOutputHandler, [Self, 1234, TValue.From(ohmPlain)]);

Available constructors:

constructor Create(const AOwner: TComponent); reintroduce; overload; virtual;

constructor Create(const AOwner: TComponent; const APort: Integer); reintroduce; overload; virtual;

constructor Create(const AOwner: TComponent; const APort: Integer; const AMode: TTMSLoggerHTMLOutputHandlerMode); reintroduce; overload; virtual;

constructor Create(const AOwner: TComponent; const APort: Integer; const AMode: TTMSLoggerHTMLOutputHandlerMode; const ATitle: string); reintroduce; overload; virtual;

  • AOwner: A component to serve as the owner for internal components. Usually Self.

  • APort: The TCP port used to create the TCP/IP listener

  • AMode: The format data will be output.

  • ATitle: The HTML page title.

Example:

var
  Handler: TTMSLoggerBrowserOutputHandler;
begin
  Handler := TTMSLoggerBrowserOutputHandler.Create(Self);
  TMSLogger.RegisterManagedOutputHandler(Handler);
  Handler.Title := 'Browser Logging (Custom Title)';

Sample output:

logging output browser

HTML Output

Class name: TTMSLoggerHTMLOutputHandler at TMSLoggingHTMLOutputHandler unit.

The TTMSLoggerHTMLOutputHandler is based on the same output as the TTMSLoggerBrowserOutputHandler but saves the output information to a file instead. The constructor overloads for this class are different than the ones for the TTMSLoggerBrowserOutputHandler. To know exactly which parameters to pass to the TMSLogger.RegisterOutputHandlerClass, you can simply type "TTMSLoggerHTMLOutputHandler.Create(" which will show a list of constructor overloads.

Available constructors:

constructor Create(const AFileName: string); overload; override;

constructor Create(const AFileName: string; ADataName: string); reintroduce; overload; virtual;

constructor Create(const AFileName: string; ADataName: string; AMode: TTMSLoggerHTMLOutputHandlerMode); reintroduce; overload; virtual;

constructor Create(const AFileName: string; ADataName: string; AMode: TTMSLoggerHTMLOutputHandlerMode; const ATitle: string); reintroduce; overload; virtual;
  • AFileName: The name of .html file to be generated.

  • ADataName: The name of .js file to be generated.

  • AMode: The format data will be output.

  • ATitle: The HTML page title.

Examples:

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerHTMLOutputHandler, ['.\demo_table.html', 'demo_table.js', TValue.From(ohmTable), 'Table logging']);
TMSLogger.RegisterOutputHandlerClass(TTMSLoggerHTMLOutputHandler, ['.\demo_plain.html', 'demo_plain.js', TValue.From(ohmPlain), 'Plain logging']);

Datasource Output

Class name: TTMSLoggerDataSourceOutputHandler at TMSLoggingDataSourceOutputHandler unit.

The TTMSLoggerDataSourceOutputHandler is capable of connecting to a datasource via the register method demonstrated in the code below.

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerDataSourceOutputHandler, [DataSource1]);

The result of the registration returns an instance of TTMSLoggerDataSourceOutputHandler which contains a Fields property to setup the field names to connect to via the datasource parameter. By default these fields are already prefilled with property name as a field name.

Aurelius Output

Class name: TTMSLoggerAureliusOutputHandler at TMSLoggingAureliusOutputHandler unit.

The TTMSLoggerAureliusOutputHandler connects via an Aurelius IDBConnectionPool interface to send logging outputs to a dataset connected via TMS Aurelius. The process of setting up a connection is simple as demonstrated in the code below.

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerAureliusOutputHandler, [TValue.From<IDBConnectionPool>(pool)]);

Slack Output

Class name: TTMSLoggerSlackWebhookOutputHandler at TMSLoggingSlackOutputHandler unit.

The TTMSLoggerSlackWebhookOutputHandler outputs log messages to a Slack channel through an incoming webhook. The incoming webhook is a Slack feature which provides you a unique URL that is used to post messages to a specific channel in your workplace.

The Create constructor requires you to pass the incoming webhook URL:

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerSlackWebhookOutputHandler, ['https://hooks.slack.com/services/ABCDEFG/ABCDEFG/hajsdfhkadsfjbXBAHASQW']);

Messages are delivered in the background, see Webhook delivery below.

logging output slack

Discord Output

Class name: TTMSLoggerDiscordWebhookOutputHandler at TMSLoggingDiscordOutputHandler unit.

The TTMSLoggerDiscordWebhookOutputHandler outputs log messages to a Discord channel through an incoming webhook. The incoming webhook is a Discord feature which provides you a unique URL that is used to post messages to a specific channel in your workplace.

The Create constructor requires you to pass the incoming webhook URL:

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerDiscordWebhookOutputHandler, ['https://discord.com/api/webhooks/0000000000/abcdefgh']);

Messages are delivered in the background, see Webhook delivery below.

Webhook delivery

The Slack and Discord output handlers descend from TTMSLoggerAsyncOutputHandler (TMSLoggingAsyncOutputHandler unit), which is also a good starting point for your own handlers targeting a web service or any other slow or unreliable destination.

Logging is never delayed by the destination: the thread that logs only adds the formatted message to an in-memory queue and returns. A background thread delivers the queued messages, grouping several of them in a single request (up to the size the destination accepts: 2000 characters for Discord, 4000 for Slack) and honoring rate limits by waiting the time requested in the HTTP 429 response before sending again. Failed requests are retried with an increasing delay.

If messages are logged faster than they can be delivered and the queue fills up, new messages are discarded instead of slowing the application down (delivery is best effort: keep a file or console output as the reliable record). The loss is not silent: a message stating how many lines were lost is posted to the channel at the position of the gap, and the LostCount property keeps the total.

The behavior can be tuned with the following properties, which can also be set in the configuration file:

Property Default Description
MaxQueueSize 1000 Maximum number of messages waiting for delivery.
BatchDelay 250 Milliseconds to wait after the first message of a burst before sending, so that the burst goes out in few requests.
MaxBatchLength 2000 (Discord), 4000 (Slack) Maximum characters per request. Longer messages are truncated.
MaxRetries 3 Attempts after a failed request before the batch is given up.
RetryDelay 1000 Milliseconds before the first retry, doubled at each retry.
MaxRetryDelay 60000 Upper limit for any wait between requests.
ShutdownTimeout 2000 Milliseconds during which the handler, when destroyed, keeps sending what is still queued. A request already in flight is bounded by RequestTimeout.
RequestTimeout 15000 Milliseconds a single request may take.

Exceptionless Output

Class name: TTMSLoggerExceptionlessOutputHandler at VCL.TMSLoggingExceptionlessOutputHandler (for VCL) or FMX.TMSLoggingExceptionlessOutputHandler (for FMX) or FNC.TMSLoggingExceptionlessOutputHandler (for FNC) unit.

The TTMSLoggerExceptionlessOutputHandler connects via the TAdvExceptionless (VCL) or TTMSFMXCloudExceptionless (FMX) or TTMSFNCCloudExceptionless (FNC) non-visual component. When registering this output handler, the log statements are send to the Exceptionless server which can be monitored through the dashboard service at https://exceptionless.com. This output handler is not pre-installed, and requires the TMS Cloud Pack for VCL and/or the TMS Cloud Pack for FMX and/or the TMS Cloud Pack for FNC installed. To start working with TTMSLoggerExceptionlessOutputHandler add the unit for the framework you are using in your application and add the following to initialize:

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerExceptionlessOutputHandler, [AdvExceptionLess1, 'AProjectID']);

or

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerExceptionlessOutputHandler, [TMSFMXCloudExceptionLess1, 'AProjectID']);

AProjectID is a string that needs to be replaced after the Exeptionless cloud component has retrieved its projects. The output handler requires an already authenticated Exceptionless cloud component in order to function properly.

logging output exceptionless

myCloudData Output

Class name: TTMSLoggerMyCloudDataOutputHandler at VCL.TMSLoggingMyCloudDataOutputHandler or FMX.TMSLoggingMyCloudDataOutputHandler unit.

The TTMSLoggerMyCloudDataOutputHandler connects via the TAdvMyCloudData (VCL) or TTMSFMXCloudMyCloudData (FMX) non-visual component. When registering this outputhandler, the log statements are send to the MyCloudData server. This outputhandler is not pre-installed, and requires the TMS Cloud Pack for VCL and/or the TMS Cloud Pack for FMX installed. To start working with TTMSLoggerMyCloudDataOutputHandler add the unit for the framework you are using in your application and add the following to initialize:

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerMyCloudDataOutputHandler, [AdvMyCloudData1, 'ATableName']);

or

TMSLogger.RegisterOutputHandlerClass(TTMSLoggerMyCloudDataOutputHandler, [TMSFMXCloudMyCloudData1, 'ATableName']);

The ATableName string parameter is optional. By default the output handler will try to create a table called 'MyCloudDataLogging' and add Meta data through the MetaData property. This property can be accessed after registering the TTMSLoggerMyCloudDataOutputHandler which returns an instance. The MetaData properties are initialized with a default value that can be overridden. Below is a sample screenshot after following the above steps. The default values for the table and Meta data names are used in tis sample.

logging output myclouddata

Custom Output

If the default output handlers are not sufficient, the logger supports implementing a custom output handler. The TTMSLoggerBaseOutputHandler and TTMSLoggerCustomFileOutputHandler class provides a set of procedures to easily implement a custom output handler. The TTMSLoggerCustomFileOutputHandler class inherits from TTMSLoggerBaseOutputHandler and adds support for logging to a file. The functionality to concatenate the output information and strip the HTML is located in the TMSLoggingUtils unit, and can be access with TTMSLoggerUtils class functions and procedures. Below is a sample that outputs the log information inside a TMemo based on the TTMSLoggerBaseOutputHandler:

Note

The code below is just for learning purposes. It's simple and not thread-safe to make it easier to understand. If you want log to a TMemo control, use the built-in Memo Output handler.

type
  TMyOutputHandler = class(TTMSLoggerBaseOutputHandler)
  private
    FMemo: TMemo;
  protected
    procedure Clear; override;
    procedure LogOutput(const AOutputInformation: TTMSLoggerOutputInformation); override;
  public
    constructor Create(const AMemo: TMemo); reintroduce; virtual;
    property Memo: TMemo read FMemo write FMemo;
  end;
 
implementation
 
{ TMyOutputHandler }
 
procedure TMyOutputHandler.Clear;
begin
  inherited;
  if Assigned(Memo) then
    Memo.Lines.Clear;
end;
 
constructor TMyOutputHandler.Create(const AMemo: TMemo);
begin
  inherited Create;
  FMemo := AMemo;
end;
 
procedure TMyOutputHandler.LogOutput(const AOutputInformation: TTMSLoggerOutputInformation);
begin
  inherited;
  if Assigned(Memo) then
    Memo.Lines.Add(TTMSLoggerUtils.StripHTML(
      TTMSLoggerUtils.GetConcatenatedLogMessage(AOutputInformation, True)));
end;
 
procedure TForm1.FormCreate(Sender: TObject);
begin
  TMSLogger.RegisterOutputHandlerClass(TMyOutputHandler, [Memo1]);
end;