Skip to content

Commit c00d527

Browse files
committed
Extracted class SqlLogEventWriter from MSSqlServerAuditLogSink.
1 parent e719815 commit c00d527

File tree

8 files changed

+124
-92
lines changed

8 files changed

+124
-92
lines changed

src/Serilog.Sinks.MSSqlServer/GlobalSuppressions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Too hard to change. Accepted for now.", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Platform.SqlBulkBatchWriter.WriteBatch(System.Collections.Generic.IEnumerable{Serilog.Events.LogEvent},System.Data.DataTable)~System.Threading.Tasks.Task")]
1212
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Too hard to change. Accepted for now.", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.Output.StandardColumnDataGenerator.ConvertPropertiesToXmlStructure(System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,Serilog.Events.LogEventPropertyValue}})~System.String")]
1313
[assembly: SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Too hard to change. Accepted for now.", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.Output.PropertiesColumnDataGenerator.TryChangeType(System.Object,System.Type,System.Object@)~System.Boolean")]
14-
[assembly: SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Serilog core guarantees to call Emit() with non-null logEvent parameter.", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerAuditSink.Emit(Serilog.Events.LogEvent)")]
15-
[assembly: SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "Serilog core guarantees to call EmitBatchAsync() with non-null logEvent parameter.", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSink.EmitBatchAsync(System.Collections.Generic.IEnumerable{Serilog.Events.LogEvent})~System.Threading.Tasks.Task")]
1614
[assembly: SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "Cannot be changed on public classes for backward compatibility reasons.", Scope = "namespaceanddescendants", Target = "Serilog.Sinks.MSSqlServer")]
1715
[assembly: SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Cannot be changed on public classes for backward compatibility reasons.", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnOptions")]
1816
[assembly: SuppressMessage("Design", "CA1010:Collections should implement generic interface", Justification = "Cannot be changed on public classes for backward compatibility reasons.", Scope = "namespaceanddescendants", Target = "Serilog.Sinks.MSSqlServer")]
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
using Serilog.Sinks.MSSqlServer.Output;
2-
using Serilog.Sinks.MSSqlServer.Platform;
1+
using Serilog.Sinks.MSSqlServer.Platform;
32
using Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Platform;
43

54
namespace Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Dependencies
65
{
76
internal class SinkDependencies
87
{
98
public IDataTableCreator DataTableCreator { get; set; }
10-
public ISqlConnectionFactory SqlConnectionFactory { get; set; }
119
public ISqlTableCreator SqlTableCreator { get; set; }
12-
public ILogEventDataGenerator LogEventDataGenerator { get; set; }
1310
public ISqlBulkBatchWriter SqlBulkBatchWriter { get; set; }
11+
public ISqlLogEventWriter SqlLogEventWriter { get; set; }
1412
}
1513
}

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactory.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,27 @@ internal static SinkDependencies Create(
1919
columnOptions = columnOptions ?? new ColumnOptions();
2020
columnOptions.FinalizeConfigurationForSinkConstructor();
2121

22+
var sqlConnectionFactory =
23+
new SqlConnectionFactory(connectionString,
24+
new AzureManagedServiceAuthenticator(
25+
sinkOptions?.UseAzureManagedIdentity ?? default,
26+
sinkOptions.AzureServiceTokenProviderResource));
27+
var logEventDataGenerator =
28+
new LogEventDataGenerator(columnOptions,
29+
new StandardColumnDataGenerator(columnOptions, formatProvider, logEventFormatter),
30+
new PropertiesColumnDataGenerator(columnOptions));
31+
2232
var sinkDependencies = new SinkDependencies
2333
{
34+
SqlTableCreator = new SqlTableCreator(new SqlCreateTableWriter(), sqlConnectionFactory),
2435
DataTableCreator = new DataTableCreator(),
25-
SqlConnectionFactory =
26-
new SqlConnectionFactory(connectionString,
27-
new AzureManagedServiceAuthenticator(
28-
sinkOptions?.UseAzureManagedIdentity ?? default,
29-
sinkOptions.AzureServiceTokenProviderResource)),
30-
LogEventDataGenerator =
31-
new LogEventDataGenerator(columnOptions,
32-
new StandardColumnDataGenerator(columnOptions, formatProvider, logEventFormatter),
33-
new PropertiesColumnDataGenerator(columnOptions))
36+
SqlBulkBatchWriter = new SqlBulkBatchWriter(
37+
sinkOptions.TableName, sinkOptions.SchemaName,
38+
columnOptions.DisableTriggers, sqlConnectionFactory, logEventDataGenerator),
39+
SqlLogEventWriter = new SqlLogEventWriter(
40+
sinkOptions.TableName, sinkOptions.SchemaName,
41+
sqlConnectionFactory, logEventDataGenerator)
3442
};
35-
sinkDependencies.SqlTableCreator = new SqlTableCreator(
36-
new SqlCreateTableWriter(), sinkDependencies.SqlConnectionFactory);
37-
sinkDependencies.SqlBulkBatchWriter = new SqlBulkBatchWriter(sinkOptions.TableName, sinkOptions.SchemaName,
38-
columnOptions.DisableTriggers, sinkDependencies.SqlConnectionFactory, sinkDependencies.LogEventDataGenerator);
3943

4044
return sinkDependencies;
4145
}

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerAuditSink.cs

Lines changed: 4 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,9 @@
1313
// limitations under the License.
1414

1515
using System;
16-
using System.Data;
17-
using System.Data.SqlClient;
18-
using System.Text;
1916
using Serilog.Core;
20-
using Serilog.Debugging;
2117
using Serilog.Events;
2218
using Serilog.Formatting;
23-
using Serilog.Sinks.MSSqlServer.Output;
2419
using Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Dependencies;
2520
using Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Options;
2621
using Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Platform;
@@ -34,8 +29,7 @@ namespace Serilog.Sinks.MSSqlServer
3429
public class MSSqlServerAuditSink : ILogEventSink, IDisposable
3530
{
3631
private readonly SinkOptions _sinkOptions;
37-
private readonly ISqlConnectionFactory _sqlConnectionFactory;
38-
private readonly ILogEventDataGenerator _logEventDataGenerator;
32+
private readonly ISqlLogEventWriter _sqlLogEventWriter;
3933

4034
/// <summary>
4135
/// Construct a sink posting to the specified database.
@@ -105,8 +99,7 @@ internal MSSqlServerAuditSink(
10599
{
106100
throw new ArgumentNullException(nameof(sinkDependencies));
107101
}
108-
_sqlConnectionFactory = sinkDependencies?.SqlConnectionFactory ?? throw new InvalidOperationException($"SqlConnectionFactory is not initialized!");
109-
_logEventDataGenerator = sinkDependencies?.LogEventDataGenerator ?? throw new InvalidOperationException($"LogEventDataGenerator is not initialized!");
102+
_sqlLogEventWriter = sinkDependencies?.SqlLogEventWriter ?? throw new InvalidOperationException($"SqlLogEventWriter is not initialized!");
110103

111104
if (_sinkOptions.AutoCreateSqlTable)
112105
{
@@ -124,62 +117,8 @@ internal MSSqlServerAuditSink(
124117

125118
/// <summary>Emit the provided log event to the sink.</summary>
126119
/// <param name="logEvent">The log event to write.</param>
127-
public void Emit(LogEvent logEvent)
128-
{
129-
try
130-
{
131-
using (var connection = _sqlConnectionFactory.Create())
132-
{
133-
connection.Open();
134-
using (SqlCommand command = connection.CreateCommand())
135-
{
136-
command.CommandType = CommandType.Text;
137-
138-
var fieldList = new StringBuilder($"INSERT INTO [{_sinkOptions.SchemaName}].[{_sinkOptions.TableName}] (");
139-
var parameterList = new StringBuilder(") VALUES (");
140-
141-
var index = 0;
142-
foreach (var field in _logEventDataGenerator.GetColumnsAndValues(logEvent))
143-
{
144-
if (index != 0)
145-
{
146-
fieldList.Append(',');
147-
parameterList.Append(',');
148-
}
149-
150-
fieldList.Append(field.Key);
151-
parameterList.Append("@P");
152-
parameterList.Append(index);
153-
154-
var parameter = new SqlParameter($"@P{index}", field.Value ?? DBNull.Value);
155-
156-
// The default is SqlDbType.DateTime, which will truncate the DateTime value if the actual
157-
// type in the database table is datetime2. So we explicitly set it to DateTime2, which will
158-
// work both if the field in the table is datetime and datetime2, which is also consistent with
159-
// the behavior of the non-audit sink.
160-
if (field.Value is DateTime)
161-
parameter.SqlDbType = SqlDbType.DateTime2;
162-
163-
command.Parameters.Add(parameter);
164-
165-
index++;
166-
}
167-
168-
parameterList.Append(')');
169-
fieldList.Append(parameterList.ToString());
170-
171-
command.CommandText = fieldList.ToString();
172-
173-
command.ExecuteNonQuery();
174-
}
175-
}
176-
}
177-
catch (Exception ex)
178-
{
179-
SelfLog.WriteLine("Unable to write log event to the database due to following error: {1}", ex.Message);
180-
throw;
181-
}
182-
}
120+
public void Emit(LogEvent logEvent) =>
121+
_sqlLogEventWriter.WriteEvent(logEvent);
183122

184123
/// <summary>
185124
/// Releases the unmanaged resources used by the Serilog.Sinks.MSSqlServer.MSSqlServerAuditSink and optionally
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Serilog.Events;
2+
3+
namespace Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Platform
4+
{
5+
internal interface ISqlLogEventWriter
6+
{
7+
void WriteEvent(LogEvent logEvent);
8+
}
9+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using System;
2+
using System.Data;
3+
using System.Data.SqlClient;
4+
using System.Text;
5+
using Serilog.Debugging;
6+
using Serilog.Events;
7+
using Serilog.Sinks.MSSqlServer.Output;
8+
9+
namespace Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Platform
10+
{
11+
internal class SqlLogEventWriter : ISqlLogEventWriter
12+
{
13+
private readonly string _tableName;
14+
private readonly string _schemaName;
15+
private readonly ISqlConnectionFactory _sqlConnectionFactory;
16+
private readonly ILogEventDataGenerator _logEventDataGenerator;
17+
18+
public SqlLogEventWriter(
19+
string tableName,
20+
string schemaName,
21+
ISqlConnectionFactory sqlConnectionFactory,
22+
ILogEventDataGenerator logEventDataGenerator)
23+
{
24+
_tableName = tableName ?? throw new ArgumentNullException(nameof(tableName));
25+
_schemaName = schemaName ?? throw new ArgumentNullException(nameof(schemaName));
26+
_sqlConnectionFactory = sqlConnectionFactory ?? throw new ArgumentNullException(nameof(sqlConnectionFactory));
27+
_logEventDataGenerator = logEventDataGenerator ?? throw new ArgumentNullException(nameof(logEventDataGenerator));
28+
}
29+
30+
public void WriteEvent(LogEvent logEvent)
31+
{
32+
try
33+
{
34+
using (var connection = _sqlConnectionFactory.Create())
35+
{
36+
connection.Open();
37+
using (SqlCommand command = connection.CreateCommand())
38+
{
39+
command.CommandType = CommandType.Text;
40+
41+
var fieldList = new StringBuilder($"INSERT INTO [{_schemaName}].[{_tableName}] (");
42+
var parameterList = new StringBuilder(") VALUES (");
43+
44+
var index = 0;
45+
foreach (var field in _logEventDataGenerator.GetColumnsAndValues(logEvent))
46+
{
47+
if (index != 0)
48+
{
49+
fieldList.Append(',');
50+
parameterList.Append(',');
51+
}
52+
53+
fieldList.Append(field.Key);
54+
parameterList.Append("@P");
55+
parameterList.Append(index);
56+
57+
var parameter = new SqlParameter($"@P{index}", field.Value ?? DBNull.Value);
58+
59+
// The default is SqlDbType.DateTime, which will truncate the DateTime value if the actual
60+
// type in the database table is datetime2. So we explicitly set it to DateTime2, which will
61+
// work both if the field in the table is datetime and datetime2, which is also consistent with
62+
// the behavior of the non-audit sink.
63+
if (field.Value is DateTime)
64+
parameter.SqlDbType = SqlDbType.DateTime2;
65+
66+
command.Parameters.Add(parameter);
67+
68+
index++;
69+
}
70+
71+
parameterList.Append(')');
72+
fieldList.Append(parameterList.ToString());
73+
74+
command.CommandText = fieldList.ToString();
75+
76+
command.ExecuteNonQuery();
77+
}
78+
}
79+
}
80+
catch (Exception ex)
81+
{
82+
SelfLog.WriteLine("Unable to write log event to the database due to following error: {1}", ex.Message);
83+
throw;
84+
}
85+
}
86+
}
87+
}

test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/MSSqlServerAuditSinkTests.cs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Data;
33
using Moq;
4-
using Serilog.Sinks.MSSqlServer.Output;
54
using Serilog.Sinks.MSSqlServer.Platform;
65
using Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Dependencies;
76
using Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Options;
@@ -16,9 +15,8 @@ public class MSSqlServerAuditSinkTests : IDisposable
1615
{
1716
private readonly SinkDependencies _sinkDependencies;
1817
private readonly Mock<IDataTableCreator> _dataTableCreatorMock;
19-
private readonly Mock<ISqlConnectionFactory> _sqlConnectionFactoryMock;
2018
private readonly Mock<ISqlTableCreator> _sqlTableCreatorMock;
21-
private readonly Mock<ILogEventDataGenerator> _logEventDataGeneratorMock;
19+
private readonly Mock<ISqlLogEventWriter> _sqlLogEventWriter;
2220
private readonly string _tableName = "tableName";
2321
private readonly string _schemaName = "schemaName";
2422
private readonly DataTable _dataTable;
@@ -32,15 +30,14 @@ public MSSqlServerAuditSinkTests()
3230
_dataTableCreatorMock.Setup(d => d.CreateDataTable(It.IsAny<string>(), It.IsAny<Serilog.Sinks.MSSqlServer.ColumnOptions>()))
3331
.Returns(_dataTable);
3432

35-
_sqlConnectionFactoryMock = new Mock<ISqlConnectionFactory>();
3633
_sqlTableCreatorMock = new Mock<ISqlTableCreator>();
37-
_logEventDataGeneratorMock = new Mock<ILogEventDataGenerator>();
34+
_sqlLogEventWriter = new Mock<ISqlLogEventWriter>();
35+
3836
_sinkDependencies = new SinkDependencies
3937
{
4038
DataTableCreator = _dataTableCreatorMock.Object,
41-
SqlConnectionFactory = _sqlConnectionFactoryMock.Object,
4239
SqlTableCreator = _sqlTableCreatorMock.Object,
43-
LogEventDataGenerator = _logEventDataGeneratorMock.Object
40+
SqlLogEventWriter = _sqlLogEventWriter.Object
4441
};
4542
}
4643

test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/MSSqlServerSinkTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using System.Data;
33
using Moq;
4-
using Serilog.Sinks.MSSqlServer.Output;
54
using Serilog.Sinks.MSSqlServer.Platform;
65
using Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Dependencies;
76
using Serilog.Sinks.MSSqlServer.Sinks.MSSqlServer.Options;
@@ -33,6 +32,7 @@ public MSSqlServerSinkTests()
3332

3433
_sqlTableCreatorMock = new Mock<ISqlTableCreator>();
3534
_sqlBulkBatchWriter = new Mock<ISqlBulkBatchWriter>();
35+
3636
_sinkDependencies = new SinkDependencies
3737
{
3838
DataTableCreator = _dataTableCreatorMock.Object,

0 commit comments

Comments
 (0)