Skip to content

Commit ca1461b

Browse files
committed
Merge remote-tracking branch 'upstream/dev' into remove-duplicate-code-config-ext
2 parents 1aa2d93 + cceb27f commit ca1461b

File tree

12 files changed

+272
-41
lines changed

12 files changed

+272
-41
lines changed

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ This is a `Collection<>` of `SqlColumn` objects that you create to define custom
320320
Each Standard Column in the `ColumnOptions.Store` list and any custom columns you add to the `AdditionalColumns` collection are `SqlColumn` objects with the following properties:
321321

322322
* `ColumnName`
323+
* `PropertyName`
323324
* `DataType`
324325
* `AllowNull`
325326
* `DataLength`
@@ -329,6 +330,10 @@ Each Standard Column in the `ColumnOptions.Store` list and any custom columns yo
329330

330331
Any valid SQL column name can be used. Standard Columns have default names assigned but these can be changed without affecting their special handling.
331332

333+
### PropertyName
334+
335+
The optional name of a Serilog property to use as the value for a custom column. If not provided, the property used is the one that has the same name as the specified ColumnName. It applies only to custom columns defined in `AdditionalColumns` and is ignored for standard columns.
336+
332337
### DataType
333338

334339
This property can be set to nearly any value in the `System.Data.SqlDbType` enumeration. Unlike previous versions of this sink, SQL column types are fully supported end-to-end, including auto-table-creation. Earlier limitations imposed by the use of the .NET `DataColumn` object no longer apply. Most of the Standard Columns only support a limited subset of the SQL column types (and often just one type). Some of the special-case SQL column types are excluded such as `timestamp` and `udt`, and deprecated types like `text` and `image` are excluded. These are the supported SQL column data types:
@@ -482,7 +487,7 @@ var columnOptions = new ColumnOptions
482487
AdditionalColumns = new Collection<SqlColumn>
483488
{
484489
new SqlColumn
485-
{ColumnName = "UserName", DataType = SqlDbType.NVarChar, DataLength = 64},
490+
{ColumnName = "EnvironmentUserName", PropertyName = "UserName", DataType = SqlDbType.NVarChar, DataLength = 64},
486491

487492
new SqlColumn
488493
{ColumnName = "UserId", DataType = SqlDbType.BigInt, NonClusteredIndex = true},
@@ -499,7 +504,7 @@ var log = new LoggerConfiguration()
499504
.CreateLogger();
500505
```
501506

502-
In this example, when a log event contains any of the properties `UserName`, `UserId`, and `RequestUri`, the property values would be written to the corresponding columns. The property names must match exactly (case-insensitive).
507+
In this example, when a log event contains any of the properties `UserName`, `UserId`, and `RequestUri`, the property values would be written to the corresponding columns. The property names must match exactly (case-insensitive). In the case of `UserName`, that value would be written to the column named `EnvironmentUserName`.
503508

504509
Unlike previous versions of the sink, Standard Column names are not reserved. If you remove the `Id` Standard Column from the `ColumnOptions.Store` list, you are free to create a new custom column called `Id` which the sink will treat like any other custom column fully under your control.
505510

@@ -568,6 +573,7 @@ As the name suggests, `columnOptionSection` is an entire configuration section i
568573
"additionalColumns": [
569574
{ "ColumnName": "EventType", "DataType": "int", "AllowNull": false },
570575
{ "ColumnName": "Release", "DataType": "varchar", "DataLength": 32 },
576+
{ "ColumnName": "EnvironmentUserName", "PropertyName": "UserName", "DataType": "varchar", "DataLength": 50 },
571577
{ "ColumnName": "All_SqlColumn_Defaults",
572578
"DataType": "varchar",
573579
"AllowNull": true,
@@ -632,6 +638,10 @@ Keys and values are case-sensitive. Case must match **_exactly_** as shown below
632638
</RemoveStandardColumns>
633639
<Columns>
634640
<add ColumnName="EventType" DataType="int"/>
641+
<add ColumnName="EnvironmentUserName"
642+
PropertyName="UserName"
643+
DataType="varchar"
644+
DataLength="50" />
635645
<add ColumnName="Release"
636646
DataType="varchar"
637647
DataLength="64"

sample/WorkerServiceDemo/Worker.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
2121

2222
while (!stoppingToken.IsCancellationRequested)
2323
{
24-
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
24+
_logger.LogInformation("Worker running at: {time}. CustomProperty1: {CustomProperty1}",
25+
DateTimeOffset.Now, "Value");
2526
await Task.Delay(1000, stoppingToken);
2627
}
2728

sample/WorkerServiceDemo/appsettings.json

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"tableName": "LogEvents",
1919
"autoCreateSqlTable": true,
2020
"useAzureManagedIdentity": false,
21-
"azureServiceTokenProviderResource": "TestAzureServiceTokenProviderResource"
21+
"azureServiceTokenProviderResource": "https://database.windpws.net/"
2222
},
2323
"restrictedToMinimumLevel": "Information",
2424
"columnOptionsSection": {
@@ -27,7 +27,14 @@
2727
"timeStamp": {
2828
"columnName": "Timestamp",
2929
"convertToUtc": false
30-
}
30+
},
31+
"additionalColumns": [
32+
{
33+
"columnName": "AdditionalColumn1",
34+
"propertyName": "CustomProperty1",
35+
"dataType": "12"
36+
}
37+
]
3138
},
3239
"logEventFormatter": "WorkerServiceDemo.CustomLogEventFormatter::Formatter, WorkerServiceDemo"
3340
}
@@ -38,21 +45,28 @@
3845
"Name": "MSSqlServer",
3946
"Args": {
4047
"connectionString": "Server=localhost;Database=LogTest;Integrated Security=SSPI;",
41-
"tableName": "LogEventsAudit",
48+
"restrictedToMinimumLevel": "Information",
49+
"logEventFormatter": "WorkerServiceDemo.CustomLogEventFormatter::Formatter, WorkerServiceDemo",
50+
"sinkOptionsSection": {
51+
"tableName": "LogEventsAudit",
52+
"autoCreateSqlTable": true,
53+
"useAzureManagedIdentity": false,
54+
"azureServiceTokenProviderResource": "https://database.windpws.net/"
55+
},
4256
"columnOptionsSection": {
4357
"addStandardColumns": [ "LogEvent" ],
4458
"removeStandardColumns": [ "MessageTemplate", "Properties" ],
4559
"timeStamp": {
4660
"columnName": "Timestamp",
4761
"convertToUtc": false
48-
}
49-
},
50-
"autoCreateSqlTable": true,
51-
"restrictedToMinimumLevel": "Information",
52-
"logEventFormatter": "WorkerServiceDemo.CustomLogEventFormatter::Formatter, WorkerServiceDemo",
53-
"sinkOptionsSection": {
54-
"useAzureManagedIdentity": false,
55-
"azureServiceTokenProviderResource": "TestAuditAzureServiceTokenProviderResource"
62+
},
63+
"additionalColumns": [
64+
{
65+
"columnName": "AdditionalColumn1",
66+
"propertyName": "CustomProperty1",
67+
"dataType": "12"
68+
}
69+
]
5670
}
5771
}
5872
}

src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ColumnConfig.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2015 Serilog Contributors
1+
// Copyright 2015 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.
@@ -40,6 +40,13 @@ public virtual string ColumnName
4040
set { this[nameof(ColumnName)] = value; }
4141
}
4242

43+
[ConfigurationProperty("PropertyName")]
44+
public virtual string PropertyName
45+
{
46+
get { return (string)this[nameof(PropertyName)]; }
47+
set { this[nameof(PropertyName)] = value; }
48+
}
49+
4350
[ConfigurationProperty("DataType")]
4451
public string DataType
4552
{
@@ -75,6 +82,8 @@ internal SqlColumn AsSqlColumn()
7582
// inheritors can override IsRequired; config might not change the names of Standard Columns
7683
SetProperty.IfProvidedNotEmpty<string>(this, nameof(ColumnName), (val) => sqlColumn.ColumnName = val);
7784

85+
SetProperty.IfProvidedNotEmpty<string>(this, nameof(PropertyName), (val) => sqlColumn.PropertyName = val);
86+
7887
SetProperty.IfProvidedNotEmpty<string>(this, nameof(DataType), (val) => sqlColumn.SetDataTypeFromConfigString(val));
7988

8089
SetProperty.IfProvided<int>(this, nameof(DataLength), (val) => sqlColumn.DataLength = val);

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSinkTraits.cs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020 Serilog Contributors
1+
// Copyright 2020 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.
@@ -37,7 +37,7 @@ internal class MSSqlServerSinkTraits : IDisposable
3737
public ColumnOptions ColumnOptions { get; }
3838
public IFormatProvider FormatProvider { get; }
3939
public ITextFormatter LogEventFormatter { get; }
40-
public ISet<string> AdditionalColumnNames { get; }
40+
public ISet<string> AdditionalColumnPropertyNames { get; }
4141
public DataTable EventTable { get; }
4242
public ISet<string> StandardColumnNames { get; }
4343

@@ -82,10 +82,10 @@ internal MSSqlServerSinkTraits(
8282
StandardColumnNames.Add(col.ColumnName);
8383
}
8484

85-
AdditionalColumnNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
85+
AdditionalColumnPropertyNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
8686
if (ColumnOptions.AdditionalColumns != null)
8787
foreach (var col in ColumnOptions.AdditionalColumns)
88-
AdditionalColumnNames.Add(col.ColumnName);
88+
AdditionalColumnPropertyNames.Add(col.PropertyName);
8989

9090
if (ColumnOptions.Store.Contains(StandardColumn.LogEvent))
9191
LogEventFormatter = logEventFormatter ?? new JsonLogEventFormatter(this);
@@ -163,7 +163,7 @@ private string RenderLogEventColumn(LogEvent logEvent)
163163
{
164164
if (ColumnOptions.LogEvent.ExcludeAdditionalProperties)
165165
{
166-
var filteredProperties = logEvent.Properties.Where(p => !AdditionalColumnNames.Contains(p.Key));
166+
var filteredProperties = logEvent.Properties.Where(p => !AdditionalColumnPropertyNames.Contains(p.Key));
167167
logEvent = new LogEvent(logEvent.Timestamp, logEvent.Level, logEvent.Exception, logEvent.MessageTemplate, filteredProperties.Select(x => new LogEventProperty(x.Key, x.Value)));
168168
}
169169

@@ -178,7 +178,7 @@ private string ConvertPropertiesToXmlStructure(IEnumerable<KeyValuePair<string,
178178
var options = ColumnOptions.Properties;
179179

180180
if (options.ExcludeAdditionalProperties)
181-
properties = properties.Where(p => !AdditionalColumnNames.Contains(p.Key));
181+
properties = properties.Where(p => !AdditionalColumnPropertyNames.Contains(p.Key));
182182

183183
if (options.PropertiesFilter != null)
184184
{
@@ -223,16 +223,20 @@ private string ConvertPropertiesToXmlStructure(IEnumerable<KeyValuePair<string,
223223
/// Mapping values from properties which have a corresponding data row.
224224
/// Matching is done based on Column name and property key
225225
/// Standard columns are not mapped
226-
/// </summary>
226+
/// </summary>
227227
/// <param name="properties"></param>
228228
private IEnumerable<KeyValuePair<string, object>> ConvertPropertiesToColumn(IReadOnlyDictionary<string, LogEventPropertyValue> properties)
229229
{
230230
foreach (var property in properties)
231231
{
232-
if (!EventTable.Columns.Contains(property.Key) || StandardColumnNames.Contains(property.Key))
232+
var additionalColumn = ColumnOptions
233+
.AdditionalColumns
234+
.FirstOrDefault(ac => ac.PropertyName == property.Key);
235+
236+
if (additionalColumn == null || StandardColumnNames.Contains(property.Key))
233237
continue;
234238

235-
var columnName = property.Key;
239+
var columnName = additionalColumn.ColumnName;
236240
var columnType = EventTable.Columns[columnName].DataType;
237241

238242
if (!(property.Value is ScalarValue scalarValue))

src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlColumn.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public class SqlColumn
1010
{
1111
private SqlDbType _dataType = SqlDbType.VarChar; // backwards-compatibility default
1212
private string _columnName = string.Empty;
13+
private string _propertyName;
1314

1415
/// <summary>
1516
/// Default constructor.
@@ -95,6 +96,16 @@ public SqlDbType DataType
9596
/// </summary>
9697
public bool NonClusteredIndex { get; set; } = false;
9798

99+
/// <summary>
100+
/// The name of the Serilog property to use as the value when filling the DataTable.
101+
/// If not specified, the ColumnName and PropertyName are the same.
102+
/// </summary>
103+
public string PropertyName
104+
{
105+
get => _propertyName ?? ColumnName;
106+
set => _propertyName = value;
107+
}
108+
98109
// Set by the constructors of the Standard Column classes that inherit from this;
99110
// allows Standard Columns and user-defined columns to coexist but remain identifiable
100111
// and allows casting back to the Standard Column without a lot of switch gymnastics.
@@ -128,7 +139,7 @@ internal virtual DataColumn AsDataColumn()
128139
}
129140

130141
/// <summary>
131-
/// Configuration accepts DataType as a simple string ("nvarchar" for example) for ease-of-use.
142+
/// Configuration accepts DataType as a simple string ("nvarchar" for example) for ease-of-use.
132143
/// This converts to SqlDbType and stores it into the DataType property.
133144
/// </summary>
134145
internal void SetDataTypeFromConfigString(string requestedSqlType)

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

Lines changed: 89 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public AdditionalPropertiesTests(ITestOutputHelper output) : base(output)
1515
}
1616

1717
[Fact]
18-
public void CreatesTableWithTwoAdditionalProperties()
18+
public void WritesLogEventWithColumnNamedProperties()
1919
{
2020
// Arrange
2121
const string additionalColumnName1 = "AdditionalColumn1";
@@ -24,24 +24,102 @@ public void CreatesTableWithTwoAdditionalProperties()
2424
{
2525
AdditionalColumns = new List<SqlColumn>
2626
{
27-
new SqlColumn(additionalColumnName1, SqlDbType.NVarChar, true, dataLength: 100),
28-
new SqlColumn(additionalColumnName2, SqlDbType.Int, true)
27+
new SqlColumn
28+
{
29+
ColumnName = additionalColumnName1,
30+
DataType = SqlDbType.NVarChar,
31+
AllowNull = true,
32+
DataLength = 100
33+
},
34+
new SqlColumn
35+
{
36+
ColumnName = additionalColumnName2,
37+
DataType = SqlDbType.Int,
38+
AllowNull = true
39+
}
2940
}
3041
};
3142

43+
var messageTemplate = $"Hello {{{additionalColumnName1}}} from thread {{{additionalColumnName2}}}";
44+
var property1Value = "PropertyValue1";
45+
var property2Value = 2;
46+
var expectedMessage = $"Hello \"{property1Value}\" from thread {property2Value}";
47+
3248
// Act
33-
using (var sink = new MSSqlServerSink(DatabaseFixture.LogEventsConnectionString,
34-
sinkOptions: new SinkOptions
49+
Log.Logger = new LoggerConfiguration()
50+
.WriteTo.MSSqlServer(
51+
DatabaseFixture.LogEventsConnectionString,
52+
sinkOptions: new SinkOptions
53+
{
54+
TableName = DatabaseFixture.LogTableName,
55+
AutoCreateSqlTable = true
56+
},
57+
columnOptions: columnOptions)
58+
.CreateLogger();
59+
Log.Information(messageTemplate, property1Value, property2Value);
60+
Log.CloseAndFlush();
61+
62+
// Assert
63+
VerifyDatabaseColumnsWereCreated(columnOptions.AdditionalColumns);
64+
VerifyLogMessageWasWritten(expectedMessage);
65+
VerifyStringColumnWritten(additionalColumnName1, property1Value);
66+
VerifyIntegerColumnWritten(additionalColumnName2, property2Value);
67+
}
68+
69+
[Fact]
70+
public void WritesLogEventWithCustomNamedProperties()
71+
{
72+
// Arrange
73+
const string additionalColumn1Name = "AdditionalColumn1";
74+
const string additionalProperty1Name = "AdditionalProperty1";
75+
const string additionalColumn2Name = "AdditionalColumn2";
76+
const string additionalProperty2Name = "AdditionalProperty2";
77+
var columnOptions = new ColumnOptions
78+
{
79+
AdditionalColumns = new List<SqlColumn>
3580
{
36-
TableName = DatabaseFixture.LogTableName,
37-
AutoCreateSqlTable = true
38-
},
39-
columnOptions: columnOptions,
40-
formatProvider: null))
41-
{ }
81+
new SqlColumn
82+
{
83+
ColumnName = additionalColumn1Name,
84+
PropertyName = additionalProperty1Name,
85+
DataType = SqlDbType.NVarChar,
86+
AllowNull = true,
87+
DataLength = 100
88+
},
89+
new SqlColumn
90+
{
91+
ColumnName = additionalColumn2Name,
92+
PropertyName = additionalProperty2Name,
93+
DataType = SqlDbType.Int,
94+
AllowNull = true
95+
}
96+
}
97+
};
98+
99+
var messageTemplate = $"Hello {{{additionalProperty1Name}}} from thread {{{additionalProperty2Name}}}";
100+
var property1Value = "PropertyValue1";
101+
var property2Value = 2;
102+
var expectedMessage = $"Hello \"{property1Value}\" from thread {property2Value}";
103+
104+
// Act
105+
Log.Logger = new LoggerConfiguration()
106+
.WriteTo.MSSqlServer(
107+
DatabaseFixture.LogEventsConnectionString,
108+
sinkOptions: new SinkOptions
109+
{
110+
TableName = DatabaseFixture.LogTableName,
111+
AutoCreateSqlTable = true
112+
},
113+
columnOptions: columnOptions)
114+
.CreateLogger();
115+
Log.Information(messageTemplate, property1Value, property2Value);
116+
Log.CloseAndFlush();
42117

43118
// Assert
44119
VerifyDatabaseColumnsWereCreated(columnOptions.AdditionalColumns);
120+
VerifyLogMessageWasWritten(expectedMessage);
121+
VerifyStringColumnWritten(additionalColumn1Name, property1Value);
122+
VerifyIntegerColumnWritten(additionalColumn2Name, property2Value);
45123
}
46124
}
47125
}

0 commit comments

Comments
 (0)