Skip to content

Commit 9574ce3

Browse files
committed
initial implementation
1 parent eefaea2 commit 9574ce3

19 files changed

+825
-109
lines changed

README.md

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -410,13 +410,21 @@ Standard Columns are always excluded from the XML `Properties` column but Stand
410410

411411
## External Configuration Syntax
412412

413-
The _Serilog.Settings.AppSettings_ package supports XML-based configuration (either `app.config` or `web.config`), and the _Serilog.Settings.Configuration_ package supports the many _Microsoft.Extensions.Configuration_ sources. JSON is the most common, but other sources include environment variables, command lines, Azure Key Vault, XML, and more.
413+
Projects targeting the .NET Framework automatically have support for XML-based configuration (either `app.config` or `web.config`) of `ColumnOptions` settings, and the _Serilog.Settings.AppSettings_ package adds XML-based configuration of sink arguments.
414414

415-
### JSON syntax
415+
Projects targeting .NET Standard can apply configuration-driven sink setup and `ColumnOptions` settings using the _Serilog.Settings.Configuration_ (SSC) package. It supports any of the configuration sources supported by the underlying _Microsoft.Extensions.Configuration_ (MEC) packages. JSON is the most common, but other sources include environment variables, command lines, Azure Key Vault, XML, and more.
416416

417417
All properties of the `ColumnOptions` class are configurable except the `Properties.PropertyFilter` predicate expression. In most cases configuration keynames match the class property names, but there are some exceptions. For example, because `PrimaryKey` is a `SqlColumn` object reference when configured through code, external configuration uses a `primaryKeyColumnName` setting to identify the primary key by name.
418418

419-
Keys and values are not case-sensitive.
419+
All `ColumnOptions` elements and lists are optional. Some settings or properties shown are mutually exclusive and are listed for documentation purposes -- these do not necessarily reflect real-world configurations that can be copy-pasted as-is.
420+
421+
Custom columns and the stand-alone Standard Column entires all support the same general column properties (`ColumnName`, `DataType`, etc) listed in thec [SqlColumn Objects](#sqlcolumn-objects) topic. The samples omit many of these properties for brevity.
422+
423+
If you combine external configuration with configuration through code, external configuration changes will be applied in addition to any `ColumnOptions` object you provide through code.
424+
425+
### JSON: .NET Standard configuration
426+
427+
Keys and values are not case-sensitive. This is an example of configuring the sink arguments.
420428

421429
```json
422430
{
@@ -441,15 +449,15 @@ Keys and values are not case-sensitive.
441449
}
442450
```
443451

444-
As the name suggests, `columnOptionSection` is an entire configuration section in its own right. All possible entries are shown below, excluding properties like `ColumnName` and `AllowNull` that are available on every column definition. Some sample values are also shown below. All properties and subsections are optional. The `AdditionalColumns` collection can also be populated from a key named `customColumns` for backwards-compatibility reasons (not shown here). Some properties shown here are mutually exclusive (such as `clusteredColumnstoreIndex` and `primaryKeyColumnName`) -- the following does not represent a real-world configuration example, it is only a reference.
452+
As the name suggests, `columnOptionSection` is an entire configuration section in its own right. The `AdditionalColumns` collection can also be populated from a key named `customColumns` (not shown here) for backwards-compatibility reasons.
445453

446454
```json
447455
"columnOptionsSection": {
448-
"addStandardColumns": [ "LogEvent" ],
449-
"removeStandardColumns": [ "MessageTemplate", "Properties" ],
450456
"disableTriggers": true,
451457
"clusteredColumnstoreIndex": false,
452458
"primaryKeyColumnName": "Id",
459+
"addStandardColumns": [ "LogEvent" ],
460+
"removeStandardColumns": [ "MessageTemplate", "Properties" ],
453461
"additionalColumns": [
454462
{ "ColumnName": "EventType", "DataType": "int", "AllowNull": false },
455463
{ "ColumnName": "Release", "DataType": "varchar", "DataLength": 32 }
@@ -488,16 +496,24 @@ As the name suggests, `columnOptionSection` is an entire configuration section i
488496
}
489497
```
490498

491-
### ConfigurationManager XML syntax
499+
### XML: .NET Framework ColumnOptions configuration
492500

493-
When targeting the .NET Framework, built-in support for `ConfigurationManager` allows you to define custom columns as it is represented in SQL Server. Columns specified must match the physical database exactly. This section will be processed automatically if it exists in the application's `web.config` or `app.config` file. The same properties listed in the topic [SqlColumn Objects](#sqlcolumn-objects) are available here.
501+
Keys and values are case-sensitive. Case must match **_exactly_** as shown below.
494502

495503
```xml
496504
<configSections>
497505
<section name="MSSqlServerSettingsSection"
498506
type="Serilog.Configuration.MSSqlServerConfigurationSection, Serilog.Sinks.MSSqlServer"/>
499507
</configSections>
500-
<MSSqlServerSettingsSection>
508+
<MSSqlServerSettingsSection DisableTriggers="false"
509+
ClusteredColumnstoreIndex="false"
510+
PrimaryKeyColumnName="Id">
511+
<AddStandardColumns>
512+
<add Name="LogEvent"/>
513+
</AddStandardColumns>
514+
<RemoveStandardColumns>
515+
<remove Name="Properties"/>
516+
</RemoveStandardColumns>
501517
<Columns>
502518
<add ColumnName="EventType" DataType="int"/>
503519
<add ColumnName="Release"
@@ -506,10 +522,31 @@ When targeting the .NET Framework, built-in support for `ConfigurationManager` a
506522
AllowNull="true"
507523
NonClusteredIndex="false"/>
508524
</Columns>
525+
<Exception ColumnName="Ex" DataLength="512"/>
526+
<Id NonClusteredIndex="true"/>
527+
<Level ColumnName="Severity" StoreAsEnum="true"/>
528+
<LogEvent ExcludeAdditionalProperties="true"
529+
ExcludeStandardColumns="true"/>
530+
<Message DataLength="1024"/>
531+
<MessageTemplate DataLength="1536"/>
532+
<Properties DataType="xml"
533+
ExcludeAdditionalProperties="true"
534+
DictionaryElementName="dict"
535+
ItemElementName="item"
536+
OmitDictionaryContainerElement="false"
537+
OmitSequenceContainerElement="false"
538+
OmitStructureContainerElement="false"
539+
OmitElementIfEmpty="true"
540+
PropertyElementName="prop"
541+
RootElementName="root"
542+
SequenceElementName="seq"
543+
StructureElementName="struct"
544+
UsePropertyKeyAsElementName="false"/>
545+
<TimeStamp ConvertToUtc="true"/>
509546
</MSSqlServerSettingsSection>
510547
```
511548

512-
### AppSettings XML syntax
549+
### XML: AppSettings sink configuration
513550

514551
Refer to the _Serilog.Settings.AppSetings_ package documentation for complete details about sink configuration. This is an example of seting some of the configuration parameters for this sink.
515552

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

Lines changed: 30 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,6 @@ private static string GetConnectionString(string nameOrConnectionString, IConfig
151151
return cs;
152152
}
153153

154-
// simulate using a property setter as an out parameter
155-
delegate void PropertySetter<T>(T value);
156-
157154
/// <summary>
158155
/// Create or add to the ColumnOptions object and apply any configuration changes to it.
159156
/// </summary>
@@ -184,7 +181,8 @@ void AddRemoveStandardColumns()
184181
{
185182
foreach (var col in addStd.GetChildren().ToList())
186183
{
187-
if (Enum.TryParse(col.Value, ignoreCase: true, result: out StandardColumn stdcol))
184+
if (Enum.TryParse(col.Value, ignoreCase: true, result: out StandardColumn stdcol)
185+
&& !opts.Store.Contains(stdcol))
188186
opts.Store.Add(stdcol);
189187
}
190188
}
@@ -195,7 +193,8 @@ void AddRemoveStandardColumns()
195193
{
196194
foreach (var col in removeStd.GetChildren().ToList())
197195
{
198-
if (Enum.TryParse(col.Value, ignoreCase: true, result: out StandardColumn stdcol))
196+
if (Enum.TryParse(col.Value, ignoreCase: true, result: out StandardColumn stdcol)
197+
&& opts.Store.Contains(stdcol))
199198
opts.Store.Remove(stdcol);
200199
}
201200
}
@@ -211,12 +210,12 @@ void AddAdditionalColumns()
211210
{
212211
foreach (var c in newcols)
213212
{
214-
if (!string.IsNullOrWhiteSpace(c.ColumnName))// && !string.IsNullOrWhiteSpace(c.DataType))
213+
if (!string.IsNullOrWhiteSpace(c.ColumnName))
215214
{
216215
if (opts.AdditionalColumns == null)
217216
opts.AdditionalColumns = new Collection<SqlColumn>();
218217

219-
opts.AdditionalColumns.Add(c);//.AsSqlColumn());
218+
opts.AdditionalColumns.Add(c);
220219
}
221220
}
222221
}
@@ -229,49 +228,49 @@ void ReadStandardColumns()
229228
{
230229
SetCommonColumnOptions(opts.Id);
231230
#pragma warning disable 618 // deprecated: BigInt property
232-
SetIfProvided<bool>((val) => { opts.Id.BigInt = val; }, section["bigInt"]);
231+
SetProperty.IfNotNull<bool>(section["bigInt"], (val) => opts.Id.BigInt = val);
233232
#pragma warning restore 618
234233
}
235234

236235
section = config.GetSection("level");
237236
if (section != null)
238237
{
239238
SetCommonColumnOptions(opts.Level);
240-
SetIfProvided<bool>((val) => { opts.Level.StoreAsEnum = val; }, section["storeAsEnum"]);
239+
SetProperty.IfNotNull<bool>(section["storeAsEnum"], (val) => opts.Level.StoreAsEnum = val);
241240
}
242241

243242
section = config.GetSection("properties");
244243
if (section != null)
245244
{
246245
SetCommonColumnOptions(opts.Properties);
247-
SetIfProvided<bool>((val) => { opts.Properties.ExcludeAdditionalProperties = val; }, section["excludeAdditionalProperties"]);
248-
SetIfProvided<string>((val) => { opts.Properties.DictionaryElementName = val; }, section["dictionaryElementName"]);
249-
SetIfProvided<string>((val) => { opts.Properties.ItemElementName = val; }, section["itemElementName"]);
250-
SetIfProvided<bool>((val) => { opts.Properties.OmitDictionaryContainerElement = val; }, section["omitDictionaryContainerElement"]);
251-
SetIfProvided<bool>((val) => { opts.Properties.OmitSequenceContainerElement = val; }, section["omitSequenceContainerElement"]);
252-
SetIfProvided<bool>((val) => { opts.Properties.OmitStructureContainerElement = val; }, section["omitStructureContainerElement"]);
253-
SetIfProvided<bool>((val) => { opts.Properties.OmitElementIfEmpty = val; }, section["omitElementIfEmpty"]);
254-
SetIfProvided<string>((val) => { opts.Properties.PropertyElementName = val; }, section["propertyElementName"]);
255-
SetIfProvided<string>((val) => { opts.Properties.RootElementName = val; }, section["rootElementName"]);
256-
SetIfProvided<string>((val) => { opts.Properties.SequenceElementName = val; }, section["sequenceElementName"]);
257-
SetIfProvided<string>((val) => { opts.Properties.StructureElementName = val; }, section["structureElementName"]);
258-
SetIfProvided<bool>((val) => { opts.Properties.UsePropertyKeyAsElementName = val; }, section["usePropertyKeyAsElementName"]);
246+
SetProperty.IfNotNull<bool>(section["excludeAdditionalProperties"], (val) => opts.Properties.ExcludeAdditionalProperties = val);
247+
SetProperty.IfNotNull<string>(section["dictionaryElementName"], (val) => opts.Properties.DictionaryElementName = val);
248+
SetProperty.IfNotNull<string>(section["itemElementName"], (val) => opts.Properties.ItemElementName = val);
249+
SetProperty.IfNotNull<bool>(section["omitDictionaryContainerElement"], (val) => opts.Properties.OmitDictionaryContainerElement = val);
250+
SetProperty.IfNotNull<bool>(section["omitSequenceContainerElement"], (val) => opts.Properties.OmitSequenceContainerElement = val);
251+
SetProperty.IfNotNull<bool>(section["omitStructureContainerElement"], (val) => opts.Properties.OmitStructureContainerElement = val);
252+
SetProperty.IfNotNull<bool>(section["omitElementIfEmpty"], (val) => opts.Properties.OmitElementIfEmpty = val);
253+
SetProperty.IfNotNull<string>(section["propertyElementName"], (val) => opts.Properties.PropertyElementName = val);
254+
SetProperty.IfNotNull<string>(section["rootElementName"], (val) => opts.Properties.RootElementName = val);
255+
SetProperty.IfNotNull<string>(section["sequenceElementName"], (val) => opts.Properties.SequenceElementName = val);
256+
SetProperty.IfNotNull<string>(section["structureElementName"], (val) => opts.Properties.StructureElementName = val);
257+
SetProperty.IfNotNull<bool>(section["usePropertyKeyAsElementName"], (val) => opts.Properties.UsePropertyKeyAsElementName = val);
259258
// TODO PropertiesFilter would need a compiled Predicate<string> (high Roslyn overhead, see Serilog Config repo #106)
260259
}
261260

262261
section = config.GetSection("timeStamp");
263262
if (section != null)
264263
{
265264
SetCommonColumnOptions(opts.TimeStamp);
266-
SetIfProvided<bool>((val) => { opts.TimeStamp.ConvertToUtc = val; }, section["convertToUtc"]);
265+
SetProperty.IfNotNull<bool>(section["convertToUtc"], (val) => opts.TimeStamp.ConvertToUtc = val);
267266
}
268267

269268
section = config.GetSection("logEvent");
270269
if (section != null)
271270
{
272271
SetCommonColumnOptions(opts.LogEvent);
273-
SetIfProvided<bool>((val) => { opts.LogEvent.ExcludeAdditionalProperties = val; }, section["excludeAdditionalProperties"]);
274-
SetIfProvided<bool>((val) => { opts.LogEvent.ExcludeStandardColumns = val; }, section["ExcludeStandardColumns"]);
272+
SetProperty.IfNotNull<bool>(section["excludeAdditionalProperties"], (val) => opts.LogEvent.ExcludeAdditionalProperties = val);
273+
SetProperty.IfNotNull<bool>(section["ExcludeStandardColumns"], (val) => opts.LogEvent.ExcludeStandardColumns = val);
275274
}
276275

277276
section = config.GetSection("message");
@@ -289,18 +288,18 @@ void ReadStandardColumns()
289288
// Standard Columns are subclasses of the SqlColumn class
290289
void SetCommonColumnOptions(SqlColumn target)
291290
{
292-
SetIfProvided<string>((val) => { target.ColumnName = val; }, section["columnName"]);
293-
SetIfProvided<string>((val) => { target.SetDataTypeFromConfigString(val); }, section["dataType"]);
294-
SetIfProvided<bool>((val) => { target.AllowNull = val; }, section["allowNull"]);
295-
SetIfProvided<int>((val) => { target.DataLength = val; }, section["dataLength"]);
296-
SetIfProvided<bool>((val) => { target.NonClusteredIndex = val; }, section["nonClusteredIndex"]);
291+
SetProperty.IfNotNullOrEmpty<string>(section["columnName"], (val) => target.ColumnName = val);
292+
SetProperty.IfNotNull<string>(section["dataType"], (val) => target.SetDataTypeFromConfigString(val));
293+
SetProperty.IfNotNull<bool>(section["allowNull"], (val) => target.AllowNull = val);
294+
SetProperty.IfNotNull<int>(section["dataLength"], (val) => target.DataLength = val);
295+
SetProperty.IfNotNull<bool>(section["nonClusteredIndex"], (val) => target.NonClusteredIndex = val);
297296
}
298297
}
299298

300299
void ReadMiscColumnOptions()
301300
{
302-
SetIfProvided<bool>((val) => { opts.DisableTriggers = val; }, config["disableTriggers"]);
303-
SetIfProvided<bool>((val) => { opts.ClusteredColumnstoreIndex = val; }, config["clusteredColumnstoreIndex"]);
301+
SetProperty.IfNotNull<bool>(config["disableTriggers"], (val) => opts.DisableTriggers = val);
302+
SetProperty.IfNotNull<bool>(config["clusteredColumnstoreIndex"], (val) => opts.ClusteredColumnstoreIndex = val);
304303

305304
string pkName = config["primaryKeyColumnName"];
306305
if (!string.IsNullOrEmpty(pkName))
@@ -334,23 +333,6 @@ void ReadMiscColumnOptions()
334333
throw new ArgumentException($"Could not match the configured primary key column name \"{pkName}\" with a data column in the table.");
335334
}
336335
}
337-
338-
// This is used to only set a column property when it is actually specified in the config.
339-
// When a value is requested from config, it returns null if that value hasn't been specified.
340-
// This also means you can't use config to set a property to null.
341-
void SetIfProvided<T>(PropertySetter<T> setter, string value)
342-
{
343-
if(value == null)
344-
return;
345-
try
346-
{
347-
var setting = (T)Convert.ChangeType(value, typeof(T));
348-
setter(setting);
349-
}
350-
// don't change the property if the conversion failed
351-
catch (InvalidCastException) { }
352-
catch (OverflowException) { }
353-
}
354336
}
355337
}
356338
}

src/Serilog.Sinks.MSSqlServer/Configuration/NetFramework.ConfigurationManager/ColumnCollection.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
using System.ComponentModel;
1615
using System.Configuration;
1716

1817
// Disable XML comment warnings for internal config classes which are required to have public members

0 commit comments

Comments
 (0)