-
Notifications
You must be signed in to change notification settings - Fork 91
/
LoggerPro.DBAppender.pas
142 lines (121 loc) · 4.27 KB
/
LoggerPro.DBAppender.pas
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
unit LoggerPro.DBAppender;
// Abstract LoggerPro Appender that writes to a database
// Subclass to use with DB connection stack of choice e.g. ADO, FireDAC etc
// only supports parameterised stored procedures for security and performance
interface
uses
System.Classes, LoggerPro, System.SysUtils, Data.DB;
type
TOnDBWriteError = reference to procedure(const Sender: TObject; const LogItem: TLogItem; const DBError: Exception;
var RetryCount: Integer);
TGetDBConnection = reference to function: TCustomConnection;
TGetStoredProc<T: class> = reference to function(Connection: TCustomConnection): T;
TSetParams<T: class> = reference to procedure(DataObject: T; LogItem: TLogItem);
///<summary>Abstract class for writing logs to database</summary>
/// <remarks>Subclass with your choice of stored procedure class to get a working logger </remarks>
TLoggerProDBAppender<T: class> = class(TLoggerProAppenderBase)
protected
const MAX_RETRY_COUNT = 5;
protected
FOnDBWriteError: TOnDBWriteError;
FGetDBConnection: TGetDBConnection;
FGetStoredProc: TGetStoredProc<T>;
FSetParams: TSetParams<T>;
FDBConnection: TCustomConnection;
FDBObject: T;
procedure RefreshParams(DataObj: T); virtual; abstract;
procedure ExecuteDataObject(DataObj: T); virtual; abstract;
public
constructor Create(GetDBConnection: TGetDBConnection; GetStoredProc: TGetStoredProc<T>; SetParams: TSetParams<T>;
OnDBWriteError: TOnDBWriteError); reintroduce;
procedure Setup; override;
procedure TearDown; override;
procedure TryToRestart(var Restarted: Boolean); override;
procedure WriteLog(const ALogItem: TLogItem); override;
end;
implementation
{ TLoggerProDBAppender }
///<summary>Create an instance of the DB logger</summary>
/// <param name="GetDBConnection">anonymous function that returns a configured DB Connection</param>
/// <param name="GetStoredProc">anonymous function that returns a stored proc that can write to the DB</param>
/// <param name="SetParams">anonymous procedure that populates parameters before the stored proc is executed</param>
/// <param name="OnDBWriteError">anonymous procedure to handle retry of a failed operaation</param>
constructor TLoggerProDBAppender<T>.Create(GetDBConnection: TGetDBConnection; GetStoredProc: TGetStoredProc<T>;
SetParams: TSetParams<T>; OnDBWriteError: TOnDBWriteError);
begin
inherited Create;
FGetDBConnection := GetDBConnection;
FGetStoredProc := GetStoredProc;
FSetParams := SetParams;
FOnDBWriteError := OnDBWriteError;
end;
procedure TLoggerProDBAppender<T>.Setup;
begin
inherited;
FDBConnection := FGetDBConnection;
end;
procedure TLoggerProDBAppender<T>.TearDown;
begin
inherited;
if FDBObject <> nil then
FDBObject.Free;
if FDBConnection <> nil then
begin
FDBConnection.Connected := False;
FDBConnection.Free;
end;
end;
procedure TLoggerProDBAppender<T>.TryToRestart(var Restarted: Boolean);
begin
try
// remove the DB Object
if FDBObject <> nil then
begin
FDBObject.Free;
FDBObject := nil;
end;
// reset the DB connection
if FDBConnection <> nil then
begin
FDBConnection.Connected := False;
FDBConnection.Free;
FDBConnection := nil;
end;
except
// no point catching the exception
end;
// now try to restart it
FDBConnection := FGetDBConnection;
Restarted := True;
end;
procedure TLoggerProDBAppender<T>.WriteLog(const ALogItem: TLogItem);
var
RetryCount: Integer;
begin
RetryCount := 0;
repeat
try
if FDBObject = nil then
begin
FDBConnection.Connected := True; //force an exception if needed
FDBObject := FGetStoredProc(FDBConnection);
RefreshParams(FDBObject); //this may not raise unhandled exception even in case of disconnection
end;
FSetParams(FDBObject, ALogItem);
ExecuteDataObject(FDBObject);
Break;
except
on E: Exception do
begin
// if there is an event handler for DB exception, call it
if Assigned(FOnDBWriteError) then
FOnDBWriteError(Self, ALogItem, E, RetryCount);
Inc(RetryCount);
// if the handler has set FRetryCount to a positive value then retry the call
if RetryCount >= MAX_RETRY_COUNT then
raise;
end;
end;
until False;
end;
end.