Difference between revisions of "LMD LogTools tutorial"
(→Creating custom sessions) |
|||
Line 14: | Line 14: | ||
In this section we will write traditional in programmer's community example - 'Hello, World!' sample program. Firstly, we create new Delphi Console Application (File->New->Other...->Delphi Projects->Console Application). Then replace content of new project by below code: | In this section we will write traditional in programmer's community example - 'Hello, World!' sample program. Firstly, we create new Delphi Console Application (File->New->Other...->Delphi Projects->Console Application). Then replace content of new project by below code: | ||
− | < | + | <syntaxhighlight lang="delphi"> |
program HelloAnyLoggerWorld; | program HelloAnyLoggerWorld; | ||
Line 31: | Line 31: | ||
end; | end; | ||
end. | end. | ||
− | </ | + | </syntaxhighlight> |
Second, we need to run AnyLogger console.After that compile and run HelloAnyLoggerWorld project. If everything is installed correctly you should see in AnyLogger console 'First message'='Hello, World!' line. | Second, we need to run AnyLogger console.After that compile and run HelloAnyLoggerWorld project. If everything is installed correctly you should see in AnyLogger console 'First message'='Hello, World!' line. | ||
Line 42: | Line 42: | ||
To start use LogTools in your application you should add LMDLogMessage.pas unit to uses section of your unit, then you'll be able to use LMDLog instance of TLMDLogSession class. | To start use LogTools in your application you should add LMDLogMessage.pas unit to uses section of your unit, then you'll be able to use LMDLog instance of TLMDLogSession class. | ||
− | < | + | <syntaxhighlight lang="delphi"> |
uses | uses | ||
..., LMDLogMessage, ...; | ..., LMDLogMessage, ...; | ||
Line 49: | Line 49: | ||
LMDLog.SendString('Test', 'Hello, World!'); | LMDLog.SendString('Test', 'Hello, World!'); | ||
... | ... | ||
− | </ | + | </syntaxhighlight> |
<br> | <br> | ||
Line 55: | Line 55: | ||
TLMDLogSession class has methods to send some info into main log - TLMDLogSession.Send*, to watch data in Watch area - TLMDLogSession.Watch* (main different with Send* function that Watch* has just changed value of watched variables, but not add new record) | TLMDLogSession class has methods to send some info into main log - TLMDLogSession.Send*, to watch data in Watch area - TLMDLogSession.Watch* (main different with Send* function that Watch* has just changed value of watched variables, but not add new record) | ||
− | < | + | <syntaxhighlight lang="delphi"> |
uses | uses | ||
..., LMDLogMessage, ...; | ..., LMDLogMessage, ...; | ||
Line 63: | Line 63: | ||
LMDLog.WatchInteger('i', i); | LMDLog.WatchInteger('i', i); | ||
... | ... | ||
− | </ | + | </syntaxhighlight> |
To logging flow of application, below methods available: TLMDLogSession.Enter*, TLMDLogSession.Leave*. Sample: | To logging flow of application, below methods available: TLMDLogSession.Enter*, TLMDLogSession.Leave*. Sample: | ||
− | < | + | <syntaxhighlight lang="delphi"> |
uses | uses | ||
..., LMDLogMessage, ...; | ..., LMDLogMessage, ...; | ||
Line 83: | Line 83: | ||
end; | end; | ||
... | ... | ||
− | </ | + | </syntaxhighlight> |
and methods that help to trace of application | and methods that help to trace of application | ||
− | < | + | <syntaxhighlight lang="delphi"> |
uses | uses | ||
..., LMDLogMessage, ...; | ..., LMDLogMessage, ...; | ||
Line 103: | Line 103: | ||
end; | end; | ||
... | ... | ||
− | </ | + | </syntaxhighlight> |
<br> To control log storage TLMDLogSession has next methods - TLMDLogSession.ClearLog, TLMDLogSession.ClearWatch and TLMDLogSession.ClearCallStack | <br> To control log storage TLMDLogSession has next methods - TLMDLogSession.ClearLog, TLMDLogSession.ClearWatch and TLMDLogSession.ClearCallStack | ||
Line 109: | Line 109: | ||
<br> TLMDLogSession allow you to control level of you logging messages. It has two properties for it - TLMDLogSession.AllowLogLevel - set minimum log level for messages, TLMDLogSession.DefaultLogLevel - set default log level for messages where level is not set as parameter. | <br> TLMDLogSession allow you to control level of you logging messages. It has two properties for it - TLMDLogSession.AllowLogLevel - set minimum log level for messages, TLMDLogSession.DefaultLogLevel - set default log level for messages where level is not set as parameter. | ||
− | < | + | <syntaxhighlight lang="delphi"> |
uses | uses | ||
..., LMDLogMessage, ...; | ..., LMDLogMessage, ...; | ||
Line 134: | Line 134: | ||
end; | end; | ||
... | ... | ||
− | </ | + | </syntaxhighlight> Logging for 'a' and 'i' will be turned off when DEBUG undefined, but 'Error' will be sent to log in any case. |
== Creating custom sessions == | == Creating custom sessions == | ||
Line 140: | Line 140: | ||
Custom sessions can be creating by using LMDLogManager.AddSession() method. Via it custom transport, name etc. can be specified. example: | Custom sessions can be creating by using LMDLogManager.AddSession() method. Via it custom transport, name etc. can be specified. example: | ||
− | < | + | <syntaxhighlight lang="delphi"> |
MyLog := LMDLogManage.AddSession('SessionName|lmd_db_session|lmd_wmcopydata_log|WindowClass=TLMDLogHelperWindow;WindowName=LMDLogHelper'); | MyLog := LMDLogManage.AddSession('SessionName|lmd_db_session|lmd_wmcopydata_log|WindowClass=TLMDLogHelperWindow;WindowName=LMDLogHelper'); | ||
− | </ | + | </syntaxhighlight> |
Then you can use it: | Then you can use it: | ||
− | < | + | <syntaxhighlight lang="delphi"> |
MyLog.SendString('name', 'value'); | MyLog.SendString('name', 'value'); | ||
− | </ | + | </syntaxhighlight> |
or | or | ||
− | < | + | <syntaxhighlight lang="delphi"> |
LMDLogManager.Sessions['SessionName'].SendString('name', 'value'); | LMDLogManager.Sessions['SessionName'].SendString('name', 'value'); | ||
− | </ | + | </syntaxhighlight> |
Aboyut this string - 'SessionName|lmd_db_session|lmd_wmcopydata_log|WindowClass=TLMDLogHelperWindow;WindowName=LMDLogHelper' | Aboyut this string - 'SessionName|lmd_db_session|lmd_wmcopydata_log|WindowClass=TLMDLogHelperWindow;WindowName=LMDLogHelper' | ||
Line 177: | Line 177: | ||
To create such plugin you will need LMD Plugins package and uCreatePlugin.pas unit.<br> | To create such plugin you will need LMD Plugins package and uCreatePlugin.pas unit.<br> | ||
− | < | + | <syntaxhighlight lang="delphi"> |
unit uCreatePlugin; | unit uCreatePlugin; | ||
Line 241: | Line 241: | ||
end. | end. | ||
− | </ | + | </syntaxhighlight> |
uCreatePlugin.pas create TLMDPlugin instance and init with three plugin method "GetSubTypesCount" - returns count of lmdlog message subtypes that will showed by this plugins. Next method is "GetSubTypeName" - returns name of subtype by it's index (0..GetSubTypesCount - 1). "GetSubType" method returns GUID that identificate subtype. And the last method is "GetSubTypeIconIndex" - it returns iconindex to show icon in AnyLogger console by subtype GUID.<br> | uCreatePlugin.pas create TLMDPlugin instance and init with three plugin method "GetSubTypesCount" - returns count of lmdlog message subtypes that will showed by this plugins. Next method is "GetSubTypeName" - returns name of subtype by it's index (0..GetSubTypesCount - 1). "GetSubType" method returns GUID that identificate subtype. And the last method is "GetSubTypeIconIndex" - it returns iconindex to show icon in AnyLogger console by subtype GUID.<br> | ||
Line 247: | Line 247: | ||
So, uCreatePlugin.pas do above basic steps for you. Next, you need to create form with interface ILMDFormWithData, which described in LMDPluginManager unit from [[LMD Plugins]] package. | So, uCreatePlugin.pas do above basic steps for you. Next, you need to create form with interface ILMDFormWithData, which described in LMDPluginManager unit from [[LMD Plugins]] package. | ||
− | < | + | <syntaxhighlight lang="delphi"> |
{ **************************** ILMDFormWithData **************************** } | { **************************** ILMDFormWithData **************************** } | ||
ILMDFormWithData = interface(IInterface) | ILMDFormWithData = interface(IInterface) | ||
Line 279: | Line 279: | ||
end; | end; | ||
... | ... | ||
− | </ | + | </syntaxhighlight> |
Next step is creating child class for TLMDActionExecutor from uCreatePlugin.pas and override handlers of plugin methods. Example:<br/> | Next step is creating child class for TLMDActionExecutor from uCreatePlugin.pas and override handlers of plugin methods. Example:<br/> | ||
− | < | + | <syntaxhighlight lang="delphi"> |
unit intfTextView; | unit intfTextView; | ||
Line 331: | Line 331: | ||
end. | end. | ||
− | </ | + | </syntaxhighlight> |
And final step is create Dll project with export function GetLMDPlugin (see [[LMD Plguins]] for details). <br> | And final step is create Dll project with export function GetLMDPlugin (see [[LMD Plguins]] for details). <br> | ||
− | < | + | <syntaxhighlight lang="delphi"> |
library TextView; | library TextView; | ||
Line 384: | Line 384: | ||
DllProc := @DllMain; | DllProc := @DllMain; | ||
end. | end. | ||
− | </ | + | </syntaxhighlight> |
<br> | <br> | ||
Compile it and copy result dll file to Viewers folder in AnyLogger console installation path. | Compile it and copy result dll file to Viewers folder in AnyLogger console installation path. |
Revision as of 13:31, 18 August 2017
Contents
LogTools package
Introduction
Any programmer who is capable of writing complex applications is aware of the fact that some parts of code cannot be debugged with an interactive debugger (e.g. system service, threaded code, painting routines etc) or that situations exist, where an interactive debugger is unavailable. Common solution for for such cases is information logging. There many ways of logging - to files, to terminal, to system dialog boxes etc. To make life more simple we implemented a powerful logging system, that allows a developer to concentrate on main application logic instead of writing and debugging his own routines.
Class Structure
AnyLogger offer a flexible and extensible set of classes to log useful information. There are four main items: Message, Session, Transport and Manager. Message is container for information that should be logged. Session is class that wrap different information into Message and send it over Transport layer. Transport layer is responsible for physical delivery of Message. And at last but not least - the Manager allows user to create root (default) and user sessions, load/save user sessions and transport parameters. Secondary points are Router and Server (message Collector).
See also the following class diagram:
First Example
In this section we will write traditional in programmer's community example - 'Hello, World!' sample program. Firstly, we create new Delphi Console Application (File->New->Other...->Delphi Projects->Console Application). Then replace content of new project by below code:
program HelloAnyLoggerWorld;
{$APPTYPE CONSOLE}
uses
SysUtils,
LMDLogMessage;
begin
try
LMDLog.SendString(‘First message’, ‘Hello, World!’);
except
on E:Exception do
LMDLog.SendException(E);
end;
end.
Second, we need to run AnyLogger console.After that compile and run HelloAnyLoggerWorld project. If everything is installed correctly you should see in AnyLogger console 'First message'='Hello, World!' line.
Now, let's take a closer look at what we did. First of all - we added LMDLogMessage unit in uses list. This unit contains declaration of all base needed types, classes and global variable LMDLog that contains object reference for root session (see. Sessions chapter). Secondly, we uses LMDLog global variable to call method of session - SendString which sends string using session transport (by default transport is WM_COPYDATA message which handled by AnyLogger console)
Basic Usage
Main purpose of this tool package is logging and monitoring actions of application. It should be used when you can't run regular debugger or when it useless - multi-thread application, system services, applications on client site etc.
To start use LogTools in your application you should add LMDLogMessage.pas unit to uses section of your unit, then you'll be able to use LMDLog instance of TLMDLogSession class.
uses
..., LMDLogMessage, ...;
...
LMDLog.SendString('Test', 'Hello, World!');
...
TLMDLogSession class has methods to send some info into main log - TLMDLogSession.Send*, to watch data in Watch area - TLMDLogSession.Watch* (main different with Send* function that Watch* has just changed value of watched variables, but not add new record)
uses
..., LMDLogMessage, ...;
...
while i < 200 do
LMDLog.WatchInteger('i', i);
...
To logging flow of application, below methods available: TLMDLogSession.Enter*, TLMDLogSession.Leave*. Sample:
uses
..., LMDLogMessage, ...;
...
function TestFunction: HResult;
begin
LMDLog.EnterMethod('TestFunction');
try
...
finally
LMDLog.SendHResult('Result', Result);
LMDLog.LeaveMethod('TestFunction');
end;
end;
...
and methods that help to trace of application
uses
..., LMDLogMessage, ...;
...
for i := 0 to Count - 1 do
begin
LMDLog.SendInteger('Iteration', i);
b := 20 + i;
LMDLog.TouchCounter('Checkpoint');
a := CalcA(b);
LMDLog.TouchCounter('Checkpoint');
c := a div 34;
LMDLog.ReleaseCounter('Checkpoint');
end;
...
To control log storage TLMDLogSession has next methods - TLMDLogSession.ClearLog, TLMDLogSession.ClearWatch and TLMDLogSession.ClearCallStack
TLMDLogSession allow you to control level of you logging messages. It has two properties for it - TLMDLogSession.AllowLogLevel - set minimum log level for messages, TLMDLogSession.DefaultLogLevel - set default log level for messages where level is not set as parameter.
uses
..., LMDLogMessage, ...;
...
{$ifdef DEBUG}
LMDLog.AllowLogLevel := LogAll;
LMDLog.AllowLogLevel := LogAll;
{$ELSE}
LMDLog.AllowLogLevel := LogFatal;
LMDLog.DefaultLogLevel := LogOff;
{$ENDIF}
for i := 0 to 4 do
begin
try
LMDLog.SendInteger('i', i);
a := 5 / i;
LMDLog.SendReal('a', a);
except
on E: EDivideByZero do
LMDLog.SendString(LogFatal, 'Error', 'Divide by zero');
end;
end;
...
Creating custom sessions
Custom sessions can be creating by using LMDLogManager.AddSession() method. Via it custom transport, name etc. can be specified. example:
MyLog := LMDLogManage.AddSession('SessionName|lmd_db_session|lmd_wmcopydata_log|WindowClass=TLMDLogHelperWindow;WindowName=LMDLogHelper');
Then you can use it:
MyLog.SendString('name', 'value');
or
LMDLogManager.Sessions['SessionName'].SendString('name', 'value');
Aboyut this string - 'SessionName|lmd_db_session|lmd_wmcopydata_log|WindowClass=TLMDLogHelperWindow;WindowName=LMDLogHelper'
- SessionName - it's exactly what is it - session name. You can name it as you wish. Except "Root" - this name is reserved for session which stored in LMDLog
- lmd_db_session - type of session that you want to create. Currently library contains two session classes - TLMDLogSession (lmd_db_session) and TLMDLogDBSession (lmd_db_session). 'lmd_db_session' and 'lmd_db_session' are specified in GetName method of session. If you will extend TLMDLogCustomSession to logging you custom data then you have to override this method and specify you own unique name
- lmd_wmcopydata_log - type of transport that you want to create. each new transport have to override GetName method of TLMDLogCustomTransport and return it's own unique name.
- WindowClass=TLMDLogHelperWindow and WindowName=LMDLogHelper - are transport parameters - can be different for different transports.
AnyLogger console application
This logging and monitoring console for LMD Log package. All data that you send from your application are collected and displayed here.
Creating Viewer plugin for AnyLogger console
NB. This API information can be changed before AnyLogger release
AnyLogger console can be extended by pluggable modules (plugins). Plugins based on new package LMD Plugins. Currently you can create plugin to view custom data, that you sent to console, in more obvious form.
To create such plugin you will need LMD Plugins package and uCreatePlugin.pas unit.
unit uCreatePlugin;
interface
uses
SysUtils, Forms, LMDPluginManager, LMDPluginImpl;
type
TLMDActionExecutor = class(TObject)
public
procedure GetCount(const AnAction: ILMDAction; var AResult: ILMDParameter); virtual; stdcall; abstract;
procedure GetType(const AnAction: ILMDAction; var AResult: ILMDParameter); virtual; stdcall; abstract;
procedure GetIcon(const AnAction: ILMDAction; var AResult: ILMDParameter); virtual; stdcall; abstract;
procedure GetName(const AnAction: ILMDAction; var AResult: ILMDParameter); virtual; stdcall; abstract;
end;
function CreatePlugin(AExecuter: TLMDActionExecutor; AFormClass: TFormClass): ILMDPlugin;
implementation
function CreatePlugin(AExecuter: TLMDActionExecutor; AFormClass: TFormClass): ILMDPlugin;
var
Plugin: TLMDPlugin;
begin
try
// Create the plugin
Plugin := TLMDPlugin.Create('Author', 'AnyLogger plugin', 0, 1);
// add GetSupportedSubTypesCount action
Plugin.AddAction('GetSubTypesCount').OnPerformAction := AExecuter.GetCount;
with Plugin.AddAction('GetSubTypeName') do
begin
with AddParameter('SubType') do
ParameterType := ptGUID;
OnPerformAction := AExecuter.GetName;
end;
with Plugin.AddAction('GetSubType') do
begin
with AddParameter('Index') do
ParameterType := ptInteger;
OnPerformAction := AExecuter.GetType;
end;
with Plugin.AddAction('GetSubTypeIconIndex') do
begin
with AddParameter('SubType') do
ParameterType := ptGUID;
OnPerformAction := AExecuter.GetIcon;
end;
// add form into plugin
Plugin.AddForm(TLMDDelphiPluginForm.Create('Viewer', AFormClass));
// return the plugin
Result := Plugin;
except
Result := nil;
exit;
end;
end;
end.
uCreatePlugin.pas create TLMDPlugin instance and init with three plugin method "GetSubTypesCount" - returns count of lmdlog message subtypes that will showed by this plugins. Next method is "GetSubTypeName" - returns name of subtype by it's index (0..GetSubTypesCount - 1). "GetSubType" method returns GUID that identificate subtype. And the last method is "GetSubTypeIconIndex" - it returns iconindex to show icon in AnyLogger console by subtype GUID.
So, uCreatePlugin.pas do above basic steps for you. Next, you need to create form with interface ILMDFormWithData, which described in LMDPluginManager unit from LMD Plugins package.
{ **************************** ILMDFormWithData **************************** }
ILMDFormWithData = interface(IInterface)
['{8999ED41-2791-4471-BFEB-994923CE1CD4}']
procedure SetFormData(AData: Pointer);
end;
</delphi> <br> This interface is needed to fill form with you custom data. AnyLogger Console call SetFormData method with TLMDLogMessage instance as parameter. Here is example of such form:<br>
<delphi>
...
type
TTextForm = class(TForm, ILMDFormWithData)
edtText: TElMemo;
private
{ Private declarations }
public
procedure SetFormData(AData: Pointer);
end;
var
TextForm: TTextForm;
implementation
{$R *.dfm}
{ TTextForm }
procedure TTextForm.SetFormData(AData: Pointer);
begin
edtText.Text := TLMDLogMessage(AData).MessageValue;
end;
...
Next step is creating child class for TLMDActionExecutor from uCreatePlugin.pas and override handlers of plugin methods. Example:
unit intfTextView;
interface
uses
Classes, SysUtils, uCreatePlugin, LMDLogMessage, LMDPluginImpl, LMDPluginManager;
type
TLMDTextActionExecutor = class(TLMDActionExecutor)
public
procedure GetCount(const AnAction: ILMDAction; var AResult: ILMDParameter); override; stdcall;
procedure GetType(const AnAction: ILMDAction; var AResult: ILMDParameter); override; stdcall;
procedure GetIcon(const AnAction: ILMDAction; var AResult: ILMDParameter); override; stdcall;
procedure GetName(const AnAction: ILMDAction; var AResult: ILMDParameter); override; stdcall;
end;
implementation
{ TLMDTextActionExecutor }
procedure TLMDTextActionExecutor.GetCount(const AnAction: ILMDAction; var AResult: ILMDParameter);
begin
AResult.AsInteger := 1;
end;
procedure TLMDTextActionExecutor.GetIcon(const AnAction: ILMDAction; var AResult: ILMDParameter);
begin
AResult.AsInteger := -1;
if IsEqualGUID(AnAction[0].AsGUID, mstString) then
AResult.AsInteger := 5;
end;
procedure TLMDTextActionExecutor.GetName(const AnAction: ILMDAction; var AResult: ILMDParameter);
begin
if IsEqualGUID(AnAction[0].AsGUID, mstString) then
AResult.AsString := 'String';
end;
procedure TLMDTextActionExecutor.GetType(const AnAction: ILMDAction; var AResult: ILMDParameter);
begin
case AnAction[0].AsInteger of
0: AResult.AsGUID := mstString;
else
AResult.AsGUID := mstNone;
end;
end;
end.
And final step is create Dll project with export function GetLMDPlugin (see LMD Plguins for details).
library TextView;
uses
SysUtils,
Classes,
Windows,
Forms,
uCreatePlugin,
LMDLogMessage,
LMDPluginImpl,
LMDPluginManager,
frmTextView in 'frmTextView.pas' {TableForm},
intfTextView in 'intfTextView.pas';
{$R *.res}
var
LOldApplication: TApplication;
function GetLMDPlugin(const AHostApplication: TApplication; var APlugin: ILMDPlugin): HRESULT; stdcall;
begin
try
LOldApplication := Application;
Application := AHostApplication;
APlugin := CreatePlugin(TLMDTextActionExecutor.Create, TTextForm);
except
Result := E_UNEXPECTED;
exit;
end;
Result := S_OK;
end;
procedure DllMain(AReason: integer) ;
begin
case AReason of
DLL_PROCESS_DETACH:
if Assigned(LOldApplication) then
Application := LOldApplication;
end;
end;
exports
GetLMDPlugin name 'GetLMDPlugin';
begin
LOldApplication := nil;
DllProc := @DllMain;
end.
Compile it and copy result dll file to Viewers folder in AnyLogger console installation path.