Skip to content

Commit 2bf5519

Browse files
authored
Merge pull request #610 from ckadluba/607-exception-auditto-shared-sqlconnection
Fixed concurrency issue #607
2 parents ce7b2ee + e0cb12a commit 2bf5519

File tree

20 files changed

+69
-291
lines changed

20 files changed

+69
-291
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# 8.1.1
2+
* Fixed concurrency issue #607 in audit sink
3+
14
# 8.1.0
25
* Implemented #542: Column option `ResolveHierarchicalPropertyName` to force non-hierarchical handling
36
* Removed unnecessary exception handlers and let Serilog Core do the SelfLog()

serilog-sinks-mssqlserver.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
2828
Directory.Build.props = Directory.Build.props
2929
Directory.Packages.props = Directory.Packages.props
3030
.github\ISSUE_TEMPLATE.md = .github\ISSUE_TEMPLATE.md
31+
.github\workflows\perftests.yml = .github\workflows\perftests.yml
3132
.github\workflows\pr-analysis-codeql.yml = .github\workflows\pr-analysis-codeql.yml
3233
.github\workflows\pr-analysis-devskim.yml = .github\workflows\pr-analysis-devskim.yml
3334
.github\workflows\pr-validation.yml = .github\workflows\pr-validation.yml
3435
README.md = README.md
3536
.github\workflows\release.yml = .github\workflows\release.yml
3637
RunPerfTests.ps1 = RunPerfTests.ps1
37-
.github\workflows\perftests.yml = .github\workflows\perftests.yml
3838
EndProjectSection
3939
EndProject
4040
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetStandardDemoLib", "sample\NetStandardDemo\NetStandardDemoLib\NetStandardDemoLib.csproj", "{8E69E31B-61C7-4175-B886-9C2078FCA477}"

src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/Hybrid/LoggerConfigurationMSSqlServerExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 Serilog Contributors
1+
// Copyright 2025 Serilog Contributors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/Microsoft.Extensions.Configuration/LoggerConfigurationMSSqlServerExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 Serilog Contributors
1+
// Copyright 2025 Serilog Contributors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.

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

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 Serilog Contributors
1+
// Copyright 2025 Serilog Contributors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -30,8 +30,6 @@ public class MSSqlServerAuditSink : ILogEventSink, IDisposable
3030
{
3131
private readonly ISqlLogEventWriter _sqlLogEventWriter;
3232

33-
private bool _disposedValue;
34-
3533
/// <summary>
3634
/// Construct a sink posting to the specified database.
3735
///
@@ -115,15 +113,7 @@ public void Dispose()
115113
/// <param name="disposing">True to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
116114
protected virtual void Dispose(bool disposing)
117115
{
118-
if (!_disposedValue)
119-
{
120-
if (disposing)
121-
{
122-
_sqlLogEventWriter.Dispose();
123-
}
124-
125-
_disposedValue = true;
126-
}
116+
// This class needn't to dispose anything. This is just here for sink interface compatibility.
127117
}
128118

129119
private static void ValidateParameters(MSSqlServerSinkOptions sinkOptions, ColumnOptions columnOptions)

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 Serilog Contributors
1+
// Copyright 2025 Serilog Contributors
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -152,7 +152,6 @@ protected virtual void Dispose(bool disposing)
152152
if (disposing)
153153
{
154154
_sqlBulkBatchWriter.Dispose();
155-
_sqlLogEventWriter.Dispose();
156155
}
157156

158157
_disposedValue = true;
Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,4 @@
1-
// Copyright 2024 Serilog Contributors
2-
//
3-
// Licensed under the Apache License, Version 2.0 (the "License");
4-
// you may not use this file except in compliance with the License.
5-
// You may obtain a copy of the License at
6-
//
7-
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
9-
// Unless required by applicable law or agreed to in writing, software
10-
// distributed under the License is distributed on an "AS IS" BASIS,
11-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12-
// See the License for the specific language governing permissions and
13-
// limitations under the License.
14-
15-
using Serilog.Events;
1+
using Serilog.Events;
162

173
namespace Serilog.Sinks.MSSqlServer.Output
184
{
@@ -21,4 +7,4 @@ internal interface IXmlPropertyFormatter
217
string GetValidElementName(string name);
228
string Simplify(LogEventPropertyValue value, ColumnOptions.PropertiesColumnOptions options);
239
}
24-
}
10+
}

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Output/JsonLogEventFormatter.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,3 @@
1-
// Copyright 2024 Serilog Contributors
2-
//
3-
// Licensed under the Apache License, Version 2.0 (the "License");
4-
// you may not use this file except in compliance with the License.
5-
// You may obtain a copy of the License at
6-
//
7-
// http://www.apache.org/licenses/LICENSE-2.0
8-
//
9-
// Unless required by applicable law or agreed to in writing, software
10-
// distributed under the License is distributed on an "AS IS" BASIS,
11-
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12-
// See the License for the specific language governing permissions and
13-
// limitations under the License.
14-
151
using System;
162
using System.Collections.Generic;
173
using System.Data;

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/ISqlCommandFactory.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ namespace Serilog.Sinks.MSSqlServer.Platform
44
{
55
internal interface ISqlCommandFactory
66
{
7-
ISqlCommandWrapper CreateCommand(ISqlConnectionWrapper sqlConnection);
87
ISqlCommandWrapper CreateCommand(string cmdText, ISqlConnectionWrapper sqlConnection);
98
}
109
}

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/ISqlLogEventWriter.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32
using System.Threading.Tasks;
43
using Serilog.Events;
54

65
namespace Serilog.Sinks.MSSqlServer.Platform
76
{
8-
internal interface ISqlLogEventWriter : IDisposable
7+
internal interface ISqlLogEventWriter
98
{
109
void WriteEvent(LogEvent logEvent);
1110

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlBulkBatchWriter.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Data;
4-
using System.Linq;
54
using System.Threading.Tasks;
6-
using Serilog.Debugging;
75
using Serilog.Events;
86
using Serilog.Sinks.MSSqlServer.Output;
97

@@ -45,10 +43,10 @@ public async Task WriteBatch(IEnumerable<LogEvent> events)
4543
{
4644
FillDataTable(events);
4745

48-
using (var cn = _sqlConnectionFactory.Create())
46+
using (var sqlConnection = _sqlConnectionFactory.Create())
4947
{
50-
await cn.OpenAsync().ConfigureAwait(false);
51-
using (var copy = cn.CreateSqlBulkCopy(_disableTriggers, _schemaAndTableName))
48+
await sqlConnection.OpenAsync().ConfigureAwait(false);
49+
using (var copy = sqlConnection.CreateSqlBulkCopy(_disableTriggers, _schemaAndTableName))
5250
{
5351
for (var i = 0; i < _dataTable.Columns.Count; i++)
5452
{

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/ISqlCommandWrapper.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
11
using System;
2-
using System.Data;
32
using System.Threading.Tasks;
4-
using Microsoft.Data.SqlClient;
53

64
namespace Serilog.Sinks.MSSqlServer.Platform.SqlClient
75
{
86
internal interface ISqlCommandWrapper : IDisposable
97
{
10-
CommandType CommandType { get; set; }
11-
string CommandText { get; set; }
12-
13-
void SetConnection(ISqlConnectionWrapper sqlConnectionWrapper);
14-
void ClearParameters();
158
void AddParameter(string parameterName, object value);
169
int ExecuteNonQuery();
1710
Task<int> ExecuteNonQueryAsync();

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/SqlCommandWrapper.cs

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,9 @@ internal class SqlCommandWrapper : ISqlCommandWrapper
1010
private readonly SqlCommand _sqlCommand;
1111
private bool _disposedValue;
1212

13-
public SqlCommandWrapper(SqlCommand sqlCommand, SqlConnection sqlConnection)
13+
public SqlCommandWrapper(SqlCommand sqlCommand)
1414
{
1515
_sqlCommand = sqlCommand ?? throw new ArgumentNullException(nameof(sqlCommand));
16-
_sqlCommand.Connection = sqlConnection ?? throw new ArgumentNullException(nameof(sqlConnection));
17-
}
18-
19-
public CommandType CommandType
20-
{
21-
get => _sqlCommand.CommandType;
22-
set => _sqlCommand.CommandType = value;
23-
}
24-
25-
public string CommandText
26-
{
27-
get => _sqlCommand.CommandText;
28-
set => _sqlCommand.CommandText = value;
29-
}
30-
31-
public void SetConnection(ISqlConnectionWrapper sqlConnectionWrapper)
32-
{
33-
_sqlCommand.Connection = sqlConnectionWrapper.SqlConnection;
34-
}
35-
36-
public void ClearParameters()
37-
{
38-
_sqlCommand.Parameters.Clear();
3916
}
4017

4118
public void AddParameter(string parameterName, object value)

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlCommandFactory.cs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,10 @@ namespace Serilog.Sinks.MSSqlServer.Platform
55
{
66
internal class SqlCommandFactory : ISqlCommandFactory
77
{
8-
public ISqlCommandWrapper CreateCommand(ISqlConnectionWrapper sqlConnection)
9-
{
10-
var sqlCommand = new SqlCommand();
11-
return new SqlCommandWrapper(sqlCommand, sqlConnection.SqlConnection);
12-
}
13-
148
public ISqlCommandWrapper CreateCommand(string cmdText, ISqlConnectionWrapper sqlConnection)
159
{
16-
var sqlCommand = new SqlCommand(cmdText);
17-
return new SqlCommandWrapper(sqlCommand, sqlConnection.SqlConnection);
10+
var sqlCommand = new SqlCommand(cmdText, sqlConnection.SqlConnection);
11+
return new SqlCommandWrapper(sqlCommand);
1812
}
1913
}
2014
}
Lines changed: 36 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Data;
43
using System.Linq;
54
using System.Text;
65
using System.Threading.Tasks;
7-
using Serilog.Debugging;
86
using Serilog.Events;
97
using Serilog.Sinks.MSSqlServer.Output;
108
using Serilog.Sinks.MSSqlServer.Platform.SqlClient;
@@ -20,8 +18,7 @@ internal class SqlInsertStatementWriter : ISqlLogEventWriter
2018
private readonly ISqlCommandFactory _sqlCommandFactory;
2119
private readonly ILogEventDataGenerator _logEventDataGenerator;
2220

23-
private ISqlCommandWrapper _sqlCommand;
24-
private bool _disposedValue;
21+
private string _sqlCommandText;
2522

2623
public SqlInsertStatementWriter(
2724
string tableName,
@@ -43,92 +40,68 @@ public SqlInsertStatementWriter(
4340

4441
public async Task WriteEvents(IEnumerable<LogEvent> events)
4542
{
46-
using (var cn = _sqlConnectionFactory.Create())
43+
using (var sqlConnection = _sqlConnectionFactory.Create())
4744
{
48-
await cn.OpenAsync().ConfigureAwait(false);
45+
await sqlConnection.OpenAsync().ConfigureAwait(false);
4946

5047
foreach (var logEvent in events)
5148
{
5249
var fields = _logEventDataGenerator.GetColumnsAndValues(logEvent).ToList();
53-
InitializeSqlCommand(cn, fields);
54-
55-
var index = 0;
56-
_sqlCommand.ClearParameters();
57-
foreach (var field in fields)
50+
using (var sqlCommand = InitializeSqlCommand(sqlConnection, fields))
5851
{
59-
_sqlCommand.AddParameter(Invariant($"@P{index}"), field.Value);
60-
index++;
52+
await sqlCommand.ExecuteNonQueryAsync().ConfigureAwait(false);
6153
}
62-
63-
await _sqlCommand.ExecuteNonQueryAsync().ConfigureAwait(false);
6454
}
6555
}
6656
}
6757

68-
/// <summary>
69-
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
70-
/// </summary>
71-
public void Dispose()
58+
private ISqlCommandWrapper InitializeSqlCommand(
59+
ISqlConnectionWrapper sqlConnection,
60+
IEnumerable<KeyValuePair<string, object>> logEventFields)
7261
{
73-
Dispose(disposing: true);
74-
GC.SuppressFinalize(this);
75-
}
62+
InitializeSqlCommandText(logEventFields);
7663

77-
/// <summary>
78-
/// Releases the unmanaged resources used by the Serilog.Sinks.MSSqlServer.Platform.SqlInsertStatementWriter and optionally
79-
/// releases the managed resources.
80-
/// </summary>
81-
/// <param name="disposing">True to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
82-
protected virtual void Dispose(bool disposing)
83-
{
84-
if (!_disposedValue)
64+
var sqlCommand = _sqlCommandFactory.CreateCommand(_sqlCommandText, sqlConnection);
65+
var index = 0;
66+
foreach (var field in logEventFields)
8567
{
86-
if (disposing)
87-
{
88-
_sqlCommand?.Dispose();
89-
}
90-
91-
_disposedValue = true;
68+
sqlCommand.AddParameter(Invariant($"@P{index}"), field.Value);
69+
index++;
9270
}
71+
72+
return sqlCommand;
9373
}
9474

95-
private void InitializeSqlCommand(ISqlConnectionWrapper sqlConnection,
96-
IEnumerable<KeyValuePair<string, object>> logEventFields)
75+
private void InitializeSqlCommandText(IEnumerable<KeyValuePair<string, object>> logEventFields)
9776
{
98-
// Optimization: generate INSERT statement and SqlCommand only once
99-
// and reuse it with different values and SqlConnections because
100-
// the structure does not change.
101-
if (_sqlCommand == null)
77+
if (_sqlCommandText != null)
10278
{
103-
_sqlCommand = _sqlCommandFactory.CreateCommand(sqlConnection);
104-
_sqlCommand.CommandType = CommandType.Text;
79+
return;
80+
}
10581

106-
var fieldList = new StringBuilder(Invariant($"INSERT INTO [{_schemaName}].[{_tableName}] ("));
107-
var parameterList = new StringBuilder(") VALUES (");
82+
var fieldList = new StringBuilder(Invariant($"INSERT INTO [{_schemaName}].[{_tableName}] ("));
83+
var parameterList = new StringBuilder(") VALUES (");
10884

109-
var index = 0;
110-
foreach (var field in logEventFields)
85+
var index = 0;
86+
foreach (var field in logEventFields)
87+
{
88+
if (index != 0)
11189
{
112-
if (index != 0)
113-
{
114-
fieldList.Append(',');
115-
parameterList.Append(',');
116-
}
117-
118-
fieldList.Append(Invariant($"[{field.Key}]"));
119-
parameterList.Append("@P");
120-
parameterList.Append(index);
121-
122-
index++;
90+
fieldList.Append(',');
91+
parameterList.Append(',');
12392
}
12493

125-
parameterList.Append(')');
126-
fieldList.Append(parameterList);
94+
fieldList.Append(Invariant($"[{field.Key}]"));
95+
parameterList.Append("@P");
96+
parameterList.Append(index);
12797

128-
_sqlCommand.CommandText = fieldList.ToString();
98+
index++;
12999
}
130100

131-
_sqlCommand.SetConnection(sqlConnection);
101+
parameterList.Append(')');
102+
fieldList.Append(parameterList);
103+
104+
_sqlCommandText = fieldList.ToString();
132105
}
133106
}
134107
}

0 commit comments

Comments
 (0)