Using TMSLogger
Add the unit VCL.TMSLogging
or FMX.TMSLogging
to the uses list depending
on the kind of framework you are creating an application for.
Additionally, the units TMSLoggingCore
and TMSLoggingUtils
are only
neccessary, for example when setting the Outputs or Filters property.
The TMSLoggingCore and TMSLoggingUtils are shared between VCL and FMX.
The IDE Plugin that is available after installation can help you add the
missing units in case the application does not compile after adding code
related to TMS Logging. More information can be found in the
IDE Plugin chapter.
The function TMSLogger returns a singleton logger instance that can be used throughout the application. By default the logger is active, but can easily be deactivated by using the following code:
TMSLogger.Active := False;
As already mentioned in the overview topic, the logger makes use of log levels to output values / objects. Each call is a combination of the log level, an optional format and the values / objects to log. The logger outputs to the console by default, and already applies a set of outputs (TMSLogger.Outputs) with a specific format (TMSLogger.OutputFormats). Below is a sample of different log levels with the default logger configuration.
procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
begin
s := 'Hello World !';
TMSLogger.Info(s);
TMSLogger.Error(s);
TMSLogger.Warning(s);
TMSLogger.Trace(s);
TMSLogger.Debug(s);
end;
Result:
The TMSLogger provides several other features and capabilities you can use, as follows.
Formatting
Formatting can be applied in 2 ways: either globally with the TMSLogger.OutputFormats properties or by using one of the log level overloads. The parsing after applying formatting is a custom implementation and supports the Delphi SysUtils Format function. A requirement to successfully execute a log statement with parsing is that the format string contains an opening and closing brace or curly bracket to form a format tag.
The TMSLogger.OutputFormats property contains a set of predefined formats with their format tags. Below is an overview of the default values for each output format:
TimeStampFormat := '[{%dt}]';
ProcessIDFormat := '[{%s}]';
ThreadIDFormat := '[{%s}]';
LogLevelFormat := '[{%s}]';
ValueFormat := '[Value: {%s}]';
NameFormat := '[Name: {%s}]';
TypeFormat := '[Type: {%s}]';
MemoryUsageFormat := '[Memory usage: {%bt} bytes]';
Accompanied with these format properties is the TMSLogger.Outputs property that can determine which information needs to be logged. Below is a sample that combines these 2 properties to create a completely different log output.
procedure TForm1.FormCreate(Sender: TObject);
begin
TMSLogger.Outputs := [loTimeStamp, loLogLevel, loValue];
TMSLogger.OutputFormats.TimeStampFormat := 'The time is {%"hh:nn:ss"dt}, ';
TMSLogger.OutputFormats.LogLevelFormat := 'the loglevel is {%s}, ';
TMSLogger.OutputFormats.ValueFormat := 'and the value is {%s}';
end;
procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
begin
s := 'Hello World !';
TMSLogger.Info(s);
TMSLogger.Error(s);
TMSLogger.Warning(s);
TMSLogger.Trace(s);
TMSLogger.Debug(s);
end;
Result will be as following:
Overriding format for a specific log message
While the above method of formatting already provides a certain flexibility, the value formatting is applied to each log statement. To override this behavior, you can specify a formatting for each value that will be logged. The following sample overrides the output of the previous sample for one of the log statements.
procedure TForm1.FormCreate(Sender: TObject);
begin
TMSLogger.Outputs := [loTimeStamp, loLogLevel, loValue];
TMSLogger.OutputFormats.TimeStampFormat := 'The time is {%"hh:nn:ss"dt}, ';
TMSLogger.OutputFormats.LogLevelFormat := 'the loglevel is {%s}, ';
TMSLogger.OutputFormats.ValueFormat := '{%s}';
end;
procedure TForm1.Button1Click(Sender: TObject);
var
s: string;
fmt: string;
begin
s := 'Hello World !';
fmt := 'The value is {%s}';
TMSLogger.Info(s);
TMSLogger.Error(s);
TMSLogger.WarningFormat(fmt, [s]);
TMSLogger.Trace(s);
TMSLogger.Debug(s);
end;
Result will be as following:
Format string syntax
Formatting is not limited to strings only, below is an overview of supported formatting tags that can be used.
Tag | Expected value |
---|---|
d | Decimal (integer) |
e | Scientific |
f | Fixed |
g | General |
m | Money |
n | Number (floating) |
p | Pointer |
s | String |
u | Unsigned decimal |
x | Hexadecimal |
The general format of each formatting tag is as follows:
{%[Index:][-][Width][.Precision]Tag}
where the square brackets refer to optional parameters, and the : . - characters are literals, the first 2 of which are used to identify two of the optional arguments. Below is an example of formatting a double value with 2 decimals:
procedure TForm1.Button1Click(Sender: TObject);
var
d: Double;
fmt: string;
begin
d := 123.456;
fmt := 'The value is {%.2f}';
TMSLogger.InfoFormat(fmt, [d]);
end;
Result will be as following:
More information can be found at the following page: http://www.delphibasics.co.uk/RTL.asp?Name=Format
As an extension to this, the logger supports a set of additional tags that can be used to format the value. Below is an overview of the format tags that can be used.
Tag | Optional parameters (between double quotes) | Expected value | Output |
---|---|---|---|
a | array | outputs the array to a string that represents the values | |
b | output as number or as "true" or "false" string (ex: {%"-1"b}) | Boolean | outputs the Boolean value to a string represented in numbers or "true" or "false" string |
bin | number of decimals (ex: {%"8"bin} | TStream object | outputs the TStream object as a binary formatted string |
bt | output as data size (B, KB, MB, GB, TB) and optional decimals separated with '#' and formatted with the FormatFloat function (ex: '{%"GB#0.00000"bt}' | Ordinal | outputs the value as a data size formatted value |
dt | datetime format (ex: {%"mm-dd-yyyy"dt) | TDateTime | outputs the TDateTime value to a string |
hex | number of digits (ex: {%"2"hex} | TStream object | outputs the TStream object as a hex formatted string |
pic | object with image data | outputs image data as a string that is sent to the output handlers | |
pichex | object with image data | outputs image data as a hex string that is sent to the output handlers | |
st | TStream object | outputs the TStream object as a string |
A sample using one of the above tags is demonstrated in the following sample:
procedure TForm1.Button1Click(Sender: TObject);
var
fmt: string;
begin
fmt := 'Today is {%"ddd, dd mmm yyyy"dt}';
TMSLogger.InfoFormat(fmt, [Now]);
end;
Output:
OnCustomFormat event
When the above format tags are not sufficient to output the value, the logger exposes an OnCustomFormat event that passes the format tag it has received and the value it needs to parse. The var AResult parameter of this event can be returned based on the custom format tag. Below is an example which demonstrates this.
procedure TForm1.Button1Click(Sender: TObject);
var
fmt: string;
d: Double;
begin
d := 123.456;
fmt := 'This is a custom tag with value {%CT}';
TMSLogger.InfoFormat(fmt, [d]);
end;
procedure TForm1.DoCustomFormat(Sender: TObject; AValue: TValue;
AFormat: string; var AResult: string);
begin
if AFormat.ToUpper.Contains('CT') and AValue.IsType<Double> then
AResult := FloatToStr(AValue.AsType<Double>);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
TMSLogger.OnCustomFormat := DoCustomFormat;
end;
Output:
The above sample simply verifies whether the custom tag is used. The AFormat parameter contains the tag part of the format string that we pass as a parameter to our TMSLogger.InfoFormat call. The AFormat parameter value is {%CT}. The AValue parameter contains the Double value and the AResult var parameter is assigned a simple FloatToStr of the AValue parameter.
Validations
Validations are attributes (based on the Delphi attribute concept) in which a certain comparison is made and a Boolean is returned whether that condition is met. When the result from this comparison is true, the log output will be sent to the output handlers. By default, there are no validations applied, thus the output is always logged.
When monitoring a certain value, be it a string, a Double, an integer,
etc. value, and you only want to output this value when it matches, for
example, a string length, a regular expression, or when the value
exceeds a minimum or maximum, you can create a validation attribute and
add it as a parameter of one of the logger calls. The
TMSLoggingCore
unit already provides a
set of validation classes that are ready to use. Below is an overview of
those validation classes and a short explanation.
TMSLoggerRangeValidation: returns true if the value that needs to be logged exceeds a certain minimum and maximum.
TMSLoggerDateTimeValidation: returns true if the value that needs to be logged exceeds a certain start and end date. The date parameters when creating a TMSLoggerDateTimeValidation attribute are strings and are converted with the DateTimeToStr function.
TMSLoggerStringValidation: returns true if the value doesn't match or contain a certain string.
TMSLoggerStringLengthValidation: returns true if the value doesn't match or exceed a certain string length.
TMSLoggerRegularExpressionValidation: returns true if the value doesn't match a certain regular expression.
When the condition is met, the value is logged. Each validation has a set of parameters to further fine-tune the condition. Two important properties are the ReverseCondition and the Format properties. When the ReverseCondition property is true, the condition is reversed, for example in case of the TMSLoggerRangeValidation, the condition returns true if the value is within a certain minimum and maximum. The format property can be used to apply a certain formatting when the condition is met and the value is logged. Below is a sample that demonstrates the use of a validation attribute and the ReverseCondition and Format properties.
procedure TForm1.Button1Click(Sender: TObject);
var
fmt: string;
i: Integer;
vl: TMSLoggerRangeValidation;
begin
fmt := 'The value is {%g}';
vl := TMSLoggerRangeValidation.Create(110, 130);
vl.Format := fmt;
i := 100;
TMSLogger.Info(i, [], [vl]);
i := 122;
TMSLogger.Info(i, [], [vl]);
i := 140;
TMSLogger.Info(i, [], [vl]);
i := 110;
TMSLogger.Info(i, [], [vl]);
vl.Free;
end;
Output:
Note that with the above sample, only the values 100 and 140 will be logged, because they exceed the minimum and maximum values that were set as parameters of the TMSLoggerRangeValidation attribute. Setting the ReverseCondition to True, will generate a different output as demonstrated in the following sample.
procedure TForm1.Button1Click(Sender: TObject);
var
fmt: string;
i: Integer;
vl: TMSLoggerRangeValidation;
begin
fmt := 'The value is {%g}';
vl := TMSLoggerRangeValidation.Create(110, 130);
vl.Format := fmt;
vl.ReverseCondition := True;
i := 100;
TMSLogger.Info(i, [], [vl]);
i := 122;
TMSLogger.Info(i, [], [vl]);
i := 140;
TMSLogger.Info(i, [], [vl]);
i := 110;
TMSLogger.Info(i, [], [vl]);
vl.Free;
end;
Output:
In this case, the values 122 and 110 will be logged, because they are within the minimum and maximum values and the reverse condition flag is set to True. Note that the Format parameter is set to 'The value is {%g}' which will then output the values with this specific format. The format string can also be directly passed as a parameter to the InfoFormat call as already demonstrated in one of the previous samples.
Custom validations
When the default validation attributes are not sufficient, you can create your own validation by inheriting from the TMSLoggerBaseValidation attribute class and overriding the function Validate(AValue: TValue): Boolean. Below is a sample that demonstrates this.
type
MyValidation = class(TMSLoggerBaseValidation)
protected
function Validate(AValue: TValue): Boolean; override;
end;
implementation
procedure TForm1.Button1Click(Sender: TObject);
var
d: Double;
vl: MyValidation;
begin
vl := MyValidation.Create;
d := 3;
TMSLogger.Debug(d, [], [vl]);
d := 4.5;
TMSLogger.Debug(d, [], [vl]);
vl.Free;
end;
{ MyValidation }
function MyValidation.Validate(AValue: TValue): Boolean;
begin
Result := False;
if AValue.IsType<Double> then
Result := Frac(AValue.AsType<Double>) = 0;
end;
Output:
The sample returns a true if the fractional part of the value is 0 which means that the value 3 is a valid value and will not be logged. The properties to control formatting and reverse conditions are not available by default when inheriting from TMSLoggerBaseValidation. When these properties and functionality need to be available in your custom validation class, you can inherit from TMSLoggerValidation instead.
Logger output statements are not limited to only one validation. Multiple validation attributes can be passed as a parameter. The logger calls have an array of TMSLoggerBaseValidation parameter that can contain multiple values. Only when each validation condition is met, the value will be logged.
Using declarative attributes
As explained earlier, the validation attributes are based on the Delphi attribute concept which means that they can also be added to an object's properties. This way, the logger can simply pass the complete object as a parameter, and the values will be logged when the validation condition is met. Below is a sample that demonstrates this.
type
TMyObject = class
private
FX: string;
FY: Double;
public
[TMSLoggerStringLengthValidation(5)]
property X: string read FX write FX;
[TMSLoggerRangeValidation(10, 20)]
property Y: Double read FY write FY;
end;
implementation
procedure TForm1.Button1Click(Sender: TObject);
var
obj: TMyObject;
begin
obj := TMyObject.Create;
obj.X := 'Hello';
obj.Y := 30;
TMSLogger.Warning(obj);
obj.Free;
end;
Output:
The output of this sample exists of the object itself and the Y property. The X property is not logged, because the 'Hello' string has a length of 5.
The use of validation attributes require the
TMSLoggingCore
unit to be added to the
uses list. If the warning below occurs after compilation, then the
attribute is not found and will not be detected by the logger.
[dcc32 Warning] UDemo.pas(14): W1025 Unsupported language feature: 'custom attribute'
Property Filtering
The logger supports filtering based on the visibility of the field or property that is being logged. The Filters property can be used to determine if public and/or published properties need to be logged with or without attributes. This way, the logger can analyze and only log the field or property that match the filter. The filter is set to allow all public and published properties with attributes by default. Below is a sample that demonstrates the use of the Filter property.
type
TMyObject = class
private
FZ: string;
FX: Double;
FY: Integer;
public
property X: Double read FX write FX;
[TMSLoggerRangeValidation(0, 10)]
property Y: Integer read FY write FY;
published
property Z: string read FZ write FZ;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
obj: TMyObject;
begin
TMSLogger.Filters := [lfPublic, lfPublished];
obj := TMyObject.Create;
obj.X := 3.456;
obj.Y := 10;
obj.Z := 'Hello World';
TMSLogger.Warning(obj);
obj.Free;
end;
Output:
Notice that the output only shows the X and Z properties, because the Filters property is set the only allow public and published properties without attributes. The Y property has an attribute and therefore not logged. The attributed validates a range between 0 and 10 and the value of the Y property is valid, and thus not logged. If the value would exceed the range, the value would be logged but only if the filters are modified to allow public properties with attributes.
Using attributes
Additionally, filtering can be fine-tuned with attributes. The filter attributes add the same kind of filtering as on logger level. The logger parses the attributes and determines if the sub properties / fields of the class or property that has the attribute applied, are valid for logging. There are 2 kinds of filter attributes, TMSLoggerClassFilter and TMSLoggerPropertyFilter. Below is a sample that demonstrates this.
type
[TMSLoggerClassFilter([lfPublic, lfPublicWithAttributes])]
TMyObject = class
private
FZ: string;
FX: Double;
FY: Integer;
public
property X: Double read FX write FX;
[TMSLoggerRangeValidation(0, 10)]
property Y: Integer read FY write FY;
published
property Z: string read FZ write FZ;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
obj: TMyObject;
begin
obj := TMyObject.Create;
obj.X := 3.456;
obj.Y := 12;
obj.Z := 'Hello World';
TMSLogger.Warning(obj);
obj.Free;
end;
Output:
In this case, only the X and Y properties will be logged, because the TMSLoggerClassFilter attribute specifies to allow public properties with and without attributes. The value Y is 12 in this case, exceeds the range validation and is logged. If the value would be set to 10, as in the previous sample, the Y property would also not be logged.
The use of filter attributes requires the
TMSLoggingCore
unit to be added to the
uses list. If the warning below occurs after compilation, then the
attribute is not found and will not be detected by the logger.
[dcc32 Warning] UDemo.pas(14): W1025 Unsupported language feature: 'custom attribute'
Multi-value Logging
The logger has a set of overloads per log level that can be used to format the output, specify validation attributes for conditional logging and specify an array of property names when an object is logged. One of the overloads is designed to quickly log a set of objects in a single call. Below is a sample that demonstrates this.
procedure TForm1.Button1Click(Sender: TObject);
var
a: string;
b: Double;
c: Boolean;
begin
a := 'Hello World';
b := 4.56;
c := True;
TMSLogger.DebugValues([a, b, c]);
end;
Output:
Note that this call does not have a separate format parameter. The formatting is based on the OutputFormats property.
Timing
By default the logger outputs the current date/time, but has the capability of formatting the timestamp output as milliseconds, microseconds or ticks depending on the TimeStampOutputMode property. When using an output mode other than the default value for this property, you always need to combine the logger calls with a StartTimer / StopTimer.
Microseconds
Example:
procedure TForm1.Button1Click(Sender: TObject);
begin
TMSLogger.TimeStampOutputMode := tsomMicroseconds;
TMSLogger.StartTimer;
Sleep(5);
TMSLogger.Debug(1);
Sleep(15);
TMSLogger.Debug(2);
Sleep(5);
TMSLogger.Debug(3);
Sleep(5);
TMSLogger.Debug(4);
TMSLogger.StopTimer;
end;
Output:
Delta Microseconds
Example:
procedure TForm1.Button1Click(Sender: TObject);
begin
TMSLogger.TimeStampOutputMode := tsomMicrosecondsDelta;
TMSLogger.StartTimer;
Sleep(5);
TMSLogger.Debug(1);
Sleep(15);
TMSLogger.Debug(2);
Sleep(5);
TMSLogger.Debug(3);
Sleep(5);
TMSLogger.Debug(4);
TMSLogger.StopTimer;
end;
Output:
Direct timing
Additionally, timing can be done with the StartTimer and StopTimer independent of the TimeStampOutputMode as demonstrated in the following sample:
procedure TForm1.Button1Click(Sender: TObject);
begin
TMSLogger.StartTimer;
Sleep(500);
TMSLogger.StopTimer(True, lsmMilliseconds, 'The elapsed time is {%d} ms');
end;
Output:
Exceptions
The logger supports automatic handling and logging of exceptions. As an addition to the standard log levels, an exception log level is added to the set. By default, exception handling is not enabled. To enable it set ExceptionHandling to true on logger level.
TMSLogger1.ExceptionHandling := True;
Whenever an exception occurs, it will be automatically logged with an Exception log level, as demonstrated in the following division by zero exception.
TMSLogger.RegisterOutputHandlerClass(TTMSLoggerTCPOutputHandler, [Self]);
TMSLogger.ExceptionHandling := True;
TMSLogger.Outputs := AllOutputs;
procedure TForm1.Button1Click(Sender: TObject);
var
a, b, c: Integer;
begin
a := 10;
b := 0;
c := a div b;
end;
Enabling exception handling will create a TApplicationEvents object in VCL and assign the Application.OnException event. If your application needs to handle additional code when an exception occurs the logger exposes an OnHandleException event.
HTML Support
The Browser Output and HTML Output handlers are both using HTML to display the output information. To add additional formatting inside the table or plain HTML formatted viewer, HTML tags can be added to the log statements. Below is a sample that demonstrates this.
procedure TForm1.Button3Click(Sender: TObject);
var
d: Double;
begin
d := 4.5;
TMSLogger.DebugFormat('The <span style=''color:red''><b>value</b></span> is {%f}', [d]);
end;
Even when specifying HTML tags, the other non-HTML formatted output handlers will still display the content correct, as they strip HTML when receiving output information from the logger.
Other TTMSLogger methods
The TMSLogger object has a set of helper methods that can be used to output additional information to the output handlers. Below is an overview of each method and a short explanation.
Name | Description |
---|---|
Clear | Sends a clear instruction to each output handler and removes the log files / log data. |
GetTimer(const AMode: TTMSLoggerTimerMode = lsmMilliseconds; const ALogResult: Boolean = False; const AFormat: string = ''): Int64 | Returns an elapsed value in ticks or milliseconds based on the mode parameters after a timer has been started with StartTimer. |
Indent | Increases the indent for log statements. |
IsTimerRunning | Returns a Boolean if the timer is still running after it was started with the StartTimer call. |
LogCurrentDateTime(const AFormat: string = '') | Logs the current date / time with an optional formatting parameter. |
LogCurrentLocale(const AFormat: string = ''); | Logs the current language identifier. |
LogProcessID(const AFormat: string = ''); | Logs the process id. |
LogScreenShot | Logs a screenshot of the main form of the application. |
LogScreenShot(const AControl: TControl) | Logs a screenshot of the specified control. |
LogSeparator | Logs a separator string. |
LogSystemInformation(const AFormat: string = '') | Logs the system information of the operating system. |
LogThreadID(const AFormat: string = ''); | Logs the thread id. |
LogMemoryUsage(const AFormat: string = ''); | Logs the memory usage of the application. |
LogMemoryUsageDifference(const ALogResult: Boolean = True; const AFormat: string = ''): Cardinal | Logs the difference of the memory usage of the application based on the previous call to LogMemoryUsage. |
StartTimer | Starts a timer, needs to be paired with StopTimer. |
StopTimer(const AMode: TTMSLoggerTimerMode = lsmMilliseconds; const ALogResult: Boolean = False; const AFormat: string = ''): Int64 | Returns an elapsed value in ticks or milliseconds based on the mode parameters and stops the timer, needs to be paired with StartTimer. |
Unindent | Decreases the indent for log statements. |