1
0
mirror of synced 2024-11-21 10:53:50 +00:00

FS#17 - Log export ability

Fixed: DateTime is now passed around everywhere (to accomodate the Export functionality)
This commit is contained in:
Mark van Renswoude 2014-10-08 12:33:11 +00:00
parent fabf621dc0
commit ca47ad3f4e
15 changed files with 299 additions and 52 deletions

View File

@ -11,7 +11,6 @@ uses
var var
client: IX2LogObservable; client: IX2LogObservable;
observerForm: TX2LogObserverMonitorForm;
begin begin
ReportMemoryLeaksOnShutdown := True; ReportMemoryLeaksOnShutdown := True;
@ -22,8 +21,12 @@ begin
client := TX2LogNamedPipeClient.Create('X2LogTest'); client := TX2LogNamedPipeClient.Create('X2LogTest');
try try
observerForm := TX2LogObserverMonitorForm.Instance(client); with TX2LogObserverMonitorForm.Create(nil, client) do
observerForm.ShowModal; try
ShowModal;
finally
Free;
end;
finally finally
client := nil; client := nil;
end; end;

View File

@ -87,7 +87,7 @@ type
FFileObserver: IX2LogObserver; FFileObserver: IX2LogObserver;
FNamedPipeObserver: IX2LogObserver; FNamedPipeObserver: IX2LogObserver;
protected protected
procedure DoLog(Sender: TObject; Level: TX2LogLevel; const Msg: string; Details: IX2LogDetails); procedure DoLog(Sender: TObject; Level: TX2LogLevel; DateTime: TDateTime; const Msg: string; Details: IX2LogDetails);
end; end;
@ -149,7 +149,7 @@ begin
end; end;
procedure TMainForm.DoLog(Sender: TObject; Level: TX2LogLevel; const Msg: string; Details: IX2LogDetails); procedure TMainForm.DoLog(Sender: TObject; Level: TX2LogLevel; DateTime: TDateTime; const Msg: string; Details: IX2LogDetails);
var var
text: string; text: string;
logDetailsText: IX2LogDetailsText; logDetailsText: IX2LogDetailsText;

View File

@ -19,8 +19,8 @@ type
destructor Destroy; override; destructor Destroy; override;
{ IX2LogBase } { IX2LogBase }
procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails = nil); virtual; procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails = nil); overload; virtual;
procedure Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails = nil); overload; virtual;
{ IX2LogObservable } { IX2LogObservable }
procedure Attach(AObserver: IX2LogObserver); procedure Attach(AObserver: IX2LogObserver);
@ -73,5 +73,15 @@ begin
observer.Log(ALevel, AMessage, ADetails); observer.Log(ALevel, AMessage, ADetails);
end; end;
procedure TX2LogBaseClient.Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
var
observer: IX2LogObserver;
begin
for observer in Observers do
observer.Log(ALevel, ADateTime, AMessage, ADetails);
end;
end. end.

View File

@ -22,7 +22,7 @@ type
end; end;
TX2LogNamedPipeClient = class(TX2LogBaseClient, IX2LogBase) TX2LogNamedPipeClient = class(TX2LogBaseClient)
private private
FWorkerThread: TThread; FWorkerThread: TThread;
protected protected
@ -393,7 +393,7 @@ begin
end; end;
end; end;
Client.Log(header.Level, msg, details); Client.Log(header.Level, header.DateTime, msg, details);
except except
on E:EReadError do on E:EReadError do
ClosePipe; ClosePipe;

View File

@ -43,15 +43,29 @@ resourcestring
LogMonitorFormButtonPause = 'Pause'; LogMonitorFormButtonPause = 'Pause';
LogMonitorFormButtonCopyDetails = 'Copy'; LogMonitorFormButtonCopyDetails = 'Copy';
LogMonitorFormButtonSaveDetails = 'Save'; LogMonitorFormButtonSaveDetails = 'Save';
LogMonitorFormButtonWordWrapDetails = 'Word wrap';
LogMonitorFormButtonFilter = 'Filter:'; LogMonitorFormButtonFilter = 'Filter:';
{ Caption of the menu items which are not on the toolbar }
LogMonitorFormMenuFile = 'File';
LogMonitorFormMenuFileSaveAs = 'Save as...';
LogMonitorFormMenuFileClose = 'Close';
LogMonitorFormMenuLog = 'Log';
LogMonitorFormMenuDetails = 'Details';
LogMonitorFormMenuWindow = 'Window';
LogMonitorFormMenuWindowAlwaysOnTop = 'Always on top';
{ Status messages } { Status messages }
LogMonitorFormStatusPaused = 'Paused: %d log message(s) skipped'; LogMonitorFormStatusPaused = 'Paused: %d log message(s) skipped';
{ Filter for Save details buttons } { Filter for Save details buttons }
LogMonitorFormSaveDetailsFilter = 'All files (*.*)|*.*'; LogMonitorFormSaveDetailsFilter = 'All files (*.*)|*.*';
{ Filter for Save as menu item }
LogMonitorFormSaveDetailsSaveAs = 'Log files (*.log)|*.log|All files (*.*)|*.*';
function GetLogLevelText(ALogLevel: TX2LogLevel): string; function GetLogLevelText(ALogLevel: TX2LogLevel): string;

View File

@ -24,7 +24,8 @@ type
class procedure SetExceptionStrategy(AStrategy: IX2LogExceptionStrategy); class procedure SetExceptionStrategy(AStrategy: IX2LogExceptionStrategy);
{ Facade for IX2LogBase } { Facade for IX2LogBase }
class procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); class procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); overload;
class procedure Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); overload;
class procedure Verbose(const AMessage: string; const ADetails: string = ''); class procedure Verbose(const AMessage: string; const ADetails: string = '');
class procedure VerboseEx(const AMessage: string; ADetails: IX2LogDetails = nil); class procedure VerboseEx(const AMessage: string; ADetails: IX2LogDetails = nil);
@ -87,6 +88,12 @@ begin
end; end;
class procedure TX2GlobalLog.Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
begin
Instance.Log(ALevel, ADateTime, AMessage, ADetails);
end;
class procedure TX2GlobalLog.Verbose(const AMessage, ADetails: string); class procedure TX2GlobalLog.Verbose(const AMessage, ADetails: string);
begin begin
Instance.Verbose(AMessage, ADetails); Instance.Verbose(AMessage, ADetails);

View File

@ -49,6 +49,7 @@ type
IX2LogBase = interface IX2LogBase = interface
['{1949E8DC-6DC5-43DC-B678-55CF8274E79D}'] ['{1949E8DC-6DC5-43DC-B678-55CF8274E79D}']
procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails = nil); overload; procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails = nil); overload;
procedure Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails = nil); overload;
end; end;
@ -95,6 +96,7 @@ type
ID: Word; ID: Word;
Version: Byte; Version: Byte;
Size: Word; Size: Word;
DateTime: TDateTime;
Level: TX2LogLevel; Level: TX2LogLevel;
{ {

View File

@ -15,14 +15,15 @@ type
private private
FLogLevels: TX2LogLevels; FLogLevels: TX2LogLevels;
protected protected
procedure DoLog(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); virtual; abstract; procedure DoLog(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); virtual; abstract;
property LogLevels: TX2LogLevels read FLogLevels; property LogLevels: TX2LogLevels read FLogLevels;
public public
constructor Create(ALogLevels: TX2LogLevels = X2LogLevelsDefault); constructor Create(ALogLevels: TX2LogLevels = X2LogLevelsDefault);
{ IX2LogBase } { IX2LogBase }
procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails = nil); procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails = nil); overload;
procedure Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails = nil); overload;
end; end;
@ -39,9 +40,15 @@ end;
procedure TX2LogCustomObserver.Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); procedure TX2LogCustomObserver.Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails);
begin
Log(ALevel, Now, AMessage, ADetails);
end;
procedure TX2LogCustomObserver.Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
begin begin
if ALevel in LogLevels then if ALevel in LogLevels then
DoLog(ALevel, AMessage, ADetails); DoLog(ALevel, ADateTime, AMessage, ADetails);
end; end;
end. end.

View File

@ -20,7 +20,7 @@ type
protected protected
function CreateWorkerThread: TX2LogObserverWorkerThread; virtual; abstract; function CreateWorkerThread: TX2LogObserverWorkerThread; virtual; abstract;
procedure DoLog(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); override; procedure DoLog(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); override;
property WorkerThread: TX2LogObserverWorkerThread read FWorkerThread; property WorkerThread: TX2LogObserverWorkerThread read FWorkerThread;
public public
@ -33,13 +33,15 @@ type
private private
FDetails: IX2LogDetails; FDetails: IX2LogDetails;
FLevel: TX2LogLevel; FLevel: TX2LogLevel;
FDateTime: TDateTime;
FMessage: string; FMessage: string;
public public
constructor Create(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); overload; constructor Create(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); overload;
constructor Create(AEntry: TX2LogQueueEntry); overload; constructor Create(AEntry: TX2LogQueueEntry); overload;
procedure Assign(Source: TPersistent); override; procedure Assign(Source: TPersistent); override;
property DateTime: TDateTime read FDateTime;
property Details: IX2LogDetails read FDetails; property Details: IX2LogDetails read FDetails;
property Level: TX2LogLevel read FLevel; property Level: TX2LogLevel read FLevel;
property Message: string read FMessage; property Message: string read FMessage;
@ -51,6 +53,7 @@ type
FFileName: string; FFileName: string;
FLogQueue: TObjectQueue<TX2LogQueueEntry>; FLogQueue: TObjectQueue<TX2LogQueueEntry>;
FLogQueueSignal: TEvent; FLogQueueSignal: TEvent;
FThreadStartSignal: TEvent;
protected protected
procedure Execute; override; procedure Execute; override;
procedure TerminatedSet; override; procedure TerminatedSet; override;
@ -68,7 +71,7 @@ type
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); procedure Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
end; end;
@ -94,19 +97,20 @@ begin
end; end;
procedure TX2LogCustomThreadedObserver.DoLog(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); procedure TX2LogCustomThreadedObserver.DoLog(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
begin begin
WorkerThread.Log(ALevel, AMessage, ADetails); WorkerThread.Log(ALevel, ADateTime, AMessage, ADetails);
end; end;
{ TX2LogQueueEntry } { TX2LogQueueEntry }
constructor TX2LogQueueEntry.Create(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); constructor TX2LogQueueEntry.Create(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
begin begin
inherited Create; inherited Create;
FLevel := ALevel; FLevel := ALevel;
FDateTime := ADateTime;
FMessage := AMessage; FMessage := AMessage;
FDetails := ADetails; FDetails := ADetails;
end; end;
@ -130,6 +134,7 @@ begin
entrySource := TX2LogQueueEntry(Source); entrySource := TX2LogQueueEntry(Source);
FLevel := entrySource.Level; FLevel := entrySource.Level;
FDateTime := entrySource.DateTime;
FMessage := entrySource.Message; FMessage := entrySource.Message;
FDetails := entrySource.Details; FDetails := entrySource.Details;
end else end else
@ -140,6 +145,7 @@ end;
{ TX2LogObserverWorkerThread } { TX2LogObserverWorkerThread }
constructor TX2LogObserverWorkerThread.Create; constructor TX2LogObserverWorkerThread.Create;
begin begin
FThreadStartSignal := TEvent.Create(nil, True, False, '');
FLogQueueSignal := TEvent.Create(nil, False, False, ''); FLogQueueSignal := TEvent.Create(nil, False, False, '');
FLogQueue := TObjectQueue<TX2LogQueueEntry>.Create(True); FLogQueue := TObjectQueue<TX2LogQueueEntry>.Create(True);
@ -149,18 +155,24 @@ end;
destructor TX2LogObserverWorkerThread.Destroy; destructor TX2LogObserverWorkerThread.Destroy;
begin begin
{ For very short-lived observers (for example, the "Save as" functionality
of the observer form) the WorkerThread can be destroyed before the thread
has a chance to properly start and clear out it's queue. }
FThreadStartSignal.WaitFor(INFINITE);
inherited Destroy; inherited Destroy;
FreeAndNil(FLogQueue); FreeAndNil(FLogQueue);
FreeAndNil(FLogQueueSignal); FreeAndNil(FLogQueueSignal);
FreeAndNil(FThreadStartSignal);
end; end;
procedure TX2LogObserverWorkerThread.Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); procedure TX2LogObserverWorkerThread.Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
begin begin
TMonitor.Enter(LogQueue); TMonitor.Enter(LogQueue);
try try
LogQueue.Enqueue(TX2LogQueueEntry.Create(ALevel, AMessage, ADetails)); LogQueue.Enqueue(TX2LogQueueEntry.Create(ALevel, ADateTime, AMessage, ADetails));
finally finally
TMonitor.Exit(LogQueue); TMonitor.Exit(LogQueue);
end; end;
@ -174,16 +186,16 @@ var
entry: TX2LogQueueEntry; entry: TX2LogQueueEntry;
begin begin
FThreadStartSignal.SetEvent;
NameThreadForDebugging('TX2LogObserverWorkerThread'); NameThreadForDebugging('TX2LogObserverWorkerThread');
Setup; Setup;
try try
while not Terminated do while True do
begin begin
WaitForEntry; { When Terminated, flush the queue }
if not Terminated then
if Terminated then WaitForEntry;
break;
entry := nil; entry := nil;
TMonitor.Enter(LogQueue); TMonitor.Enter(LogQueue);
@ -199,7 +211,8 @@ begin
ProcessEntry(entry); ProcessEntry(entry);
finally finally
FreeAndNil(entry); FreeAndNil(entry);
end; end else if Terminated then
break;
end; end;
finally finally
Cleanup; Cleanup;

View File

@ -7,7 +7,7 @@ uses
type type
TX2LogEvent = procedure(Sender: TObject; Level: TX2LogLevel; const Msg: string; Details: IX2LogDetails) of object; TX2LogEvent = procedure(Sender: TObject; Level: TX2LogLevel; DateTime: TDateTime; const Msg: string; Details: IX2LogDetails) of object;
TX2LogEventObserver = class(TX2LogCustomObserver) TX2LogEventObserver = class(TX2LogCustomObserver)
@ -15,7 +15,7 @@ type
FOnLog: TX2LogEvent; FOnLog: TX2LogEvent;
FRunInMainThread: Boolean; FRunInMainThread: Boolean;
protected protected
procedure DoLog(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); override; procedure DoLog(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); override;
public public
constructor Create(ALogLevels: TX2LogLevels = X2LogLevelsDefault); overload; constructor Create(ALogLevels: TX2LogLevels = X2LogLevelsDefault); overload;
constructor Create(AOnLog: TX2LogEvent; ALogLevels: TX2LogLevels = X2LogLevelsDefault); overload; constructor Create(AOnLog: TX2LogEvent; ALogLevels: TX2LogLevels = X2LogLevelsDefault); overload;
@ -48,7 +48,7 @@ begin
end; end;
procedure TX2LogEventObserver.DoLog(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); procedure TX2LogEventObserver.DoLog(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
begin begin
if Assigned(FOnLog) then if Assigned(FOnLog) then
begin begin
@ -58,10 +58,10 @@ begin
procedure procedure
begin begin
if Assigned(FOnLog) then if Assigned(FOnLog) then
FOnLog(Self, ALevel, AMessage, ADetails); FOnLog(Self, ALevel, ADateTime, AMessage, ADetails);
end); end);
end else end else
FOnLog(Self, ALevel, AMessage, ADetails); FOnLog(Self, ALevel, ADateTime, AMessage, ADetails);
end; end;
end; end;

View File

@ -122,7 +122,7 @@ begin
if Supports(AEntry.Details, IX2LogDetailsStreamable, logDetailsStreamable) then if Supports(AEntry.Details, IX2LogDetailsStreamable, logDetailsStreamable) then
begin begin
detailsExtension := ExtractFileExt(FileName); detailsExtension := ExtractFileExt(FileName);
baseReportFileName := ChangeFileExt(FileName, '_' + FormatDateTime(GetLogResourceString(@LogFileNameDateFormat), Now)); baseReportFileName := ChangeFileExt(FileName, '_' + FormatDateTime(GetLogResourceString(@LogFileNameDateFormat), AEntry.DateTime));
detailsFileName := baseReportFileName + detailsExtension; detailsFileName := baseReportFileName + detailsExtension;
detailsNumber := 0; detailsNumber := 0;
@ -170,7 +170,7 @@ begin
{ Append line to log file } { Append line to log file }
writer := TFile.AppendText(FileName); writer := TFile.AppendText(FileName);
try try
writer.WriteLine('[' + FormatDateTime(GetLogResourceString(@LogFileLineDateFormat), Now) + '] ' + writer.WriteLine('[' + FormatDateTime(GetLogResourceString(@LogFileLineDateFormat), AEntry.DateTime) + '] ' +
GetLogLevelText(AEntry.Level) + ': ' + errorMsg); GetLogLevelText(AEntry.Level) + ': ' + errorMsg);
finally finally
FreeAndNil(writer); FreeAndNil(writer);

View File

@ -10,6 +10,7 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm
Font.Height = -11 Font.Height = -11
Font.Name = 'Tahoma' Font.Name = 'Tahoma'
Font.Style = [] Font.Style = []
Menu = mmMain
OldCreateOrder = False OldCreateOrder = False
Position = poScreenCenter Position = poScreenCenter
ShowHint = True ShowHint = True
@ -181,7 +182,7 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm
end end
item item
Position = 2 Position = 2
Width = 424 Width = 428
WideText = 'Message' WideText = 'Message'
end> end>
end end
@ -198,24 +199,24 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm
ShowCaptions = True ShowCaptions = True
TabOrder = 1 TabOrder = 1
OnCustomDraw = ToolbarCustomDraw OnCustomDraw = ToolbarCustomDraw
object tbClear: TToolButton
Left = 0
Top = 0
Action = actClear
AutoSize = True
end
object tbPause: TToolButton object tbPause: TToolButton
Left = 56 Left = 0
Top = 0 Top = 0
Action = actPause Action = actPause
AutoSize = True AutoSize = True
Style = tbsCheck Style = tbsCheck
end end
object tbClear: TToolButton
Left = 60
Top = 0
Action = actClear
AutoSize = True
end
object lblFilter: TLabel object lblFilter: TLabel
Left = 116 Left = 116
Top = 0 Top = 0
Width = 46 Width = 46
Height = 22 Height = 13
Caption = ' Filter: ' Caption = ' Filter: '
Layout = tlCenter Layout = tlCenter
end end
@ -266,7 +267,7 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm
Left = 448 Left = 448
Top = 48 Top = 48
Bitmap = { Bitmap = {
494C01010A004000D00010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 494C01010A004000EC0010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600
0000000000003600000028000000400000003000000001002000000000000030 0000000000003600000028000000400000003000000001002000000000000030
0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000
@ -722,15 +723,97 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm
end end
object actWordWrap: TAction object actWordWrap: TAction
AutoCheck = True AutoCheck = True
Caption = '&Word wrap' Caption = 'Word wrap'
Enabled = False Enabled = False
ImageIndex = 9 ImageIndex = 9
OnExecute = actWordWrapExecute OnExecute = actWordWrapExecute
end end
object actClose: TAction
Caption = '&Close'
OnExecute = actCloseExecute
end
object actAlwaysOnTop: TAction
Caption = 'Always on top'
OnExecute = actAlwaysOnTopExecute
end
object actSaveAs: TAction
Caption = '&Save as...'
ImageIndex = 5
OnExecute = actSaveAsExecute
end
end end
object sdDetails: TSaveDialog object sdDetails: TSaveDialog
Options = [ofOverwritePrompt, ofHideReadOnly, ofEnableSizing] Options = [ofOverwritePrompt, ofHideReadOnly, ofEnableSizing]
Left = 512 Left = 512
Top = 112 Top = 112
end end
object mmMain: TMainMenu
Images = ilsLog
Left = 448
Top = 112
object mmMainFile: TMenuItem
Caption = 'File'
object mmMainFileSaveAs: TMenuItem
Action = actSaveAs
end
object mmMainFileSep1: TMenuItem
Caption = '-'
end
object mmMainFileClose: TMenuItem
Action = actClose
end
end
object mmMainLog: TMenuItem
Caption = 'Log'
object mmMainLogPause: TMenuItem
Action = actPause
AutoCheck = True
end
object mmMainLogClear: TMenuItem
Action = actClear
end
object mmMainLogSep1: TMenuItem
Caption = '-'
end
object mmMainLogVerbose: TMenuItem
Action = actShowVerbose
end
object mmMainLogInfo: TMenuItem
Action = actShowInfo
end
object mmMainLogWarning: TMenuItem
Action = actShowWarning
end
object mmMainLogError: TMenuItem
Action = actShowError
end
end
object mmMainDetails: TMenuItem
Caption = 'Details'
object mmMainDetailsCopy: TMenuItem
Action = actCopyDetails
end
object mmMainDetailsSave: TMenuItem
Action = actSaveDetails
end
object mmMainDetailsSep1: TMenuItem
Caption = '-'
end
object mmMainDetailsWordWrap: TMenuItem
Action = actWordWrap
AutoCheck = True
end
end
object mmMainWindow: TMenuItem
Caption = 'Window'
object mmMainWindowAlwaysOnTop: TMenuItem
Action = actAlwaysOnTop
end
end
end
object sdSaveAs: TSaveDialog
Options = [ofOverwritePrompt, ofHideReadOnly, ofEnableSizing]
Left = 512
Top = 176
end
end end

View File

@ -18,7 +18,7 @@ uses
Winapi.Messages, Winapi.Messages,
X2Log.Details.Intf, X2Log.Details.Intf,
X2Log.Intf; X2Log.Intf, Vcl.Menus;
const const
@ -70,6 +70,30 @@ type
tbWordWrap: TToolButton; tbWordWrap: TToolButton;
tbDetailsSep1: TToolButton; tbDetailsSep1: TToolButton;
actWordWrap: TAction; actWordWrap: TAction;
mmMain: TMainMenu;
mmMainFile: TMenuItem;
mmMainLog: TMenuItem;
mmMainDetails: TMenuItem;
mmMainWindow: TMenuItem;
actClose: TAction;
mmMainFileClose: TMenuItem;
mmMainLogClear: TMenuItem;
mmMainLogPause: TMenuItem;
mmMainLogSep1: TMenuItem;
mmMainLogVerbose: TMenuItem;
mmMainLogInfo: TMenuItem;
mmMainLogWarning: TMenuItem;
mmMainLogError: TMenuItem;
mmMainDetailsCopy: TMenuItem;
mmMainDetailsSave: TMenuItem;
mmMainDetailsWordWrap: TMenuItem;
mmMainDetailsSep1: TMenuItem;
actAlwaysOnTop: TAction;
mmMainWindowAlwaysOnTop: TMenuItem;
actSaveAs: TAction;
mmMainFileSep1: TMenuItem;
mmMainFileSaveAs: TMenuItem;
sdSaveAs: TSaveDialog;
procedure FormShow(Sender: TObject); procedure FormShow(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure FormClose(Sender: TObject; var Action: TCloseAction);
@ -78,6 +102,7 @@ type
procedure vstLogGetHint(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: string); procedure vstLogGetHint(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle; var HintText: string);
procedure vstLogGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer); procedure vstLogGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
procedure vstLogFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex); procedure vstLogFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
procedure actCloseExecute(Sender: TObject);
procedure actClearExecute(Sender: TObject); procedure actClearExecute(Sender: TObject);
procedure actCopyDetailsExecute(Sender: TObject); procedure actCopyDetailsExecute(Sender: TObject);
procedure actSaveDetailsExecute(Sender: TObject); procedure actSaveDetailsExecute(Sender: TObject);
@ -88,6 +113,8 @@ type
procedure actShowWarningExecute(Sender: TObject); procedure actShowWarningExecute(Sender: TObject);
procedure actShowErrorExecute(Sender: TObject); procedure actShowErrorExecute(Sender: TObject);
procedure actWordWrapExecute(Sender: TObject); procedure actWordWrapExecute(Sender: TObject);
procedure actAlwaysOnTopExecute(Sender: TObject);
procedure actSaveAsExecute(Sender: TObject);
private class var private class var
FInstances: TMonitorFormDictionary; FInstances: TMonitorFormDictionary;
private private
@ -128,6 +155,8 @@ type
procedure SetVisibleDetails(AControl: TControl); procedure SetVisibleDetails(AControl: TControl);
procedure SetWordWrap(AValue: Boolean); procedure SetWordWrap(AValue: Boolean);
procedure ExportLog(ALog: IX2LogBase);
property Closed: Boolean read FClosed; property Closed: Boolean read FClosed;
property Details: IX2LogDetails read FDetails; property Details: IX2LogDetails read FDetails;
property LockCount: Integer read FLockCount; property LockCount: Integer read FLockCount;
@ -154,7 +183,8 @@ type
procedure Unlock; procedure Unlock;
{ IX2LogObserver } { IX2LogObserver }
procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); overload;
procedure Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); overload;
property FreeOnClose: Boolean read FFreeOnClose write FFreeOnClose; property FreeOnClose: Boolean read FFreeOnClose write FFreeOnClose;
property MaxEntries: Cardinal read FMaxEntries write FMaxEntries; property MaxEntries: Cardinal read FMaxEntries write FMaxEntries;
@ -170,7 +200,8 @@ uses
Vcl.Themes, Vcl.Themes,
Winapi.Windows, Winapi.Windows,
X2Log.Constants; X2Log.Constants,
X2Log.Observer.LogFile;
{$R *.dfm} {$R *.dfm}
@ -184,7 +215,7 @@ type
Message: string; Message: string;
Details: IX2LogDetails; Details: IX2LogDetails;
procedure Initialize(APaused: Boolean; ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); procedure Initialize(APaused: Boolean; ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
end; end;
PLogEntryNodeData = ^TLogEntryNodeData; PLogEntryNodeData = ^TLogEntryNodeData;
@ -199,9 +230,9 @@ const
{ TLogEntryNode } { TLogEntryNode }
procedure TLogEntryNodeData.Initialize(APaused: Boolean; ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); procedure TLogEntryNodeData.Initialize(APaused: Boolean; ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
begin begin
Self.Time := Now; Self.Time := ADateTime;
Self.Paused := APaused; Self.Paused := APaused;
Self.Level := ALevel; Self.Level := ALevel;
Self.Message := AMessage; Self.Message := AMessage;
@ -316,12 +347,21 @@ begin
vstLog.Header.Columns[ColumnTime].Text := GetLogResourceString(@LogMonitorFormColumnTime); vstLog.Header.Columns[ColumnTime].Text := GetLogResourceString(@LogMonitorFormColumnTime);
vstLog.Header.Columns[ColumnMessage].Text := GetLogResourceString(@LogMonitorFormColumnMessage); vstLog.Header.Columns[ColumnMessage].Text := GetLogResourceString(@LogMonitorFormColumnMessage);
mmMainFile.Caption := GetLogResourceString(@LogMonitorFormMenuFile);
mmMainLog.Caption := GetLogResourceString(@LogMonitorFormMenuLog);
mmMainDetails.Caption := GetLogResourceString(@LogMonitorFormMenuDetails);
mmMainWindow.Caption := GetLogResourceString(@LogMonitorFormMenuWindow);
actSaveAs.Caption := GetLogResourceString(@LogMonitorFormMenuFileSaveAs);
actClose.Caption := GetLogResourceString(@LogMonitorFormMenuFileClose);
actClear.Caption := GetLogResourceString(@LogMonitorFormButtonClear); actClear.Caption := GetLogResourceString(@LogMonitorFormButtonClear);
actPause.Caption := GetLogResourceString(@LogMonitorFormButtonPause); actPause.Caption := GetLogResourceString(@LogMonitorFormButtonPause);
actCopyDetails.Caption := GetLogResourceString(@LogMonitorFormButtonCopyDetails); actCopyDetails.Caption := GetLogResourceString(@LogMonitorFormButtonCopyDetails);
actSaveDetails.Caption := GetLogResourceString(@LogMonitorFormButtonSaveDetails); actSaveDetails.Caption := GetLogResourceString(@LogMonitorFormButtonSaveDetails);
actAlwaysOnTop.Caption := GetLogResourceString(@LogMonitorFormMenuWindowAlwaysOnTop);
sdDetails.Filter := GetLogResourceString(@LogMonitorFormSaveDetailsFilter); sdDetails.Filter := GetLogResourceString(@LogMonitorFormSaveDetailsFilter);
sdSaveAs.Filter := GetLogResourceString(@LogMonitorFormSaveDetailsSaveAs);
lblFilter.Caption := ' ' + GetLogResourceString(@LogMonitorFormButtonFilter) + ' '; lblFilter.Caption := ' ' + GetLogResourceString(@LogMonitorFormButtonFilter) + ' ';
actShowVerbose.Caption := GetLogLevelText(TX2LogLevel.Verbose); actShowVerbose.Caption := GetLogLevelText(TX2LogLevel.Verbose);
@ -417,6 +457,12 @@ end;
procedure TX2LogObserverMonitorForm.Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); procedure TX2LogObserverMonitorForm.Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails);
begin
Log(ALevel, Now, AMessage, ADetails);
end;
procedure TX2LogObserverMonitorForm.Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails);
var var
node: PVirtualNode; node: PVirtualNode;
nodeData: PLogEntryNodeData; nodeData: PLogEntryNodeData;
@ -440,7 +486,7 @@ begin
{ BeginUpdate causes OnInitNode to be triggered on-demand, { BeginUpdate causes OnInitNode to be triggered on-demand,
moved Initialize call here } moved Initialize call here }
Initialize(nodeData^); Initialize(nodeData^);
nodeData^.Initialize(Paused, ALevel, AMessage, ADetails); nodeData^.Initialize(Paused, ALevel, ADateTime, AMessage, ADetails);
vstLog.IsVisible[node] := (not Paused) and (ALevel in VisibleLevels); vstLog.IsVisible[node] := (not Paused) and (ALevel in VisibleLevels);
@ -484,6 +530,7 @@ end;
procedure TX2LogObserverMonitorForm.UpdateUI; procedure TX2LogObserverMonitorForm.UpdateUI;
begin begin
actClear.Enabled := (vstLog.RootNodeCount > 0); actClear.Enabled := (vstLog.RootNodeCount > 0);
actSaveAs.Enabled := (vstLog.RootNodeCount > 0);
end; end;
@ -706,6 +753,20 @@ begin
end; end;
procedure TX2LogObserverMonitorForm.ExportLog(ALog: IX2LogBase);
var
node: PVirtualNode;
nodeData: PLogEntryNodeData;
begin
for node in vstLog.Nodes do
begin
nodeData := vstLog.GetNodeData(node);
ALog.Log(nodeData^.Level, nodeData^.Time, nodeData^.Message, nodeData^.Details);
end;
end;
procedure TX2LogObserverMonitorForm.vstLogFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); procedure TX2LogObserverMonitorForm.vstLogFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var var
nodeData: PLogEntryNodeData; nodeData: PLogEntryNodeData;
@ -787,6 +848,12 @@ begin
end; end;
procedure TX2LogObserverMonitorForm.actCloseExecute(Sender: TObject);
begin
Close;
end;
procedure TX2LogObserverMonitorForm.actClearExecute(Sender: TObject); procedure TX2LogObserverMonitorForm.actClearExecute(Sender: TObject);
begin begin
vstLog.Clear; vstLog.Clear;
@ -889,6 +956,33 @@ begin
end; end;
procedure TX2LogObserverMonitorForm.actAlwaysOnTopExecute(Sender: TObject);
begin
actAlwaysOnTop.Checked := not actAlwaysOnTop.Checked;
if actAlwaysOnTop.Checked then
SetWindowPos(Self.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOMOVE or SWP_NOACTIVATE)
else
SetWindowPos(Self.Handle, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOMOVE or SWP_NOACTIVATE);
end;
procedure TX2LogObserverMonitorForm.actSaveAsExecute(Sender: TObject);
var
logFile: IX2LogBase;
begin
if sdSaveAs.Execute then
begin
{ Default behaviour of the LogFile observer is to append }
System.SysUtils.DeleteFile(sdSaveAs.FileName);
logFile := TX2LogFileObserver.Create(sdSaveAs.FileName, X2LogLevelsAll);
ExportLog(logFile);
end;
end;
procedure TX2LogObserverMonitorForm.ToolbarCustomDraw(Sender: TToolBar; const ARect: TRect; var DefaultDraw: Boolean); procedure TX2LogObserverMonitorForm.ToolbarCustomDraw(Sender: TToolBar; const ARect: TRect; var DefaultDraw: Boolean);
var var
element: TThemedElementDetails; element: TThemedElementDetails;

View File

@ -216,6 +216,7 @@ begin
header.Version := X2LogMessageVersion; header.Version := X2LogMessageVersion;
header.Size := SizeOf(header); header.Size := SizeOf(header);
header.Level := AEntry.Level; header.Level := AEntry.Level;
header.DateTime := AEntry.DateTime;
WriteBuffer.WriteBuffer(header, SizeOf(header)); WriteBuffer.WriteBuffer(header, SizeOf(header));

View File

@ -25,9 +25,22 @@ begin
SetLogResourceString(@LogMonitorFormButtonPause, 'Pauzeren'); SetLogResourceString(@LogMonitorFormButtonPause, 'Pauzeren');
SetLogResourceString(@LogMonitorFormButtonCopyDetails, 'Kopiëren'); SetLogResourceString(@LogMonitorFormButtonCopyDetails, 'Kopiëren');
SetLogResourceString(@LogMonitorFormButtonSaveDetails, 'Opslaan'); SetLogResourceString(@LogMonitorFormButtonSaveDetails, 'Opslaan');
SetLogResourceString(@LogMonitorFormButtonWordWrapDetails, 'Terugloop');
SetLogResourceString(@LogMonitorFormButtonFilter, 'Filter:');
SetLogResourceString(@LogMonitorFormMenuFile, 'Bestand');
SetLogResourceString(@LogMonitorFormMenuFileSaveAs, 'Opslaan als...');
SetLogResourceString(@LogMonitorFormMenuFileClose, 'Sluiten');
SetLogResourceString(@LogMonitorFormMenuLog, 'Log');
SetLogResourceString(@LogMonitorFormMenuDetails, 'Details');
SetLogResourceString(@LogMonitorFormMenuWindow, 'Venster');
SetLogResourceString(@LogMonitorFormMenuWindowAlwaysOnTop, 'Altijd op voorgrond');
SetLogResourceString(@LogMonitorFormStatusPaused, 'Gepauzeerd: %d melding(en) overgeslagen'); SetLogResourceString(@LogMonitorFormStatusPaused, 'Gepauzeerd: %d melding(en) overgeslagen');
SetLogResourceString(@LogMonitorFormSaveDetailsFilter, 'Alle bestanden (*.*)|*.*'); SetLogResourceString(@LogMonitorFormSaveDetailsFilter, 'Alle bestanden (*.*)|*.*');
SetLogResourceString(@LogMonitorFormSaveDetailsSaveAs, 'Log bestanden (*.log)|*.log|Alle bestanden (*.*)|*.*');
end; end;