Skip to content

Commit 71ca19a

Browse files
authored
Merge pull request #598 from ckadluba/perf-refactor
Refactoring and Performance Optimizations
2 parents 882d2a8 + 919576f commit 71ca19a

33 files changed

+527
-328
lines changed

.editorconfig

+13-1
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,24 @@ csharp_style_var_for_built_in_types = true:warning
5757
csharp_style_var_when_type_is_apparent = true:warning
5858
csharp_using_directive_placement = outside_namespace:warning
5959

60+
# Naming rules for non-public fields
6061
dotnet_naming_rule.private_members_with_underscore.symbols = private_fields
6162
dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore
6263
dotnet_naming_rule.private_members_with_underscore.severity = error
6364
dotnet_naming_symbols.private_fields.applicable_kinds = field
64-
dotnet_naming_symbols.private_fields.applicable_accessibilities = private,protected
65+
dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected
6566
dotnet_naming_style.prefix_underscore.capitalization = camel_case
6667
dotnet_naming_style.prefix_underscore.required_prefix = _
68+
69+
# Naming rules for const or static public fields
70+
dotnet_naming_rule.public_fields_pascal_case.symbols = public_constant_static_fields
71+
dotnet_naming_rule.public_fields_pascal_case.style = pascal_case
72+
dotnet_naming_rule.public_fields_pascal_case.severity = error
73+
dotnet_naming_symbols.public_constant_static_fields.applicable_kinds = field
74+
dotnet_naming_symbols.public_constant_static_fields.applicable_accessibilities = public
75+
dotnet_naming_symbols.public_constant_static_fields.required_modifiers = const, static
76+
dotnet_naming_style.pascal_case.capitalization = pascal_case
77+
6778
dotnet_sort_system_directives_first = true
6879
dotnet_style_require_accessibility_modifiers = always:error
80+

.github/ISSUE_TEMPLATE.md

+5-4
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,18 @@ If you are opening a feature request, you can ignore this template. Bug reports
88
99
>> List the names and versions of all Serilog packages used in the project:
1010
11-
- Serilog:
12-
- Serilog.Sinks.MSSqlServer:
11+
- Serilog:
12+
- Serilog.Sinks.MSSqlServer:
1313
- (configuration, etc.)
1414

1515
>> Target framework and operating system:
1616
17-
[ ] .NET 6
17+
[ ] .NET 9
18+
[ ] .NET 8
1819
[ ] .NET Framework 4.8
1920
[ ] .NET Framework 4.7
2021
[ ] .NET Framework 4.6
21-
OS:
22+
OS:
2223

2324
>> Provide a *simple* reproduction of your Serilog configuration code:
2425

.github/workflows/release.yml

+6
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ jobs:
3939
run: ./RunPerfTests.ps1 -Filter "*QuickBenchmarks*"
4040
shell: pwsh
4141

42+
- name: Upload perf test results artifact
43+
uses: actions/upload-artifact@v4
44+
with:
45+
name: perftestresults
46+
path: artifacts\perftests
47+
4248
- name: Get last commit message
4349
id: last_commit
4450
if: success() && github.ref == 'refs/heads/main'

CHANGES.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# 8.0.1
2+
* Refactoring and performance optimizations in batched and audit sink
3+
* Create perftest result on release
4+
* Updated issue template
5+
* Updated editorconfig
6+
* Added specific documentation about when SQL SELECT permission is not required
7+
18
# 8.0.0
29
* Updated to .NET 8
310
* Updated nearly all dependencies

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,8 @@ CREATE TABLE [Logs] (
200200

201201
At a minimum, writing log entries requires SELECT and INSERT permissions for the log table. (SELECT is required because the sink's batching behavior uses bulk inserts which reads the schema before the write operations begin).
202202

203+
If the audit version of the sink is used or the sink option [UseSqlBulkCopy](#usesqlbulkcopy) is set to `true`, only INSERT statements are used and no SELECT permission is required.
204+
203205
SQL permissions are a very complex subject. Here is an example of one possible solution (valid for SQL 2012 or later):
204206

205207
```

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

-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ namespace Serilog.Sinks.MSSqlServer.Dependencies
44
{
55
internal class SinkDependencies
66
{
7-
public IDataTableCreator DataTableCreator { get; set; }
87
public ISqlCommandExecutor SqlDatabaseCreator { get; set; }
98
public ISqlCommandExecutor SqlTableCreator { get; set; }
109
public ISqlBulkBatchWriter SqlBulkBatchWriter { get; set; }

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

+7-11
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ internal static SinkDependencies Create(
2323
var sqlConnectionStringBuilderWrapper = new SqlConnectionStringBuilderWrapper(
2424
connectionString, sinkOptions.EnlistInTransaction);
2525
var sqlConnectionFactory = new SqlConnectionFactory(sqlConnectionStringBuilderWrapper);
26+
var sqlCommandFactory = new SqlCommandFactory();
2627
var dataTableCreator = new DataTableCreator(sinkOptions.TableName, columnOptions);
2728
var sqlCreateTableWriter = new SqlCreateTableWriter(sinkOptions.SchemaName,
2829
sinkOptions.TableName, columnOptions, dataTableCreator);
@@ -47,21 +48,16 @@ internal static SinkDependencies Create(
4748

4849
var sinkDependencies = new SinkDependencies
4950
{
50-
DataTableCreator = dataTableCreator,
5151
SqlDatabaseCreator = new SqlDatabaseCreator(
52-
sqlCreateDatabaseWriter, sqlConnectionFactoryNoDb),
52+
sqlCreateDatabaseWriter, sqlConnectionFactoryNoDb, sqlCommandFactory),
5353
SqlTableCreator = new SqlTableCreator(
54-
sqlCreateTableWriter, sqlConnectionFactory),
55-
SqlBulkBatchWriter = sinkOptions.UseSqlBulkCopy
56-
? (ISqlBulkBatchWriter)new SqlBulkBatchWriter(
57-
sinkOptions.TableName, sinkOptions.SchemaName, columnOptions.DisableTriggers,
58-
sqlConnectionFactory, logEventDataGenerator)
59-
: (ISqlBulkBatchWriter)new SqlInsertStatementWriter(
60-
sinkOptions.TableName, sinkOptions.SchemaName,
61-
sqlConnectionFactory, logEventDataGenerator),
54+
sqlCreateTableWriter, sqlConnectionFactory, sqlCommandFactory),
55+
SqlBulkBatchWriter = new SqlBulkBatchWriter(
56+
sinkOptions.TableName, sinkOptions.SchemaName, columnOptions.DisableTriggers,
57+
dataTableCreator, sqlConnectionFactory, logEventDataGenerator),
6258
SqlLogEventWriter = new SqlInsertStatementWriter(
6359
sinkOptions.TableName, sinkOptions.SchemaName,
64-
sqlConnectionFactory, logEventDataGenerator)
60+
sqlConnectionFactory, sqlCommandFactory, logEventDataGenerator)
6561
};
6662

6763
return sinkDependencies;

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

+16-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
// Copyright 2024 Serilog Contributors
2-
//
1+
// Copyright 2024 Serilog Contributors
2+
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
55
// You may obtain a copy of the License at
6-
//
6+
//
77
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
8+
//
99
// Unless required by applicable law or agreed to in writing, software
1010
// distributed under the License is distributed on an "AS IS" BASIS,
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -30,11 +30,13 @@ public class MSSqlServerAuditSink : ILogEventSink, IDisposable
3030
{
3131
private readonly ISqlLogEventWriter _sqlLogEventWriter;
3232

33+
private bool _disposedValue;
34+
3335
/// <summary>
3436
/// Construct a sink posting to the specified database.
3537
///
3638
/// Note: this is the legacy version of the extension method. Please use the new one using MSSqlServerSinkOptions instead.
37-
///
39+
///
3840
/// </summary>
3941
/// <param name="connectionString">Connection string to access the database.</param>
4042
/// <param name="tableName">Name of the table to store the data in.</param>
@@ -113,7 +115,15 @@ public void Dispose()
113115
/// <param name="disposing">True to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
114116
protected virtual void Dispose(bool disposing)
115117
{
116-
// This class needn't to dispose anything. This is just here for sink interface compatibility.
118+
if (!_disposedValue)
119+
{
120+
if (disposing)
121+
{
122+
_sqlLogEventWriter.Dispose();
123+
}
124+
125+
_disposedValue = true;
126+
}
117127
}
118128

119129
private static void ValidateParameters(MSSqlServerSinkOptions sinkOptions, ColumnOptions columnOptions)
@@ -134,11 +144,6 @@ private static void CheckSinkDependencies(SinkDependencies sinkDependencies)
134144
throw new ArgumentNullException(nameof(sinkDependencies));
135145
}
136146

137-
if (sinkDependencies.DataTableCreator == null)
138-
{
139-
throw new InvalidOperationException("DataTableCreator is not initialized!");
140-
}
141-
142147
if (sinkDependencies.SqlTableCreator == null)
143148
{
144149
throw new InvalidOperationException("SqlTableCreator is not initialized!");

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSink.cs

+18-15
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
// Copyright 2024 Serilog Contributors
2-
//
1+
// Copyright 2024 Serilog Contributors
2+
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
55
// You may obtain a copy of the License at
6-
//
6+
//
77
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
8+
//
99
// Unless required by applicable law or agreed to in writing, software
1010
// distributed under the License is distributed on an "AS IS" BASIS,
1111
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,7 +14,6 @@
1414

1515
using System;
1616
using System.Collections.Generic;
17-
using System.Data;
1817
using System.Threading.Tasks;
1918
using Serilog.Events;
2019
using Serilog.Formatting;
@@ -29,8 +28,9 @@ namespace Serilog.Sinks.MSSqlServer
2928
/// </summary>
3029
public class MSSqlServerSink : IBatchedLogEventSink, IDisposable
3130
{
31+
private readonly MSSqlServerSinkOptions _sinkOptions;
3232
private readonly ISqlBulkBatchWriter _sqlBulkBatchWriter;
33-
private readonly DataTable _eventTable;
33+
private readonly ISqlLogEventWriter _sqlLogEventWriter; // Used if sink option UseSqlBulkCopy is set to false
3434

3535
/// <summary>
3636
/// The default database schema name.
@@ -53,7 +53,7 @@ public class MSSqlServerSink : IBatchedLogEventSink, IDisposable
5353
/// Construct a sink posting to the specified database.
5454
///
5555
/// Note: this is the legacy version of the extension method. Please use the new one using MSSqlServerSinkOptions instead.
56-
///
56+
///
5757
/// </summary>
5858
/// <param name="connectionString">Connection string to access the database.</param>
5959
/// <param name="tableName">Name of the table to store the data in.</param>
@@ -108,8 +108,9 @@ internal MSSqlServerSink(
108108
ValidateParameters(sinkOptions);
109109
CheckSinkDependencies(sinkDependencies);
110110

111+
_sinkOptions = sinkOptions;
111112
_sqlBulkBatchWriter = sinkDependencies.SqlBulkBatchWriter;
112-
_eventTable = sinkDependencies.DataTableCreator.CreateDataTable();
113+
_sqlLogEventWriter = sinkDependencies.SqlLogEventWriter;
113114

114115
CreateDatabaseAndTable(sinkOptions, sinkDependencies);
115116
}
@@ -119,7 +120,9 @@ internal MSSqlServerSink(
119120
/// </summary>
120121
/// <param name="batch">The events to emit.</param>
121122
public Task EmitBatchAsync(IReadOnlyCollection<LogEvent> batch) =>
122-
_sqlBulkBatchWriter.WriteBatch(batch, _eventTable);
123+
_sinkOptions.UseSqlBulkCopy
124+
? _sqlBulkBatchWriter.WriteBatch(batch)
125+
: _sqlLogEventWriter.WriteEvents(batch);
123126

124127
/// <summary>
125128
/// Called upon batchperiod if no data is in batch. Not used by this sink.
@@ -148,7 +151,7 @@ protected virtual void Dispose(bool disposing)
148151
{
149152
if (disposing)
150153
{
151-
_eventTable.Dispose();
154+
_sqlLogEventWriter.Dispose();
152155
}
153156

154157
_disposedValue = true;
@@ -170,11 +173,6 @@ private static void CheckSinkDependencies(SinkDependencies sinkDependencies)
170173
throw new ArgumentNullException(nameof(sinkDependencies));
171174
}
172175

173-
if (sinkDependencies.DataTableCreator == null)
174-
{
175-
throw new InvalidOperationException("DataTableCreator is not initialized!");
176-
}
177-
178176
if (sinkDependencies.SqlTableCreator == null)
179177
{
180178
throw new InvalidOperationException("SqlTableCreator is not initialized!");
@@ -184,6 +182,11 @@ private static void CheckSinkDependencies(SinkDependencies sinkDependencies)
184182
{
185183
throw new InvalidOperationException("SqlBulkBatchWriter is not initialized!");
186184
}
185+
186+
if (sinkDependencies.SqlLogEventWriter == null)
187+
{
188+
throw new InvalidOperationException("SqlLogEventWriter is not initialized!");
189+
}
187190
}
188191

189192
private void CreateDatabaseAndTable(MSSqlServerSinkOptions sinkOptions, SinkDependencies sinkDependencies)
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
using System.Collections.Generic;
2-
using System.Data;
1+
using System;
2+
using System.Collections.Generic;
33
using System.Threading.Tasks;
44
using Serilog.Events;
55

66
namespace Serilog.Sinks.MSSqlServer.Platform
77
{
8-
internal interface ISqlBulkBatchWriter
8+
internal interface ISqlBulkBatchWriter : IDisposable
99
{
10-
Task WriteBatch(IEnumerable<LogEvent> events, DataTable dataTable);
10+
Task WriteBatch(IEnumerable<LogEvent> events);
1111
}
1212
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using Serilog.Sinks.MSSqlServer.Platform.SqlClient;
2+
3+
namespace Serilog.Sinks.MSSqlServer.Platform
4+
{
5+
internal interface ISqlCommandFactory
6+
{
7+
ISqlCommandWrapper CreateCommand(ISqlConnectionWrapper sqlConnection);
8+
ISqlCommandWrapper CreateCommand(string cmdText, ISqlConnectionWrapper sqlConnection);
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
using Serilog.Events;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Threading.Tasks;
4+
using Serilog.Events;
25

36
namespace Serilog.Sinks.MSSqlServer.Platform
47
{
5-
internal interface ISqlLogEventWriter
8+
internal interface ISqlLogEventWriter : IDisposable
69
{
710
void WriteEvent(LogEvent logEvent);
11+
12+
Task WriteEvents(IEnumerable<LogEvent> events);
813
}
914
}

0 commit comments

Comments
 (0)