Skip to content

Commit 48fdea3

Browse files
committed
Merge branch 'dev'
2 parents c9c6bb0 + f0a0511 commit 48fdea3

15 files changed

+945
-467
lines changed

.editorconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
root = true
2+
3+
[*]
4+
trim_trailing_whitespace = true
5+
insert_final_newline = true
6+
indent_style = space
7+
indent_size = 4
8+
9+
[*.{csproj,json,config,yml}]
10+
indent_size = 2
11+
12+
[*.sh]
13+
end_of_line = lf
14+
15+
[*.{cmd, bat}]
16+
end_of_line = crlf

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
5.2
2+
* Support for Audit sink added (#118/#110).
3+
14
4.0.0
25
* Serilog 2.0
36
* [Documentation fix](https://github.com/serilog/serilog-sinks-mssqlserver/pull/32)

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ A Serilog sink that writes events to Microsoft SQL Server. While a NoSql store a
55
**Package** - [Serilog.Sinks.MSSqlServer](http://nuget.org/packages/serilog.sinks.mssqlserver)
66
| **Platforms** - .NET 4.5 and .NET Standard 2.0
77

8+
From version 5.2 and up, this sink also support the Audit capabilities.
9+
810
## Configuration
911

1012
At minimum a connection string and table name are required.

src/Serilog.Sinks.MSSqlServer/LoggerConfigurationMSSqlServerExtensions.cs

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,8 @@ public static LoggerConfiguration MSSqlServer(
6464

6565
var defaultedPeriod = period ?? MSSqlServerSink.DefaultPeriod;
6666

67-
MSSqlServerConfigurationSection serviceConfigSection =
68-
ConfigurationManager.GetSection("MSSqlServerSettingsSection") as MSSqlServerConfigurationSection;
69-
7067
// If we have additional columns from config, load them as well
71-
if (serviceConfigSection != null && serviceConfigSection.Columns.Count > 0)
68+
if (ConfigurationManager.GetSection("MSSqlServerSettingsSection") is MSSqlServerConfigurationSection serviceConfigSection && serviceConfigSection.Columns.Count > 0)
7269
{
7370
if (columnOptions == null)
7471
{
@@ -93,6 +90,57 @@ public static LoggerConfiguration MSSqlServer(
9390
restrictedToMinimumLevel);
9491
}
9592

93+
/// <summary>
94+
/// Adds a sink that writes log events to a table in a MSSqlServer database.
95+
/// Create a database and execute the table creation script found here
96+
/// https://gist.github.com/mivano/10429656
97+
/// or use the autoCreateSqlTable option.
98+
/// </summary>
99+
/// <param name="loggerAuditSinkConfiguration">The logger configuration.</param>
100+
/// <param name="connectionString">The connection string to the database where to store the events.</param>
101+
/// <param name="tableName">Name of the table to store the events in.</param>
102+
/// <param name="schemaName">Name of the schema for the table to store the data in. The default is 'dbo'.</param>
103+
/// <param name="restrictedToMinimumLevel">The minimum log event level required in order to write an event to the sink.</param>
104+
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
105+
/// <param name="autoCreateSqlTable">Create log table with the provided name on destination sql server.</param>
106+
/// <param name="columnOptions"></param>
107+
/// <returns>Logger configuration, allowing configuration to continue.</returns>
108+
/// <exception cref="ArgumentNullException">A required parameter is null.</exception>
109+
public static LoggerConfiguration MSSqlServer(this LoggerAuditSinkConfiguration loggerAuditSinkConfiguration,
110+
string connectionString,
111+
string tableName,
112+
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
113+
IFormatProvider formatProvider = null,
114+
bool autoCreateSqlTable = false,
115+
ColumnOptions columnOptions = null,
116+
string schemaName = "dbo")
117+
{
118+
if (loggerAuditSinkConfiguration == null) throw new ArgumentNullException("loggerAuditSinkConfiguration");
119+
120+
// If we have additional columns from config, load them as well
121+
if (ConfigurationManager.GetSection("MSSqlServerSettingsSection") is MSSqlServerConfigurationSection serviceConfigSection && serviceConfigSection.Columns.Count > 0)
122+
{
123+
if (columnOptions == null)
124+
{
125+
columnOptions = new ColumnOptions();
126+
}
127+
GenerateDataColumnsFromConfig(serviceConfigSection, columnOptions);
128+
}
129+
130+
connectionString = GetConnectionString(connectionString);
131+
132+
return loggerAuditSinkConfiguration.Sink(
133+
new MSSqlServerAuditSink(
134+
connectionString,
135+
tableName,
136+
formatProvider,
137+
autoCreateSqlTable,
138+
columnOptions,
139+
schemaName
140+
),
141+
restrictedToMinimumLevel);
142+
}
143+
96144
/// <summary>
97145
/// Examine if supplied connection string is a reference to an item in the "ConnectionStrings" section of web.config
98146
/// If it is, return the ConnectionStrings item, if not, return string as supplied.
@@ -101,7 +149,7 @@ public static LoggerConfiguration MSSqlServer(
101149
/// <remarks>Pulled from review of Entity Framework 6 methodology for doing the same</remarks>
102150
private static string GetConnectionString(string nameOrConnectionString)
103151
{
104-
152+
105153
// If there is an `=`, we assume this is a raw connection string not a named value
106154
// If there are no `=`, attempt to pull the named value from config
107155
if (nameOrConnectionString.IndexOf('=') < 0)
@@ -140,48 +188,48 @@ private static void GenerateDataColumnsFromConfig(MSSqlServerConfigurationSectio
140188
switch (c.DataType)
141189
{
142190
case "bigint":
143-
dataType = Type.GetType("System.Int64");
191+
dataType = typeof(long);
144192
break;
145193
case "bit":
146-
dataType = Type.GetType("System.Boolean");
194+
dataType = typeof(bool);
147195
break;
148196
case "char":
149197
case "nchar":
150198
case "ntext":
151199
case "nvarchar":
152200
case "text":
153201
case "varchar":
154-
dataType = Type.GetType("System.String");
202+
dataType = typeof(string);
155203
break;
156204
case "date":
157205
case "datetime":
158206
case "datetime2":
159207
case "smalldatetime":
160-
dataType = Type.GetType("System.DateTime");
208+
dataType = typeof(DateTime);
161209
break;
162210
case "decimal":
163211
case "money":
164212
case "numeric":
165213
case "smallmoney":
166-
dataType = Type.GetType("System.Decimal");
214+
dataType = typeof(Decimal);
167215
break;
168216
case "float":
169-
dataType = Type.GetType("System.Double");
217+
dataType = typeof(double);
170218
break;
171219
case "int":
172-
dataType = Type.GetType("System.Int32");
220+
dataType = typeof(int);
173221
break;
174222
case "real":
175-
dataType = Type.GetType("System.Single");
223+
dataType = typeof(float);
176224
break;
177225
case "smallint":
178-
dataType = Type.GetType("System.Int16");
226+
dataType = typeof(short);
179227
break;
180228
case "time":
181-
dataType = Type.GetType("System.TimeSpan");
229+
dataType = typeof(TimeSpan);
182230
break;
183231
case "uniqueidentifier":
184-
dataType = Type.GetType("System.Guid");
232+
dataType = typeof(Guid);
185233
break;
186234
}
187235
column.DataType = dataType;

src/Serilog.Sinks.MSSqlServer/Serilog.Sinks.MSSqlServer.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Description>A Serilog sink that writes events to Microsoft SQL Server</Description>
5-
<VersionPrefix>5.1.1</VersionPrefix>
5+
<VersionPrefix>5.1.2</VersionPrefix>
66
<Authors>Michiel van Oudheusden;Serilog Contributors</Authors>
77
<TargetFrameworks>netstandard2.0;net45;net452</TargetFrameworks>
88
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Copyright 2018 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.Core;
16+
using Serilog.Debugging;
17+
using Serilog.Events;
18+
using System;
19+
using System.Data;
20+
using System.Data.SqlClient;
21+
using System.Linq;
22+
using System.Text;
23+
24+
namespace Serilog.Sinks.MSSqlServer
25+
{
26+
/// <summary>
27+
/// Writes log events as rows in a table of MSSqlServer database using Audit logic, meaning that each row is synchronously committed
28+
/// and any errors that occur are propagated to the caller.
29+
/// </summary>
30+
public class MSSqlServerAuditSink : ILogEventSink, IDisposable
31+
{
32+
private readonly MSSqlServerSinkTraits _traits;
33+
34+
/// <summary>
35+
/// Construct a sink posting to the specified database.
36+
/// </summary>
37+
/// <param name="connectionString">Connection string to access the database.</param>
38+
/// <param name="tableName">Name of the table to store the data in.</param>
39+
/// <param name="schemaName">Name of the schema for the table to store the data in. The default is 'dbo'.</param>
40+
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
41+
/// <param name="autoCreateSqlTable">Create log table with the provided name on destination sql server.</param>
42+
/// <param name="columnOptions">Options that pertain to columns</param>
43+
public MSSqlServerAuditSink(
44+
string connectionString,
45+
string tableName,
46+
IFormatProvider formatProvider,
47+
bool autoCreateSqlTable = false,
48+
ColumnOptions columnOptions = null,
49+
string schemaName = "dbo"
50+
)
51+
{
52+
if (columnOptions != null)
53+
{
54+
if (columnOptions.DisableTriggers)
55+
throw new NotSupportedException($"The {nameof(ColumnOptions.DisableTriggers)} option is not supported for auditing.");
56+
}
57+
58+
_traits = new MSSqlServerSinkTraits(connectionString, tableName, schemaName, columnOptions, formatProvider, autoCreateSqlTable);
59+
60+
}
61+
62+
/// <summary>Emit the provided log event to the sink.</summary>
63+
/// <param name="logEvent">The log event to write.</param>
64+
public void Emit(LogEvent logEvent)
65+
{
66+
try
67+
{
68+
using (SqlConnection connection = new SqlConnection(_traits.ConnectionString))
69+
{
70+
connection.Open();
71+
using (SqlCommand command = connection.CreateCommand())
72+
{
73+
command.CommandType = CommandType.Text;
74+
75+
StringBuilder fieldList = new StringBuilder($"INSERT INTO [{_traits.SchemaName}].[{_traits.TableName}] (");
76+
StringBuilder parameterList = new StringBuilder(") VALUES (");
77+
78+
int index = 0;
79+
foreach (var field in _traits.GetColumnsAndValues(logEvent))
80+
{
81+
if (index != 0)
82+
{
83+
fieldList.Append(',');
84+
parameterList.Append(',');
85+
}
86+
87+
fieldList.Append(field.Key);
88+
parameterList.Append("@P");
89+
parameterList.Append(index);
90+
91+
SqlParameter parameter = new SqlParameter($"@P{index}", field.Value ?? DBNull.Value);
92+
93+
// The default is SqlDbType.DateTime, which will truncate the DateTime value if the actual
94+
// type in the database table is datetime2. So we explicitly set it to DateTime2, which will
95+
// work both if the field in the table is datetime and datetime2, which is also consistent with
96+
// the behavior of the non-audit sink.
97+
if (field.Value is DateTime)
98+
parameter.SqlDbType = SqlDbType.DateTime2;
99+
100+
command.Parameters.Add(parameter);
101+
102+
index++;
103+
}
104+
105+
parameterList.Append(')');
106+
fieldList.Append(parameterList.ToString());
107+
108+
command.CommandText = fieldList.ToString();
109+
110+
command.ExecuteNonQuery();
111+
}
112+
}
113+
}
114+
catch (Exception ex)
115+
{
116+
SelfLog.WriteLine("Unable to write log event to the database due to following error: {1}", ex.Message);
117+
throw;
118+
}
119+
}
120+
121+
/// <summary>
122+
/// Releases the unmanaged resources used by the Serilog.Sinks.MSSqlServer.MSSqlServerAuditSink and optionally
123+
/// releases the managed resources.
124+
/// </summary>
125+
/// <param name="disposing">True to release both managed and unmanaged resources; false to release only unmanaged
126+
/// resources.</param>
127+
protected virtual void Dispose(bool disposing)
128+
{
129+
if (disposing)
130+
{
131+
_traits.Dispose();
132+
}
133+
}
134+
135+
/// <summary>
136+
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
137+
/// </summary>
138+
public void Dispose()
139+
{
140+
Dispose(true);
141+
GC.SuppressFinalize(this);
142+
}
143+
}
144+
}

0 commit comments

Comments
 (0)