Skip to content

Commit 1ad392b

Browse files
authored
Merge pull request #599 from ckadluba/issue-542-disable-hierarchical-props
Implemented #542: Column option ResolveHierarchicalPropertyName to force non-hierarchical handling
2 parents 71ca19a + 458b867 commit 1ad392b

File tree

12 files changed

+243
-25
lines changed

12 files changed

+243
-25
lines changed

Build.ps1

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,17 @@ try
7171
{
7272
Push-Location "$testProjectPath"
7373

74-
echo "build: Testing project in $testProjectPath"
75-
& dotnet test -c Release --collect "XPlat Code Coverage"
76-
if ($LASTEXITCODE -ne 0)
74+
# Run tests for different targets in sequence to avoid database tests
75+
# to fail because of concurrency problems
76+
foreach ($tfm in @( "net462", "net472", "net8.0" ))
7777
{
78-
exit 2
78+
echo "build: Testing project in $testProjectPath for target $tfm"
79+
& dotnet test -c Release --collect "XPlat Code Coverage" --framework "$tfm" --results-directory "./TestResults/$tfm"
80+
if ($LASTEXITCODE -ne 0)
81+
{
82+
exit 2
83+
}
7984
}
80-
8185
}
8286
finally
8387
{

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# 8.1.0
2+
* Implemented #542: Column option `ResolveHierarchicalPropertyName` to force non-hierarchical handling
3+
14
# 8.0.1
25
* Refactoring and performance optimizations in batched and audit sink
36
* Create perftest result on release

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ Each Standard Column in the `ColumnOptions.Store` list and any custom columns yo
339339

340340
* `ColumnName`
341341
* `PropertyName`
342+
* `ResolveHierarchicalPropertyName`
342343
* `DataType`
343344
* `AllowNull`
344345
* `DataLength`
@@ -352,7 +353,11 @@ Any valid SQL column name can be used. Standard Columns have default names assig
352353

353354
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.
354355

355-
PropertyName can contain a simple property name like `SomeProperty` but it can also be used to hierachically reference sub-properties with expressions like `SomeProperty.SomeSubProperty.SomeThirdLevelProperty`. This can be used to easily bind additional columns to specific sub-properties following the paradigm of structured logging. Please be aware that collections are not supported. This means expressions like `SomeProperty.SomeArray[2]` will not work.
356+
PropertyName can contain a simple property name like `SomeProperty` but it can also be used to hierarchically reference sub-properties with expressions like `SomeProperty.SomeSubProperty.SomeThirdLevelProperty`. This can be used to easily bind additional columns to specific sub-properties following the paradigm of structured logging. Please be aware that collections are not supported. This means expressions like `SomeProperty.SomeArray[2]` will not work. Hierarchical property resolution can be disabled using `ResolveHierarchicalPropertyName` in case you need property names containing dots which should not be treated as hierarchical.
357+
358+
### ResolveHierarchicalPropertyName
359+
360+
Controls whether hierarchical sub-property expressions in `PropertyName` are evaluated (see above). The default is `true`. If set to `false` any value is treated as a simple property name and no hierarchical sub-property binding is done.
356361

357362
### DataType
358363

@@ -385,7 +390,7 @@ Numeric types use the default precision and scale. For numeric types, you are re
385390

386391
### AllowNull
387392

388-
Determines whether or not the column can store SQL `NULL` values. The default is true. Some of the other features like `PrimaryKey` have related restrictions, and some of the Standard Columns impose restrictions (for example, the `Id` column never allows nulls).
393+
Determines whether the column can store SQL `NULL` values. The default is `true`. Some of the other features like `PrimaryKey` have related restrictions, and some of the Standard Columns impose restrictions (for example, the `Id` column never allows nulls).
389394

390395
### DataLength
391396

sample/WorkerServiceDemo/Worker.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
2727
};
2828
_logger.LogInformation("{@Structured} {@Scalar}", structured, "Scalar Value");
2929

30+
31+
// Logging a property with dots in its name to AdditionalColumn3
32+
// but treat it as unstructured according to configuration in AdditionalColumns in appsettings.json
33+
_logger.LogInformation("Non-structured property with dot-name to AdditionalColumn3 {@NonstructuredProperty.WithNameContainingDots.Name}",
34+
new Random().Next().ToString());
35+
3036
while (!stoppingToken.IsCancellationRequested)
3137
{
3238
_logger.LogInformation("Worker running at: {time}. CustomProperty1: {CustomProperty1}",

sample/WorkerServiceDemo/appsettings.json

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"columnName": "Timestamp",
2828
"convertToUtc": false
2929
},
30-
"additionalColumns": [
30+
"customColumns": [
3131
{
3232
"columnName": "AdditionalColumn1",
3333
"propertyName": "CustomProperty1",
@@ -37,6 +37,12 @@
3737
"columnName": "AdditionalColumn2",
3838
"propertyName": "Structured.Name",
3939
"dataType": "12"
40+
},
41+
{
42+
"columnName": "AdditionalColumn3",
43+
"propertyName": "NonstructuredProperty.WithNameContainingDots.Name",
44+
"resolveHierarchicalPropertyName": false,
45+
"dataType": "12"
4046
}
4147
]
4248
},
@@ -63,11 +69,22 @@
6369
"columnName": "Timestamp",
6470
"convertToUtc": false
6571
},
66-
"additionalColumns": [
72+
"customColumns": [
6773
{
6874
"columnName": "AdditionalColumn1",
6975
"propertyName": "CustomProperty1",
7076
"dataType": "12"
77+
},
78+
{
79+
"columnName": "AdditionalColumn2",
80+
"propertyName": "Structured.Name",
81+
"dataType": "12"
82+
},
83+
{
84+
"columnName": "AdditionalColumn3",
85+
"propertyName": "NonstructuredProperty.WithNameContainingDots.Name",
86+
"resolveHierarchicalPropertyName": false,
87+
"dataType": "12"
7188
}
7289
]
7390
}

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Copyright 2015 Serilog Contributors
2-
//
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,6 +30,12 @@ protected override object GetElementKey(ConfigurationElement element)
3030
{
3131
return ((ColumnConfig)element)?.ColumnName;
3232
}
33+
34+
// For testing
35+
internal void Add(ColumnConfig config)
36+
{
37+
BaseAdd(config);
38+
}
3339
}
3440
}
3541

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Copyright 2015 Serilog Contributors
2-
//
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.
@@ -48,6 +48,13 @@ public virtual string PropertyName
4848
set { this[nameof(PropertyName)] = value; }
4949
}
5050

51+
[ConfigurationProperty("ResolveHierarchicalPropertyName")]
52+
public virtual string ResolveHierarchicalPropertyName
53+
{
54+
get { return (string)this[nameof(ResolveHierarchicalPropertyName)]; }
55+
set { this[nameof(ResolveHierarchicalPropertyName)] = value; }
56+
}
57+
5158
[ConfigurationProperty("DataType")]
5259
public string DataType
5360
{
@@ -85,6 +92,8 @@ internal SqlColumn AsSqlColumn()
8592

8693
SetProperty.IfProvidedNotEmpty<string>(this, nameof(PropertyName), (val) => sqlColumn.PropertyName = val);
8794

95+
SetProperty.IfProvided<bool>(this, nameof(ResolveHierarchicalPropertyName), (val) => sqlColumn.ResolveHierarchicalPropertyName = val);
96+
8897
SetProperty.IfProvidedNotEmpty<string>(this, nameof(DataType), (val) => sqlColumn.SetDataTypeFromConfigString(val));
8998

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

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 and Azure SQL</Description>
5-
<VersionPrefix>8.0.1</VersionPrefix>
5+
<VersionPrefix>8.1.0</VersionPrefix>
66
<EnablePackageValidation>true</EnablePackageValidation>
77
<PackageValidationBaselineVersion>8.0.0</PackageValidationBaselineVersion>
88
<Authors>Michiel van Oudheusden;Christian Kadluba;Serilog Contributors</Authors>

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

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class SqlColumn
1313
private SqlDbType _dataType = SqlDbType.VarChar; // backwards-compatibility default
1414
private string _columnName = string.Empty;
1515
private string _propertyName;
16+
private bool _resolveHierarchicalPropertyName = true;
1617
private readonly List<string> _propertyNameHierarchy = new List<string>();
1718

1819
/// <summary>
@@ -109,7 +110,20 @@ public string PropertyName
109110
set
110111
{
111112
_propertyName = value;
112-
ParseHierarchicalPropertyName(value);
113+
ParseHierarchicalPropertyName();
114+
}
115+
}
116+
117+
/// <summary>
118+
/// Controls whether hierarchical expressions in `PropertyName` are evaluated. The default is `true`.
119+
/// </summary>
120+
public bool ResolveHierarchicalPropertyName
121+
{
122+
get => _resolveHierarchicalPropertyName;
123+
set
124+
{
125+
_resolveHierarchicalPropertyName = value;
126+
ParseHierarchicalPropertyName();
113127
}
114128
}
115129

@@ -130,8 +144,6 @@ internal bool HasHierarchicalPropertyName
130144
// and allows casting back to the Standard Column without a lot of switch gymnastics.
131145
internal StandardColumn? StandardColumnIdentifier { get; set; }
132146

133-
internal Type StandardColumnType { get; set; }
134-
135147
/// <summary>
136148
/// Converts a SQL sink SqlColumn object to a System.Data.DataColumn object. The original
137149
/// SqlColumn object is stored in the DataColumn's ExtendedProperties collection.
@@ -170,9 +182,17 @@ internal void SetDataTypeFromConfigString(string requestedSqlType)
170182
DataType = sqlType;
171183
}
172184

173-
private void ParseHierarchicalPropertyName(string propertyName)
185+
private void ParseHierarchicalPropertyName()
174186
{
175-
_propertyNameHierarchy.AddRange(propertyName.Split('.'));
187+
_propertyNameHierarchy.Clear();
188+
if (ResolveHierarchicalPropertyName)
189+
{
190+
_propertyNameHierarchy.AddRange(PropertyName.Split('.'));
191+
}
192+
else
193+
{
194+
_propertyNameHierarchy.Add(PropertyName);
195+
}
176196
}
177197
}
178198
}

test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Implementations/System.Configuration/SystemConfigurationColumnOptionsProviderTests.cs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System.Data;
2-
using Moq;
2+
using System.Linq;
3+
using FluentAssertions;
34
using Serilog.Configuration;
45
using Serilog.Sinks.MSSqlServer.Configuration;
56
using Serilog.Sinks.MSSqlServer.Tests.TestUtils;
@@ -28,7 +29,7 @@ public void ConfigureColumnOptionsReadsTraceIdColumnOptions()
2829
_configurationSection.TraceId.AllowNull = "false";
2930
_configurationSection.TraceId.DataType = "22"; // VarChar
3031
var columnOptions = new MSSqlServer.ColumnOptions();
31-
32+
3233
// Act
3334
_sut.ConfigureColumnOptions(_configurationSection, columnOptions);
3435

@@ -56,5 +57,48 @@ public void ConfigureColumnOptionsReadsSpanIdColumnOptions()
5657
Assert.False(columnOptions.SpanId.AllowNull);
5758
Assert.Equal(SqlDbType.VarChar, columnOptions.SpanId.DataType);
5859
}
60+
61+
[Fact]
62+
public void ConfigureColumnOptionsReadsAdditionalColumnsResolveHierarchicalPropertyName()
63+
{
64+
// Arrange
65+
const string columnName = "AdditionalColumn1";
66+
var columnConfig = new ColumnConfig
67+
{
68+
ColumnName = columnName,
69+
ResolveHierarchicalPropertyName = "false"
70+
};
71+
_configurationSection.Columns.Add(columnConfig);
72+
var columnOptions = new MSSqlServer.ColumnOptions();
73+
74+
// Act
75+
_sut.ConfigureColumnOptions(_configurationSection, columnOptions);
76+
77+
// Assert
78+
var additionalColumn1 = columnOptions.AdditionalColumns.SingleOrDefault(c => c.ColumnName == columnName);
79+
additionalColumn1.Should().NotBeNull();
80+
additionalColumn1.ResolveHierarchicalPropertyName.Should().Be(false);
81+
}
82+
83+
[Fact]
84+
public void ConfigureColumnOptionsDefaultsAdditionalColumnsResolveHierarchicalPropertyName()
85+
{
86+
// Arrange
87+
const string columnName = "AdditionalColumn1";
88+
var columnConfig = new ColumnConfig
89+
{
90+
ColumnName = columnName
91+
};
92+
_configurationSection.Columns.Add(columnConfig);
93+
var columnOptions = new MSSqlServer.ColumnOptions();
94+
95+
// Act
96+
_sut.ConfigureColumnOptions(_configurationSection, columnOptions);
97+
98+
// Assert
99+
var additionalColumn1 = columnOptions.AdditionalColumns.SingleOrDefault(c => c.ColumnName == columnName);
100+
additionalColumn1.Should().NotBeNull();
101+
additionalColumn1.ResolveHierarchicalPropertyName.Should().Be(true);
102+
}
59103
}
60104
}

0 commit comments

Comments
 (0)