2014-05-18 18:09:07 +00:00
|
|
|
unit X2Log.Observer.CustomThreaded;
|
|
|
|
|
|
|
|
interface
|
|
|
|
uses
|
|
|
|
System.Classes,
|
|
|
|
System.Generics.Collections,
|
|
|
|
System.SyncObjs,
|
|
|
|
|
|
|
|
X2Log.Intf,
|
|
|
|
X2Log.Observer.Custom;
|
|
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
TX2LogObserverWorkerThread = class;
|
|
|
|
|
|
|
|
|
|
|
|
TX2LogCustomThreadedObserver = class(TX2LogCustomObserver)
|
|
|
|
private
|
|
|
|
FWorkerThread: TX2LogObserverWorkerThread;
|
|
|
|
protected
|
|
|
|
function CreateWorkerThread: TX2LogObserverWorkerThread; virtual; abstract;
|
|
|
|
|
2014-10-20 12:07:44 +00:00
|
|
|
procedure DoLog(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage, ACategory: string; ADetails: IX2LogDetails); override;
|
2014-05-18 18:09:07 +00:00
|
|
|
|
|
|
|
property WorkerThread: TX2LogObserverWorkerThread read FWorkerThread;
|
|
|
|
public
|
|
|
|
constructor Create(ALogLevels: TX2LogLevels = X2LogLevelsDefault);
|
|
|
|
destructor Destroy; override;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
TX2LogQueueEntry = class(TPersistent)
|
|
|
|
private
|
2014-05-30 12:51:01 +00:00
|
|
|
FDetails: IX2LogDetails;
|
2014-05-18 18:09:07 +00:00
|
|
|
FLevel: TX2LogLevel;
|
2014-10-08 12:33:11 +00:00
|
|
|
FDateTime: TDateTime;
|
2014-10-20 12:07:44 +00:00
|
|
|
FCategory: string;
|
2014-05-18 18:09:07 +00:00
|
|
|
FMessage: string;
|
|
|
|
public
|
2014-10-20 12:07:44 +00:00
|
|
|
constructor Create(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage, ACategory: string; ADetails: IX2LogDetails); overload;
|
2014-05-18 18:09:07 +00:00
|
|
|
constructor Create(AEntry: TX2LogQueueEntry); overload;
|
|
|
|
|
|
|
|
procedure Assign(Source: TPersistent); override;
|
|
|
|
|
2014-10-08 12:33:11 +00:00
|
|
|
property DateTime: TDateTime read FDateTime;
|
2014-05-30 12:51:01 +00:00
|
|
|
property Details: IX2LogDetails read FDetails;
|
2014-05-18 18:09:07 +00:00
|
|
|
property Level: TX2LogLevel read FLevel;
|
2014-10-20 12:07:44 +00:00
|
|
|
property Category: string read FCategory;
|
2014-05-18 18:09:07 +00:00
|
|
|
property Message: string read FMessage;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
TX2LogObserverWorkerThread = class(TThread)
|
|
|
|
private
|
|
|
|
FLogQueue: TObjectQueue<TX2LogQueueEntry>;
|
|
|
|
FLogQueueSignal: TEvent;
|
2014-10-08 12:33:11 +00:00
|
|
|
FThreadStartSignal: TEvent;
|
2014-05-18 18:09:07 +00:00
|
|
|
protected
|
|
|
|
procedure Execute; override;
|
|
|
|
procedure TerminatedSet; override;
|
|
|
|
|
|
|
|
procedure Setup; virtual;
|
|
|
|
procedure Cleanup; virtual;
|
|
|
|
|
|
|
|
procedure WaitForEntry; virtual;
|
|
|
|
procedure ProcessEntry(AEntry: TX2LogQueueEntry); virtual; abstract;
|
|
|
|
|
|
|
|
property LogQueue: TObjectQueue<TX2LogQueueEntry> read FLogQueue;
|
|
|
|
property LogQueueSignal: TEvent read FLogQueueSignal;
|
|
|
|
public
|
|
|
|
constructor Create;
|
|
|
|
destructor Destroy; override;
|
|
|
|
|
2014-10-20 12:07:44 +00:00
|
|
|
procedure Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage, ACategory: string; ADetails: IX2LogDetails);
|
2014-05-18 18:09:07 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
|
|
System.SysUtils;
|
|
|
|
|
|
|
|
|
|
|
|
{ TX2LogCustomThreadedObserver }
|
|
|
|
constructor TX2LogCustomThreadedObserver.Create(ALogLevels: TX2LogLevels);
|
|
|
|
begin
|
|
|
|
inherited Create(ALogLevels);
|
|
|
|
|
|
|
|
FWorkerThread := CreateWorkerThread;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
destructor TX2LogCustomThreadedObserver.Destroy;
|
|
|
|
begin
|
|
|
|
FreeAndNil(FWorkerThread);
|
|
|
|
|
|
|
|
inherited Destroy;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
2014-10-20 12:07:44 +00:00
|
|
|
procedure TX2LogCustomThreadedObserver.DoLog(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage, ACategory: string; ADetails: IX2LogDetails);
|
2014-05-18 18:09:07 +00:00
|
|
|
begin
|
2014-10-20 12:07:44 +00:00
|
|
|
WorkerThread.Log(ALevel, ADateTime, AMessage, ACategory, ADetails);
|
2014-05-18 18:09:07 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{ TX2LogQueueEntry }
|
2014-10-20 12:07:44 +00:00
|
|
|
constructor TX2LogQueueEntry.Create(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage, ACategory: string; ADetails: IX2LogDetails);
|
2014-05-18 18:09:07 +00:00
|
|
|
begin
|
|
|
|
inherited Create;
|
|
|
|
|
|
|
|
FLevel := ALevel;
|
2014-10-08 12:33:11 +00:00
|
|
|
FDateTime := ADateTime;
|
2014-10-20 12:07:44 +00:00
|
|
|
FCategory := ACategory;
|
2014-05-18 18:09:07 +00:00
|
|
|
FMessage := AMessage;
|
|
|
|
FDetails := ADetails;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
constructor TX2LogQueueEntry.Create(AEntry: TX2LogQueueEntry);
|
|
|
|
begin
|
|
|
|
inherited Create;
|
|
|
|
|
|
|
|
Assign(AEntry);
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
procedure TX2LogQueueEntry.Assign(Source: TPersistent);
|
|
|
|
var
|
|
|
|
entrySource: TX2LogQueueEntry;
|
|
|
|
|
|
|
|
begin
|
|
|
|
if Source is TX2LogQueueEntry then
|
|
|
|
begin
|
|
|
|
entrySource := TX2LogQueueEntry(Source);
|
|
|
|
|
|
|
|
FLevel := entrySource.Level;
|
2014-10-08 12:33:11 +00:00
|
|
|
FDateTime := entrySource.DateTime;
|
2014-10-20 12:07:44 +00:00
|
|
|
FCategory := entrySource.Category;
|
2014-05-18 18:09:07 +00:00
|
|
|
FMessage := entrySource.Message;
|
|
|
|
FDetails := entrySource.Details;
|
|
|
|
end else
|
|
|
|
inherited Assign(Source);
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
{ TX2LogObserverWorkerThread }
|
|
|
|
constructor TX2LogObserverWorkerThread.Create;
|
|
|
|
begin
|
2014-10-08 12:33:11 +00:00
|
|
|
FThreadStartSignal := TEvent.Create(nil, True, False, '');
|
2014-10-22 18:24:54 +00:00
|
|
|
FLogQueueSignal := TEvent.Create(nil, True, False, '');
|
2014-05-18 18:09:07 +00:00
|
|
|
FLogQueue := TObjectQueue<TX2LogQueueEntry>.Create(True);
|
|
|
|
|
|
|
|
inherited Create(False);
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
destructor TX2LogObserverWorkerThread.Destroy;
|
|
|
|
begin
|
2014-10-08 12:33:11 +00:00
|
|
|
{ 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);
|
|
|
|
|
2014-05-18 18:09:07 +00:00
|
|
|
inherited Destroy;
|
|
|
|
|
|
|
|
FreeAndNil(FLogQueue);
|
|
|
|
FreeAndNil(FLogQueueSignal);
|
2014-10-08 12:33:11 +00:00
|
|
|
FreeAndNil(FThreadStartSignal);
|
2014-05-18 18:09:07 +00:00
|
|
|
end;
|
|
|
|
|
|
|
|
|
2014-10-20 12:07:44 +00:00
|
|
|
procedure TX2LogObserverWorkerThread.Log(ALevel: TX2LogLevel; ADateTime: TDateTime; const AMessage, ACategory: string; ADetails: IX2LogDetails);
|
2019-10-30 14:01:19 +00:00
|
|
|
var
|
|
|
|
details: IX2LogDetails;
|
|
|
|
|
2014-05-18 18:09:07 +00:00
|
|
|
begin
|
2019-10-30 14:01:19 +00:00
|
|
|
details := nil;
|
|
|
|
if Assigned(ADetails) then
|
|
|
|
details := ADetails.Clone;
|
|
|
|
|
2014-05-18 18:09:07 +00:00
|
|
|
TMonitor.Enter(LogQueue);
|
|
|
|
try
|
2019-10-30 14:01:19 +00:00
|
|
|
LogQueue.Enqueue(TX2LogQueueEntry.Create(ALevel, ADateTime, AMessage, ACategory, details));
|
2014-10-22 18:24:54 +00:00
|
|
|
LogQueueSignal.SetEvent;
|
2014-05-18 18:09:07 +00:00
|
|
|
finally
|
|
|
|
TMonitor.Exit(LogQueue);
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
procedure TX2LogObserverWorkerThread.Execute;
|
|
|
|
var
|
|
|
|
entry: TX2LogQueueEntry;
|
|
|
|
|
|
|
|
begin
|
2014-10-08 12:33:11 +00:00
|
|
|
FThreadStartSignal.SetEvent;
|
2014-05-25 14:20:58 +00:00
|
|
|
NameThreadForDebugging('TX2LogObserverWorkerThread');
|
|
|
|
|
2014-05-18 18:09:07 +00:00
|
|
|
Setup;
|
|
|
|
try
|
2014-10-08 12:33:11 +00:00
|
|
|
while True do
|
2014-05-18 18:09:07 +00:00
|
|
|
begin
|
2014-10-08 12:33:11 +00:00
|
|
|
{ When Terminated, flush the queue }
|
|
|
|
if not Terminated then
|
|
|
|
WaitForEntry;
|
2014-05-18 18:09:07 +00:00
|
|
|
|
|
|
|
entry := nil;
|
|
|
|
TMonitor.Enter(LogQueue);
|
|
|
|
try
|
|
|
|
if LogQueue.Count > 0 then
|
2014-10-22 18:24:54 +00:00
|
|
|
entry := LogQueue.Extract
|
|
|
|
else
|
|
|
|
LogQueueSignal.ResetEvent;
|
2014-05-18 18:09:07 +00:00
|
|
|
finally
|
|
|
|
TMonitor.Exit(LogQueue);
|
|
|
|
end;
|
|
|
|
|
|
|
|
if Assigned(entry) then
|
|
|
|
try
|
|
|
|
ProcessEntry(entry);
|
|
|
|
finally
|
|
|
|
FreeAndNil(entry);
|
2014-10-08 12:33:11 +00:00
|
|
|
end else if Terminated then
|
|
|
|
break;
|
2014-05-18 18:09:07 +00:00
|
|
|
end;
|
|
|
|
finally
|
|
|
|
Cleanup;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
procedure TX2LogObserverWorkerThread.Setup;
|
|
|
|
begin
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
procedure TX2LogObserverWorkerThread.Cleanup;
|
|
|
|
begin
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
procedure TX2LogObserverWorkerThread.WaitForEntry;
|
|
|
|
begin
|
|
|
|
case LogQueueSignal.WaitFor(INFINITE) of
|
|
|
|
wrAbandoned,
|
|
|
|
wrError:
|
|
|
|
Terminate;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
|
|
|
|
|
|
|
|
procedure TX2LogObserverWorkerThread.TerminatedSet;
|
|
|
|
begin
|
|
|
|
LogQueueSignal.SetEvent;
|
|
|
|
|
|
|
|
inherited TerminatedSet;
|
|
|
|
end;
|
|
|
|
|
|
|
|
end.
|