diff --git a/NamedPipeClient/X2LogNamedPipeClient.dpr b/NamedPipeClient/X2LogNamedPipeClient.dpr index 1ba60ff..2734716 100644 --- a/NamedPipeClient/X2LogNamedPipeClient.dpr +++ b/NamedPipeClient/X2LogNamedPipeClient.dpr @@ -11,7 +11,6 @@ uses var client: IX2LogObservable; - observerForm: TX2LogObserverMonitorForm; begin ReportMemoryLeaksOnShutdown := True; @@ -22,8 +21,12 @@ begin client := TX2LogNamedPipeClient.Create('X2LogTest'); try - observerForm := TX2LogObserverMonitorForm.Instance(client); - observerForm.ShowModal; + with TX2LogObserverMonitorForm.Create(nil, client) do + try + ShowModal; + finally + Free; + end; finally client := nil; end; diff --git a/Test/source/MainFrm.pas b/Test/source/MainFrm.pas index 6b88c33..2016f03 100644 --- a/Test/source/MainFrm.pas +++ b/Test/source/MainFrm.pas @@ -87,7 +87,7 @@ type FFileObserver: IX2LogObserver; FNamedPipeObserver: IX2LogObserver; 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; @@ -149,7 +149,7 @@ begin 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 text: string; logDetailsText: IX2LogDetailsText; diff --git a/X2Log.Client.Base.pas b/X2Log.Client.Base.pas index 4fea16e..16c7e40 100644 --- a/X2Log.Client.Base.pas +++ b/X2Log.Client.Base.pas @@ -19,8 +19,8 @@ type destructor Destroy; override; { 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 } procedure Attach(AObserver: IX2LogObserver); @@ -73,5 +73,15 @@ begin observer.Log(ALevel, AMessage, ADetails); 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. diff --git a/X2Log.Client.NamedPipe.pas b/X2Log.Client.NamedPipe.pas index 0ffa7dc..169a89d 100644 --- a/X2Log.Client.NamedPipe.pas +++ b/X2Log.Client.NamedPipe.pas @@ -22,7 +22,7 @@ type end; - TX2LogNamedPipeClient = class(TX2LogBaseClient, IX2LogBase) + TX2LogNamedPipeClient = class(TX2LogBaseClient) private FWorkerThread: TThread; protected @@ -393,7 +393,7 @@ begin end; end; - Client.Log(header.Level, msg, details); + Client.Log(header.Level, header.DateTime, msg, details); except on E:EReadError do ClosePipe; diff --git a/X2Log.Constants.pas b/X2Log.Constants.pas index 0492009..52bd8d1 100644 --- a/X2Log.Constants.pas +++ b/X2Log.Constants.pas @@ -43,15 +43,29 @@ resourcestring LogMonitorFormButtonPause = 'Pause'; LogMonitorFormButtonCopyDetails = 'Copy'; LogMonitorFormButtonSaveDetails = 'Save'; + LogMonitorFormButtonWordWrapDetails = 'Word wrap'; 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 } LogMonitorFormStatusPaused = 'Paused: %d log message(s) skipped'; { Filter for Save details buttons } LogMonitorFormSaveDetailsFilter = 'All files (*.*)|*.*'; + { Filter for Save as menu item } + LogMonitorFormSaveDetailsSaveAs = 'Log files (*.log)|*.log|All files (*.*)|*.*'; + function GetLogLevelText(ALogLevel: TX2LogLevel): string; diff --git a/X2Log.Global.pas b/X2Log.Global.pas index 1356d01..d1098ad 100644 --- a/X2Log.Global.pas +++ b/X2Log.Global.pas @@ -24,7 +24,8 @@ type class procedure SetExceptionStrategy(AStrategy: IX2LogExceptionStrategy); { 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 VerboseEx(const AMessage: string; ADetails: IX2LogDetails = nil); @@ -87,6 +88,12 @@ begin 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); begin Instance.Verbose(AMessage, ADetails); diff --git a/X2Log.Intf.pas b/X2Log.Intf.pas index 6dc19d2..e6e0ad3 100644 --- a/X2Log.Intf.pas +++ b/X2Log.Intf.pas @@ -49,6 +49,7 @@ type IX2LogBase = interface ['{1949E8DC-6DC5-43DC-B678-55CF8274E79D}'] 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; @@ -95,6 +96,7 @@ type ID: Word; Version: Byte; Size: Word; + DateTime: TDateTime; Level: TX2LogLevel; { diff --git a/X2Log.Observer.Custom.pas b/X2Log.Observer.Custom.pas index acc54e3..d2a8b1c 100644 --- a/X2Log.Observer.Custom.pas +++ b/X2Log.Observer.Custom.pas @@ -15,14 +15,15 @@ type private FLogLevels: TX2LogLevels; 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; public constructor Create(ALogLevels: TX2LogLevels = X2LogLevelsDefault); { 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; @@ -39,9 +40,15 @@ end; 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 if ALevel in LogLevels then - DoLog(ALevel, AMessage, ADetails); + DoLog(ALevel, ADateTime, AMessage, ADetails); end; end. diff --git a/X2Log.Observer.CustomThreaded.pas b/X2Log.Observer.CustomThreaded.pas index 3118c53..2dd83ff 100644 --- a/X2Log.Observer.CustomThreaded.pas +++ b/X2Log.Observer.CustomThreaded.pas @@ -20,7 +20,7 @@ type protected 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; public @@ -33,13 +33,15 @@ type private FDetails: IX2LogDetails; FLevel: TX2LogLevel; + FDateTime: TDateTime; FMessage: string; 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; procedure Assign(Source: TPersistent); override; + property DateTime: TDateTime read FDateTime; property Details: IX2LogDetails read FDetails; property Level: TX2LogLevel read FLevel; property Message: string read FMessage; @@ -51,6 +53,7 @@ type FFileName: string; FLogQueue: TObjectQueue; FLogQueueSignal: TEvent; + FThreadStartSignal: TEvent; protected procedure Execute; override; procedure TerminatedSet; override; @@ -68,7 +71,7 @@ type constructor Create; destructor Destroy; override; - procedure Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); + procedure Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); end; @@ -94,19 +97,20 @@ begin end; -procedure TX2LogCustomThreadedObserver.DoLog(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); +procedure TX2LogCustomThreadedObserver.DoLog(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); begin - WorkerThread.Log(ALevel, AMessage, ADetails); + WorkerThread.Log(ALevel, ADateTime, AMessage, ADetails); end; { TX2LogQueueEntry } -constructor TX2LogQueueEntry.Create(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); +constructor TX2LogQueueEntry.Create(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); begin inherited Create; FLevel := ALevel; + FDateTime := ADateTime; FMessage := AMessage; FDetails := ADetails; end; @@ -130,6 +134,7 @@ begin entrySource := TX2LogQueueEntry(Source); FLevel := entrySource.Level; + FDateTime := entrySource.DateTime; FMessage := entrySource.Message; FDetails := entrySource.Details; end else @@ -140,6 +145,7 @@ end; { TX2LogObserverWorkerThread } constructor TX2LogObserverWorkerThread.Create; begin + FThreadStartSignal := TEvent.Create(nil, True, False, ''); FLogQueueSignal := TEvent.Create(nil, False, False, ''); FLogQueue := TObjectQueue.Create(True); @@ -149,18 +155,24 @@ end; destructor TX2LogObserverWorkerThread.Destroy; 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; FreeAndNil(FLogQueue); FreeAndNil(FLogQueueSignal); + FreeAndNil(FThreadStartSignal); end; -procedure TX2LogObserverWorkerThread.Log(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); +procedure TX2LogObserverWorkerThread.Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); begin TMonitor.Enter(LogQueue); try - LogQueue.Enqueue(TX2LogQueueEntry.Create(ALevel, AMessage, ADetails)); + LogQueue.Enqueue(TX2LogQueueEntry.Create(ALevel, ADateTime, AMessage, ADetails)); finally TMonitor.Exit(LogQueue); end; @@ -174,16 +186,16 @@ var entry: TX2LogQueueEntry; begin + FThreadStartSignal.SetEvent; NameThreadForDebugging('TX2LogObserverWorkerThread'); Setup; try - while not Terminated do + while True do begin - WaitForEntry; - - if Terminated then - break; + { When Terminated, flush the queue } + if not Terminated then + WaitForEntry; entry := nil; TMonitor.Enter(LogQueue); @@ -199,7 +211,8 @@ begin ProcessEntry(entry); finally FreeAndNil(entry); - end; + end else if Terminated then + break; end; finally Cleanup; diff --git a/X2Log.Observer.Event.pas b/X2Log.Observer.Event.pas index 9d2e744..b41635f 100644 --- a/X2Log.Observer.Event.pas +++ b/X2Log.Observer.Event.pas @@ -7,7 +7,7 @@ uses 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) @@ -15,7 +15,7 @@ type FOnLog: TX2LogEvent; FRunInMainThread: Boolean; protected - procedure DoLog(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); override; + procedure DoLog(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); override; public constructor Create(ALogLevels: TX2LogLevels = X2LogLevelsDefault); overload; constructor Create(AOnLog: TX2LogEvent; ALogLevels: TX2LogLevels = X2LogLevelsDefault); overload; @@ -48,7 +48,7 @@ begin end; -procedure TX2LogEventObserver.DoLog(ALevel: TX2LogLevel; const AMessage: string; ADetails: IX2LogDetails); +procedure TX2LogEventObserver.DoLog(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage: string; ADetails: IX2LogDetails); begin if Assigned(FOnLog) then begin @@ -58,10 +58,10 @@ begin procedure begin if Assigned(FOnLog) then - FOnLog(Self, ALevel, AMessage, ADetails); + FOnLog(Self, ALevel, ADateTime, AMessage, ADetails); end); end else - FOnLog(Self, ALevel, AMessage, ADetails); + FOnLog(Self, ALevel, ADateTime, AMessage, ADetails); end; end; diff --git a/X2Log.Observer.LogFile.pas b/X2Log.Observer.LogFile.pas index b605e67..fa4634a 100644 --- a/X2Log.Observer.LogFile.pas +++ b/X2Log.Observer.LogFile.pas @@ -122,7 +122,7 @@ begin if Supports(AEntry.Details, IX2LogDetailsStreamable, logDetailsStreamable) then begin detailsExtension := ExtractFileExt(FileName); - baseReportFileName := ChangeFileExt(FileName, '_' + FormatDateTime(GetLogResourceString(@LogFileNameDateFormat), Now)); + baseReportFileName := ChangeFileExt(FileName, '_' + FormatDateTime(GetLogResourceString(@LogFileNameDateFormat), AEntry.DateTime)); detailsFileName := baseReportFileName + detailsExtension; detailsNumber := 0; @@ -170,7 +170,7 @@ begin { Append line to log file } writer := TFile.AppendText(FileName); try - writer.WriteLine('[' + FormatDateTime(GetLogResourceString(@LogFileLineDateFormat), Now) + '] ' + + writer.WriteLine('[' + FormatDateTime(GetLogResourceString(@LogFileLineDateFormat), AEntry.DateTime) + '] ' + GetLogLevelText(AEntry.Level) + ': ' + errorMsg); finally FreeAndNil(writer); diff --git a/X2Log.Observer.MonitorForm.dfm b/X2Log.Observer.MonitorForm.dfm index 04d5ab0..e198f86 100644 --- a/X2Log.Observer.MonitorForm.dfm +++ b/X2Log.Observer.MonitorForm.dfm @@ -10,6 +10,7 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm Font.Height = -11 Font.Name = 'Tahoma' Font.Style = [] + Menu = mmMain OldCreateOrder = False Position = poScreenCenter ShowHint = True @@ -181,7 +182,7 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm end item Position = 2 - Width = 424 + Width = 428 WideText = 'Message' end> end @@ -198,24 +199,24 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm ShowCaptions = True TabOrder = 1 OnCustomDraw = ToolbarCustomDraw - object tbClear: TToolButton - Left = 0 - Top = 0 - Action = actClear - AutoSize = True - end object tbPause: TToolButton - Left = 56 + Left = 0 Top = 0 Action = actPause AutoSize = True Style = tbsCheck end + object tbClear: TToolButton + Left = 60 + Top = 0 + Action = actClear + AutoSize = True + end object lblFilter: TLabel Left = 116 Top = 0 Width = 46 - Height = 22 + Height = 13 Caption = ' Filter: ' Layout = tlCenter end @@ -266,7 +267,7 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm Left = 448 Top = 48 Bitmap = { - 494C01010A004000D00010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 + 494C01010A004000EC0010001000FFFFFFFFFF10FFFFFFFFFFFFFFFF424D3600 0000000000003600000028000000400000003000000001002000000000000030 0000000000000000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000000000000000 @@ -722,15 +723,97 @@ object X2LogObserverMonitorForm: TX2LogObserverMonitorForm end object actWordWrap: TAction AutoCheck = True - Caption = '&Word wrap' + Caption = 'Word wrap' Enabled = False ImageIndex = 9 OnExecute = actWordWrapExecute 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 object sdDetails: TSaveDialog Options = [ofOverwritePrompt, ofHideReadOnly, ofEnableSizing] Left = 512 Top = 112 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 diff --git a/X2Log.Observer.MonitorForm.pas b/X2Log.Observer.MonitorForm.pas index 8507211..3e6c343 100644 --- a/X2Log.Observer.MonitorForm.pas +++ b/X2Log.Observer.MonitorForm.pas @@ -18,7 +18,7 @@ uses Winapi.Messages, X2Log.Details.Intf, - X2Log.Intf; + X2Log.Intf, Vcl.Menus; const @@ -70,6 +70,30 @@ type tbWordWrap: TToolButton; tbDetailsSep1: TToolButton; 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 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 vstLogGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer); procedure vstLogFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex); + procedure actCloseExecute(Sender: TObject); procedure actClearExecute(Sender: TObject); procedure actCopyDetailsExecute(Sender: TObject); procedure actSaveDetailsExecute(Sender: TObject); @@ -88,6 +113,8 @@ type procedure actShowWarningExecute(Sender: TObject); procedure actShowErrorExecute(Sender: TObject); procedure actWordWrapExecute(Sender: TObject); + procedure actAlwaysOnTopExecute(Sender: TObject); + procedure actSaveAsExecute(Sender: TObject); private class var FInstances: TMonitorFormDictionary; private @@ -128,6 +155,8 @@ type procedure SetVisibleDetails(AControl: TControl); procedure SetWordWrap(AValue: Boolean); + procedure ExportLog(ALog: IX2LogBase); + property Closed: Boolean read FClosed; property Details: IX2LogDetails read FDetails; property LockCount: Integer read FLockCount; @@ -154,7 +183,8 @@ type procedure Unlock; { 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 MaxEntries: Cardinal read FMaxEntries write FMaxEntries; @@ -170,7 +200,8 @@ uses Vcl.Themes, Winapi.Windows, - X2Log.Constants; + X2Log.Constants, + X2Log.Observer.LogFile; {$R *.dfm} @@ -184,7 +215,7 @@ type Message: string; 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; PLogEntryNodeData = ^TLogEntryNodeData; @@ -199,9 +230,9 @@ const { 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 - Self.Time := Now; + Self.Time := ADateTime; Self.Paused := APaused; Self.Level := ALevel; Self.Message := AMessage; @@ -316,12 +347,21 @@ begin vstLog.Header.Columns[ColumnTime].Text := GetLogResourceString(@LogMonitorFormColumnTime); 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); actPause.Caption := GetLogResourceString(@LogMonitorFormButtonPause); actCopyDetails.Caption := GetLogResourceString(@LogMonitorFormButtonCopyDetails); actSaveDetails.Caption := GetLogResourceString(@LogMonitorFormButtonSaveDetails); + actAlwaysOnTop.Caption := GetLogResourceString(@LogMonitorFormMenuWindowAlwaysOnTop); sdDetails.Filter := GetLogResourceString(@LogMonitorFormSaveDetailsFilter); + sdSaveAs.Filter := GetLogResourceString(@LogMonitorFormSaveDetailsSaveAs); lblFilter.Caption := ' ' + GetLogResourceString(@LogMonitorFormButtonFilter) + ' '; actShowVerbose.Caption := GetLogLevelText(TX2LogLevel.Verbose); @@ -417,6 +457,12 @@ end; 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 node: PVirtualNode; nodeData: PLogEntryNodeData; @@ -440,7 +486,7 @@ begin { BeginUpdate causes OnInitNode to be triggered on-demand, moved Initialize call here } Initialize(nodeData^); - nodeData^.Initialize(Paused, ALevel, AMessage, ADetails); + nodeData^.Initialize(Paused, ALevel, ADateTime, AMessage, ADetails); vstLog.IsVisible[node] := (not Paused) and (ALevel in VisibleLevels); @@ -484,6 +530,7 @@ end; procedure TX2LogObserverMonitorForm.UpdateUI; begin actClear.Enabled := (vstLog.RootNodeCount > 0); + actSaveAs.Enabled := (vstLog.RootNodeCount > 0); end; @@ -706,6 +753,20 @@ begin 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); var nodeData: PLogEntryNodeData; @@ -787,6 +848,12 @@ begin end; +procedure TX2LogObserverMonitorForm.actCloseExecute(Sender: TObject); +begin + Close; +end; + + procedure TX2LogObserverMonitorForm.actClearExecute(Sender: TObject); begin vstLog.Clear; @@ -889,6 +956,33 @@ begin 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); var element: TThemedElementDetails; diff --git a/X2Log.Observer.NamedPipe.pas b/X2Log.Observer.NamedPipe.pas index 0f76ca9..8ba5308 100644 --- a/X2Log.Observer.NamedPipe.pas +++ b/X2Log.Observer.NamedPipe.pas @@ -216,6 +216,7 @@ begin header.Version := X2LogMessageVersion; header.Size := SizeOf(header); header.Level := AEntry.Level; + header.DateTime := AEntry.DateTime; WriteBuffer.WriteBuffer(header, SizeOf(header)); diff --git a/X2Log.Translations.Dutch.pas b/X2Log.Translations.Dutch.pas index 29b1eab..e6b7575 100644 --- a/X2Log.Translations.Dutch.pas +++ b/X2Log.Translations.Dutch.pas @@ -25,9 +25,22 @@ begin SetLogResourceString(@LogMonitorFormButtonPause, 'Pauzeren'); SetLogResourceString(@LogMonitorFormButtonCopyDetails, 'Kopiëren'); 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(@LogMonitorFormSaveDetailsFilter, 'Alle bestanden (*.*)|*.*'); + SetLogResourceString(@LogMonitorFormSaveDetailsSaveAs, 'Log bestanden (*.log)|*.log|Alle bestanden (*.*)|*.*'); end;