diff --git a/.editorconfig b/.editorconfig index f16002ac..d3a3808d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,3 +14,14 @@ end_of_line = lf [*.{cmd, bat}] end_of_line = crlf + +[*.cs] + +# CA2227: Collection properties should be read only +dotnet_diagnostic.CA2227.severity = none + +# CA1303: Do not pass literals as localized parameters +dotnet_diagnostic.CA1303.severity = none + +# S108: Nested blocks of code should not be left empty +dotnet_diagnostic.S108.severity = none diff --git a/README.md b/README.md index d38760c9..bd2821dd 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ A Serilog sink that writes events to Microsoft SQL Server. This sink will write the log event data to a table and can optionally also store the properties inside an XML or JSON column so they can be queried. Important properties can also be written to their own separate columns. **Package** - [Serilog.Sinks.MSSqlServer](http://nuget.org/packages/serilog.sinks.mssqlserver) -| **Minimum Platforms** - .NET Framework 4.5.2, .NET Core 2.0, .NET Standard 2.0 +| **Minimum Platforms** - .NET Framework 4.6.1, .NET Core 2.0, .NET Standard 2.0 #### Topics @@ -34,9 +34,13 @@ All sink configuration methods accept the following arguments, though not necess * `batchPostingLimit` * `period` * `formatProvider` +* `useMsi` +* `azureServiceTokenProviderResource` ### Basic Arguments +Adding support for MSI (Managed Service Identities), and AccessTokens in sqlConnections. + At minimum, `connectionString` and `tableName` are required. If you are using an external configuration source such as an XML file or JSON file, you can use a named connection string instead of providing the full "raw" connection string. If `schemaName` is omitted, the default is `dbo`. @@ -70,7 +74,6 @@ Because of the way external configuration has been implemented in various .NET f | Your Framework | TFM | Project Types | External Configuration | | --- | --- | --- | --- | -| .NET Framework 4.5.2 | `net452` | app or library | _System.Configuration_ | | .NET Framework 4.6.1+ | `net461` | app or library | _System.Configuration_ | | .NET Framework 4.6.1+ | `net461` | app or library | _Microsoft.Extensions.Configuration_ | | .NET Standard 2.0 | `netstandard2.0` | library only | _Microsoft.Extensions.Configuration_ | diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/Hybrid/LoggerConfigurationMSSqlServerExtensions.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/Hybrid/LoggerConfigurationMSSqlServerExtensions.cs index ad336244..db90ed1b 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/Hybrid/LoggerConfigurationMSSqlServerExtensions.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/Hybrid/LoggerConfigurationMSSqlServerExtensions.cs @@ -34,7 +34,8 @@ public static class LoggerConfigurationMSSqlServerExtensions /// /// The configuration section name for app.config or web.config configuration files. /// - public static string AppConfigSectionName = "MSSqlServerSettingsSection"; + public static string AppConfigSectionName { get; } = "MSSqlServerSettingsSection"; + /// /// Adds a sink that writes log events to a table in a MSSqlServer database. @@ -54,6 +55,8 @@ public static class LoggerConfigurationMSSqlServerExtensions /// An externally-modified group of column settings /// A config section defining various column settings /// Name of the schema for the table to store the data in. The default is 'dbo'. + /// Option to use MSI + /// Resource required in AzureServiceTokenProvider.GetAccessTokenAsync(azureServiceTokenProviderResource). This will error if null, and useMsi is st to true /// Logger configuration, allowing configuration to continue. /// A required parameter is null. public static LoggerConfiguration MSSqlServer( @@ -68,20 +71,28 @@ public static LoggerConfiguration MSSqlServer( bool autoCreateSqlTable = false, ColumnOptions columnOptions = null, IConfigurationSection columnOptionsSection = null, - string schemaName = "dbo" + string schemaName = "dbo", + bool useMsi = false, + string azureServiceTokenProviderResource = null ) { - if(loggerConfiguration == null) + if (useMsi && string.IsNullOrWhiteSpace(azureServiceTokenProviderResource)) + throw new ArgumentNullException(nameof(azureServiceTokenProviderResource), "If useMsi is set to true, you must also provide an azureServiceTokenProviderResource"); + + if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration"); var defaultedPeriod = period ?? MSSqlServerSink.DefaultPeriod; var colOpts = columnOptions ?? new ColumnOptions(); var connStr = connectionString; + var tokenResource = azureServiceTokenProviderResource; if (ConfigurationManager.GetSection(AppConfigSectionName) is MSSqlServerConfigurationSection serviceConfigSection) { colOpts = ApplySystemConfiguration.ConfigureColumnOptions(serviceConfigSection, colOpts); connStr = ApplySystemConfiguration.GetConnectionString(connStr); + if(useMsi) + tokenResource = ApplySystemConfiguration.GetAzureServiceTokenProviderResource(tokenResource); if (appConfiguration != null || columnOptionsSection != null) SelfLog.WriteLine("Warning: Both System.Configuration (app.config or web.config) and Microsoft.Extensions.Configuration are being applied to the MSSQLServer sink."); @@ -91,6 +102,8 @@ public static LoggerConfiguration MSSqlServer( { connStr = ApplyMicrosoftExtensionsConfiguration.GetConnectionString(connStr, appConfiguration); colOpts = ApplyMicrosoftExtensionsConfiguration.ConfigureColumnOptions(colOpts, columnOptionsSection); + if (useMsi) + tokenResource = ApplyMicrosoftExtensionsConfiguration.GetAzureServiceTokenProviderResource(tokenResource, appConfiguration); } return loggerConfiguration.Sink( @@ -102,7 +115,9 @@ public static LoggerConfiguration MSSqlServer( formatProvider, autoCreateSqlTable, colOpts, - schemaName + schemaName, + useMsi, + tokenResource ), restrictedToMinimumLevel); } @@ -120,6 +135,8 @@ public static LoggerConfiguration MSSqlServer( /// An externally-modified group of column settings /// A config section defining various column settings /// Name of the schema for the table to store the data in. The default is 'dbo'. + /// Option to use MSI + /// Resource required in AzureServiceTokenProvider.GetAccessTokenAsync(azureServiceTokenProviderResource). This will error if null, and useMsi is st to true /// Logger configuration, allowing configuration to continue. /// A required parameter is null. public static LoggerConfiguration MSSqlServer( @@ -132,19 +149,26 @@ public static LoggerConfiguration MSSqlServer( bool autoCreateSqlTable = false, ColumnOptions columnOptions = null, IConfigurationSection columnOptionsSection = null, - string schemaName = "dbo" + string schemaName = "dbo", + bool useMsi = false, + string azureServiceTokenProviderResource = null ) { - if(loggerAuditSinkConfiguration == null) + if (useMsi && string.IsNullOrWhiteSpace(azureServiceTokenProviderResource)) + throw new ArgumentNullException(nameof(azureServiceTokenProviderResource), "If useMsi is set to true, you must also provide an azureServiceTokenProviderResource"); + + if (loggerAuditSinkConfiguration == null) throw new ArgumentNullException("loggerAuditSinkConfiguration"); var colOpts = columnOptions ?? new ColumnOptions(); var connStr = connectionString; + var tokenResource = azureServiceTokenProviderResource; if (ConfigurationManager.GetSection(AppConfigSectionName) is MSSqlServerConfigurationSection serviceConfigSection) { colOpts = ApplySystemConfiguration.ConfigureColumnOptions(serviceConfigSection, colOpts); connStr = ApplySystemConfiguration.GetConnectionString(connStr); + tokenResource = ApplySystemConfiguration.GetAzureServiceTokenProviderResource(tokenResource); if (appConfiguration != null || columnOptionsSection != null) SelfLog.WriteLine("Warning: Both System.Configuration (app.config or web.config) and Microsoft.Extensions.Configuration are being applied to the MSSQLServer sink."); @@ -154,6 +178,7 @@ public static LoggerConfiguration MSSqlServer( { connStr = ApplyMicrosoftExtensionsConfiguration.GetConnectionString(connStr, appConfiguration); colOpts = ApplyMicrosoftExtensionsConfiguration.ConfigureColumnOptions(colOpts, columnOptionsSection); + tokenResource = ApplyMicrosoftExtensionsConfiguration.GetAzureServiceTokenProviderResource(tokenResource, appConfiguration); } return loggerAuditSinkConfiguration.Sink( @@ -163,7 +188,9 @@ public static LoggerConfiguration MSSqlServer( formatProvider, autoCreateSqlTable, colOpts, - schemaName + schemaName, + useMsi, + tokenResource ), restrictedToMinimumLevel); } diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/Microsoft.Extensions.Configuration/LoggerConfigurationMSSqlServerExtensions.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/Microsoft.Extensions.Configuration/LoggerConfigurationMSSqlServerExtensions.cs index 8f351bb2..da9f1788 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/Microsoft.Extensions.Configuration/LoggerConfigurationMSSqlServerExtensions.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/Microsoft.Extensions.Configuration/LoggerConfigurationMSSqlServerExtensions.cs @@ -45,6 +45,8 @@ public static partial class LoggerConfigurationMSSqlServerExtensions /// An externally-modified group of column settings /// A config section defining various column settings /// Name of the schema for the table to store the data in. The default is 'dbo'. + /// Option to use MSI + /// Resource required in AzureServiceTokenProvider.GetAccessTokenAsync(azureServiceTokenProviderResource). This will error if null, and useMsi is st to true /// Logger configuration, allowing configuration to continue. /// A required parameter is null. public static LoggerConfiguration MSSqlServer( @@ -59,15 +61,21 @@ public static LoggerConfiguration MSSqlServer( bool autoCreateSqlTable = false, ColumnOptions columnOptions = null, IConfigurationSection columnOptionsSection = null, - string schemaName = "dbo" + string schemaName = "dbo", + bool useMsi = false, + string azureServiceTokenProviderResource = null ) { - if(loggerConfiguration == null) + if (useMsi && string.IsNullOrWhiteSpace(azureServiceTokenProviderResource)) + throw new ArgumentNullException(nameof(azureServiceTokenProviderResource), "If useMsi is set to true, you must also provide an azureServiceTokenProviderResource"); + + if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration"); var defaultedPeriod = period ?? MSSqlServerSink.DefaultPeriod; var connectionStr = ApplyMicrosoftExtensionsConfiguration.GetConnectionString(connectionString, appConfiguration); var colOpts = ApplyMicrosoftExtensionsConfiguration.ConfigureColumnOptions(columnOptions, columnOptionsSection); + var tokenResource = useMsi ? ApplyMicrosoftExtensionsConfiguration.GetAzureServiceTokenProviderResource(azureServiceTokenProviderResource, appConfiguration) : null; return loggerConfiguration.Sink( new MSSqlServerSink( @@ -78,7 +86,9 @@ public static LoggerConfiguration MSSqlServer( formatProvider, autoCreateSqlTable, colOpts, - schemaName + schemaName, + useMsi, + tokenResource ), restrictedToMinimumLevel); } @@ -96,6 +106,8 @@ public static LoggerConfiguration MSSqlServer( /// An externally-modified group of column settings /// A config section defining various column settings /// Name of the schema for the table to store the data in. The default is 'dbo'. + /// Option to use MSI + /// Resource required in AzureServiceTokenProvider.GetAccessTokenAsync(azureServiceTokenProviderResource). This will error if null, and useMsi is st to true /// Logger configuration, allowing configuration to continue. /// A required parameter is null. public static LoggerConfiguration MSSqlServer( @@ -108,23 +120,31 @@ public static LoggerConfiguration MSSqlServer( bool autoCreateSqlTable = false, ColumnOptions columnOptions = null, IConfigurationSection columnOptionsSection = null, - string schemaName = "dbo" + string schemaName = "dbo", + bool useMsi = false, + string azureServiceTokenProviderResource = null ) { - if(loggerAuditSinkConfiguration == null) + if (useMsi && string.IsNullOrWhiteSpace(azureServiceTokenProviderResource)) + throw new ArgumentNullException(nameof(azureServiceTokenProviderResource), "If useMsi is set to true, you must also provide an azureServiceTokenProviderResource"); + + if (loggerAuditSinkConfiguration == null) throw new ArgumentNullException("loggerAuditSinkConfiguration"); var connectionStr = ApplyMicrosoftExtensionsConfiguration.GetConnectionString(connectionString, appConfiguration); var colOpts = ApplyMicrosoftExtensionsConfiguration.ConfigureColumnOptions(columnOptions, columnOptionsSection); + var tokenResource = useMsi ? ApplyMicrosoftExtensionsConfiguration.GetAzureServiceTokenProviderResource(azureServiceTokenProviderResource, appConfiguration) : null; return loggerAuditSinkConfiguration.Sink( new MSSqlServerAuditSink( - connectionString, + connectionStr, tableName, formatProvider, autoCreateSqlTable, - columnOptions, - schemaName + colOpts, + schemaName, + useMsi, + tokenResource ), restrictedToMinimumLevel); } diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/System.Configuration/LoggerConfigurationMSSqlServerExtensions.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/System.Configuration/LoggerConfigurationMSSqlServerExtensions.cs index ef8f9dd9..fe23eb12 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/System.Configuration/LoggerConfigurationMSSqlServerExtensions.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Extensions/System.Configuration/LoggerConfigurationMSSqlServerExtensions.cs @@ -48,6 +48,8 @@ public static class LoggerConfigurationMSSqlServerExtensions /// Supplies culture-specific formatting information, or null. /// Create log table with the provided name on destination sql server. /// + /// Option to use MSI + /// Resource required in AzureServiceTokenProvider.GetAccessTokenAsync(azureServiceTokenProviderResource). This will error if null, and useMsi is st to true /// Logger configuration, allowing configuration to continue. /// A required parameter is null. public static LoggerConfiguration MSSqlServer( @@ -60,9 +62,13 @@ public static LoggerConfiguration MSSqlServer( IFormatProvider formatProvider = null, bool autoCreateSqlTable = false, ColumnOptions columnOptions = null, - string schemaName = "dbo" - ) + string schemaName = "dbo", + bool useMsi = false, + string azureServiceTokenProviderResource = null) { + if (useMsi && string.IsNullOrWhiteSpace(azureServiceTokenProviderResource)) + throw new ArgumentNullException(nameof(azureServiceTokenProviderResource), "If useMsi is set to true, you must also provide an azureServiceTokenProviderResource"); + if (loggerConfiguration == null) throw new ArgumentNullException("loggerConfiguration"); var defaultedPeriod = period ?? MSSqlServerSink.DefaultPeriod; @@ -73,6 +79,8 @@ public static LoggerConfiguration MSSqlServer( connectionString = ApplySystemConfiguration.GetConnectionString(connectionString); + var tokenResource = useMsi ? ApplySystemConfiguration.GetAzureServiceTokenProviderResource(azureServiceTokenProviderResource) : null; + return loggerConfiguration.Sink( new MSSqlServerSink( connectionString, @@ -82,8 +90,9 @@ public static LoggerConfiguration MSSqlServer( formatProvider, autoCreateSqlTable, colOpts, - schemaName - ), + schemaName, + useMsi, + tokenResource), restrictedToMinimumLevel); } @@ -101,6 +110,8 @@ public static LoggerConfiguration MSSqlServer( /// Supplies culture-specific formatting information, or null. /// Create log table with the provided name on destination sql server. /// + /// Option to use MSI + /// Resource required in AzureServiceTokenProvider.GetAccessTokenAsync(azureServiceTokenProviderResource). This will error if null, and useMsi is st to true /// Logger configuration, allowing configuration to continue. /// A required parameter is null. public static LoggerConfiguration MSSqlServer(this LoggerAuditSinkConfiguration loggerAuditSinkConfiguration, @@ -110,8 +121,13 @@ public static LoggerConfiguration MSSqlServer(this LoggerAuditSinkConfiguration IFormatProvider formatProvider = null, bool autoCreateSqlTable = false, ColumnOptions columnOptions = null, - string schemaName = "dbo") + string schemaName = "dbo", + bool useMsi = false, + string azureServiceTokenProviderResource = null) { + if (useMsi && string.IsNullOrWhiteSpace(azureServiceTokenProviderResource)) + throw new ArgumentNullException(nameof(azureServiceTokenProviderResource), "If useMsi is set to true, you must also provide an azureServiceTokenProviderResource"); + if (loggerAuditSinkConfiguration == null) throw new ArgumentNullException("loggerAuditSinkConfiguration"); var colOpts = columnOptions ?? new ColumnOptions(); @@ -121,6 +137,8 @@ public static LoggerConfiguration MSSqlServer(this LoggerAuditSinkConfiguration connectionString = ApplySystemConfiguration.GetConnectionString(connectionString); + var tokenResource = useMsi ? ApplySystemConfiguration.GetAzureServiceTokenProviderResource(azureServiceTokenProviderResource) : null; + return loggerAuditSinkConfiguration.Sink( new MSSqlServerAuditSink( connectionString, @@ -128,8 +146,9 @@ public static LoggerConfiguration MSSqlServer(this LoggerAuditSinkConfiguration formatProvider, autoCreateSqlTable, colOpts, - schemaName - ), + schemaName, + useMsi, + tokenResource), restrictedToMinimumLevel); } } diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/ApplyMicrosoftExtensionsConfiguration.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/ApplyMicrosoftExtensionsConfiguration.cs index 7ee058bc..2d47d343 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/ApplyMicrosoftExtensionsConfiguration.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/Microsoft.Extensions.Configuration/ApplyMicrosoftExtensionsConfiguration.cs @@ -8,7 +8,7 @@ namespace Serilog.Sinks.MSSqlServer { /// - /// Configures the sink's connection string and ColumnOtions object. + /// Configures the sink's connection string and ColumnOtions object, and azure token provider resource. /// internal static class ApplyMicrosoftExtensionsConfiguration { @@ -32,6 +32,26 @@ internal static string GetConnectionString(string nameOrConnectionString, IConfi return cs; } + /// + /// Examine if supplied resource string is a reference to an item in web.config + /// If it is, return the named item, if not, return string as supplied. + /// + /// + /// + /// + internal static string GetAzureServiceTokenProviderResource(string nameOrResource, IConfiguration appConfiguration) + { + // If there is an `/`, we assume this is a raw resource string not a named value + // If there are no `/`, attempt to pull the named value from config + if (nameOrResource.IndexOf('/') > -1) return nameOrResource; + string cs = appConfiguration?.GetValue(nameOrResource); + if (string.IsNullOrEmpty(cs)) + { + SelfLog.WriteLine("MSSqlServer sink configured value {0} is not found in app settings and does not appear to be a raw resource string such as http://login.microsoftonline.com.", nameOrResource); + } + return cs; + } + /// /// Create or add to the ColumnOptions object and apply any configuration changes to it. /// diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/SetProperty.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/SetProperty.cs index 6acdd42d..5907b809 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/SetProperty.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/SetProperty.cs @@ -8,12 +8,14 @@ namespace Serilog.Sinks.MSSqlServer public static partial class SetProperty { // Usage: - // SetProperty.IfValueNotNull(stringFromConfig, (boolOutputValue) => opts.BoolProperty = boolOutputValue); +#pragma warning disable S125 // Sections of code should not be commented out + // SetProperty.IfValueNotNull(stringFromConfig, (boolOutputValue) => opts.BoolProperty = boolOutputValue); /// /// Simulates passing a property-setter to an "out" argument. /// - public delegate void PropertySetter(T value); + public delegate void PropertySetter(T value); +#pragma warning restore S125 // Sections of code should not be commented out /// /// This will only set a value (execute the PropertySetter delegate) if the value is non-null. diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ApplySystemConfiguration.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ApplySystemConfiguration.cs index 4809061f..405adc31 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ApplySystemConfiguration.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ApplySystemConfiguration.cs @@ -7,7 +7,7 @@ namespace Serilog.Sinks.MSSqlServer { /// - /// Configures the sink's connection string and ColumnOtions object. + /// Configures the sink's connection string, ColumnOtions object, and azure token provider resource. /// internal static class ApplySystemConfiguration { @@ -38,6 +38,25 @@ internal static string GetConnectionString(string nameOrConnectionString) return nameOrConnectionString; } + /// + /// Examine if supplied resource string is a reference to an item in web.config + /// If it is, return the named item, if not, return string as supplied. + /// + /// + /// + internal static string GetAzureServiceTokenProviderResource(string nameOrResource) + { + // If there is an `/`, we assume this is a raw resource string not a named value + // If there are no `/`, attempt to pull the named value from config + if (nameOrResource.IndexOf('/') > -1) return nameOrResource; + var cs = ConfigurationManager.AppSettings.Get(nameOrResource); + if (string.IsNullOrEmpty(cs)) + { + SelfLog.WriteLine("MSSqlServer sink configured value {0} is not found in app settings and does not appear to be a raw resource string such as http://login.microsoftonline.com.", nameOrResource); + } + return cs; + } + /// /// Populate ColumnOptions properties and collections from app config /// diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ColumnConfig.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ColumnConfig.cs index 26461060..dc0e14a7 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ColumnConfig.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/ColumnConfig.cs @@ -35,36 +35,36 @@ public ColumnConfig(string columnName, string dataType) [ConfigurationProperty("ColumnName", IsRequired = true, IsKey = true)] public virtual string ColumnName { - get { return (string)this["ColumnName"]; } - set { this["ColumnName"] = value; } + get { return (string)this[nameof(ColumnName)]; } + set { this[nameof(ColumnName)] = value; } } [ConfigurationProperty("DataType")] public string DataType { - get { return (string)this["DataType"]; } - set { this["DataType"] = value; } + get { return (string)this[nameof(DataType)]; } + set { this[nameof(DataType)] = value; } } [ConfigurationProperty("DataLength")] public string DataLength { - get { return (string)this["DataLength"]; } - set { this["DataLength"] = value; } + get { return (string)this[nameof(DataLength)]; } + set { this[nameof(DataLength)] = value; } } [ConfigurationProperty("AllowNull")] public string AllowNull { - get { return (string)this["AllowNull"]; } - set { this["AllowNull"] = value; } + get { return (string)this[nameof(AllowNull)]; } + set { this[nameof(AllowNull)] = value; } } [ConfigurationProperty("NonClusteredIndex")] public string NonClusteredIndex { - get { return (string)this["NonClusteredIndex"]; } - set { this["NonClusteredIndex"] = value; } + get { return (string)this[nameof(NonClusteredIndex)]; } + set { this[nameof(NonClusteredIndex)] = value; } } internal SqlColumn AsSqlColumn() @@ -72,18 +72,18 @@ internal SqlColumn AsSqlColumn() var sqlColumn = new SqlColumn(); // inheritors can override IsRequired; config might not change the names of Standard Columns - SetProperty.IfProvidedNotEmpty(this, "ColumnName", (val) => sqlColumn.ColumnName = val); + SetProperty.IfProvidedNotEmpty(this, nameof(ColumnName), (val) => sqlColumn.ColumnName = val); - SetProperty.IfProvidedNotEmpty(this, "DataType", (val) => sqlColumn.SetDataTypeFromConfigString(val)); + SetProperty.IfProvidedNotEmpty(this, nameof(DataType), (val) => sqlColumn.SetDataTypeFromConfigString(val)); - SetProperty.IfProvided(this, "DataLength", (val) => sqlColumn.DataLength = val); + SetProperty.IfProvided(this, nameof(DataLength), (val) => sqlColumn.DataLength = val); if (sqlColumn.DataLength == 0 && SqlDataTypes.DataLengthRequired.Contains(sqlColumn.DataType)) throw new ArgumentException($"SQL column {sqlColumn.ColumnName} of data type {sqlColumn.DataType.ToString()} requires a non-zero DataLength property."); - SetProperty.IfProvided(this, "AllowNull", (val) => sqlColumn.AllowNull = val); + SetProperty.IfProvided(this, nameof(AllowNull), (val) => sqlColumn.AllowNull = val); - SetProperty.IfProvided(this, "NonClusteredIndex", (val) => sqlColumn.NonClusteredIndex = val); + SetProperty.IfProvided(this, nameof(NonClusteredIndex), (val) => sqlColumn.NonClusteredIndex = val); return sqlColumn; } diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/MSSqlServerConfigurationSection.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/MSSqlServerConfigurationSection.cs index 4c6134c9..6e9c629e 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/MSSqlServerConfigurationSection.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/MSSqlServerConfigurationSection.cs @@ -34,36 +34,36 @@ public static MSSqlServerConfigurationSection Settings public MSSqlServerConfigurationSection() { - base["Level"] = new StandardColumnConfigLevel(); + base[nameof(Level)] = new StandardColumnConfigLevel(); } [ConfigurationProperty("DisableTriggers")] public string DisableTriggers { - get => (string)base["DisableTriggers"]; + get => (string)base[nameof(DisableTriggers)]; set { - base["DisableTriggers"] = value; + base[nameof(DisableTriggers)] = value; } } [ConfigurationProperty("ClusteredColumnstoreIndex")] public string ClusteredColumnstoreIndex { - get => (string)base["ClusteredColumnstoreIndex"]; + get => (string)base[nameof(ClusteredColumnstoreIndex)]; set { - base["ClusteredColumnstoreIndex"] = value; + base[nameof(ClusteredColumnstoreIndex)] = value; } } [ConfigurationProperty("PrimaryKeyColumnName")] public string PrimaryKeyColumnName { - get => (string)base["PrimaryKeyColumnName"]; + get => (string)base[nameof(PrimaryKeyColumnName)]; set { - base["PrimaryKeyColumnName"] = value; + base[nameof(PrimaryKeyColumnName)] = value; } } @@ -71,57 +71,57 @@ public string PrimaryKeyColumnName [ConfigurationCollection(typeof(StandardColumnCollection), AddItemName = "add")] public StandardColumnCollection AddStandardColumns { - get => (StandardColumnCollection)base["AddStandardColumns"]; + get => (StandardColumnCollection)base[nameof(AddStandardColumns)]; } [ConfigurationProperty("RemoveStandardColumns", IsDefaultCollection = false)] [ConfigurationCollection(typeof(StandardColumnCollection), AddItemName = "remove")] public StandardColumnCollection RemoveStandardColumns { - get => (StandardColumnCollection)base["RemoveStandardColumns"]; + get => (StandardColumnCollection)base[nameof(RemoveStandardColumns)]; } [ConfigurationProperty("Columns", IsDefaultCollection = false)] [ConfigurationCollection(typeof(ColumnCollection), AddItemName = "add")] public ColumnCollection Columns { - get => (ColumnCollection)base["Columns"]; + get => (ColumnCollection)base[nameof(Columns)]; } [ConfigurationProperty("Exception")] public StandardColumnConfigException Exception { - get => (StandardColumnConfigException)base["Exception"]; + get => (StandardColumnConfigException)base[nameof(Exception)]; } [ConfigurationProperty("Id")] public StandardColumnConfigId Id { - get => (StandardColumnConfigId)base["Id"]; + get => (StandardColumnConfigId)base[nameof(Id)]; } [ConfigurationProperty("Level")] public StandardColumnConfigLevel Level { - get => (StandardColumnConfigLevel)base["Level"]; + get => (StandardColumnConfigLevel)base[nameof(Level)]; } [ConfigurationProperty("LogEvent")] public StandardColumnConfigLogEvent LogEvent { - get => (StandardColumnConfigLogEvent)base["LogEvent"]; + get => (StandardColumnConfigLogEvent)base[nameof(LogEvent)]; } [ConfigurationProperty("Message")] public StandardColumnConfigMessage Message { - get => (StandardColumnConfigMessage)base["Message"]; + get => (StandardColumnConfigMessage)base[nameof(Message)]; } [ConfigurationProperty("MessageTemplate")] public StandardColumnConfigMessageTemplate MessageTemplate { - get => (StandardColumnConfigMessageTemplate)base["MessageTemplate"]; + get => (StandardColumnConfigMessageTemplate)base[nameof(MessageTemplate)]; } // Name changed to avoid conflict with Properties in ConfigurationElement base class @@ -134,7 +134,7 @@ public StandardColumnConfigProperties PropertiesColumn [ConfigurationProperty("TimeStamp")] public StandardColumnConfigTimeStamp TimeStamp { - get => (StandardColumnConfigTimeStamp)base["TimeStamp"]; + get => (StandardColumnConfigTimeStamp)base[nameof(TimeStamp)]; } } } diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfig.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfig.cs index 15130a02..11984ea2 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfig.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfig.cs @@ -13,8 +13,8 @@ public StandardColumnConfig() [ConfigurationProperty("Name", IsRequired = true, IsKey = true)] public string Name { - get { return (string)this["Name"]; } - set { this["Name"] = value; } + get { return (string)this[nameof(Name)]; } + set { this[nameof(Name)] = value; } } } } diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigLevel.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigLevel.cs index 779bc389..9ec25199 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigLevel.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigLevel.cs @@ -21,10 +21,10 @@ public override string ColumnName [ConfigurationProperty("StoreAsEnum")] public string StoreAsEnum { - get => (string)base["StoreAsEnum"]; + get => (string)base[nameof(StoreAsEnum)]; set { - base["StoreAsEnum"] = value; + base[nameof(StoreAsEnum)] = value; } } diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigLogEvent.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigLogEvent.cs index 463e8baf..6ed363fe 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigLogEvent.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigLogEvent.cs @@ -21,20 +21,20 @@ public override string ColumnName [ConfigurationProperty("ExcludeAdditionalProperties")] public string ExcludeAdditionalProperties { - get => (string)base["ExcludeAdditionalProperties"]; + get => (string)base[nameof(ExcludeAdditionalProperties)]; set { - base["ExcludeAdditionalProperties"] = value; + base[nameof(ExcludeAdditionalProperties)] = value; } } [ConfigurationProperty("ExcludeStandardColumns")] public string ExcludeStandardColumns { - get => (string)base["ExcludeStandardColumns"]; + get => (string)base[nameof(ExcludeStandardColumns)]; set { - base["ExcludeStandardColumns"] = value; + base[nameof(ExcludeStandardColumns)] = value; } } } diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigProperties.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigProperties.cs index 0f279f33..08506f60 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigProperties.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigProperties.cs @@ -21,120 +21,120 @@ public override string ColumnName [ConfigurationProperty("ExcludeAdditionalProperties")] public string ExcludeAdditionalProperties { - get => (string)base["ExcludeAdditionalProperties"]; + get => (string)base[nameof(ExcludeAdditionalProperties)]; set { - base["ExcludeAdditionalProperties"] = value; + base[nameof(ExcludeAdditionalProperties)] = value; } } [ConfigurationProperty("DictionaryElementName")] public string DictionaryElementName { - get => (string)base["DictionaryElementName"]; + get => (string)base[nameof(DictionaryElementName)]; set { - base["DictionaryElementName"] = value; + base[nameof(DictionaryElementName)] = value; } } [ConfigurationProperty("ItemElementName")] public string ItemElementName { - get => (string)base["ItemElementName"]; + get => (string)base[nameof(ItemElementName)]; set { - base["ItemElementName"] = value; + base[nameof(ItemElementName)] = value; } } [ConfigurationProperty("OmitDictionaryContainerElement")] public string OmitDictionaryContainerElement { - get => (string)base["OmitDictionaryContainerElement"]; + get => (string)base[nameof(OmitDictionaryContainerElement)]; set { - base["OmitDictionaryContainerElement"] = value; + base[nameof(OmitDictionaryContainerElement)] = value; } } [ConfigurationProperty("OmitSequenceContainerElement")] public string OmitSequenceContainerElement { - get => (string)base["OmitSequenceContainerElement"]; + get => (string)base[nameof(OmitSequenceContainerElement)]; set { - base["OmitSequenceContainerElement"] = value; + base[nameof(OmitSequenceContainerElement)] = value; } } [ConfigurationProperty("OmitStructureContainerElement")] public string OmitStructureContainerElement { - get => (string)base["OmitStructureContainerElement"]; + get => (string)base[nameof(OmitStructureContainerElement)]; set { - base["OmitStructureContainerElement"] = value; + base[nameof(OmitStructureContainerElement)] = value; } } [ConfigurationProperty("OmitElementIfEmpty")] public string OmitElementIfEmpty { - get => (string)base["OmitElementIfEmpty"]; + get => (string)base[nameof(OmitElementIfEmpty)]; set { - base["OmitElementIfEmpty"] = value; + base[nameof(OmitElementIfEmpty)] = value; } } [ConfigurationProperty("PropertyElementName")] public string PropertyElementName { - get => (string)base["PropertyElementName"]; + get => (string)base[nameof(PropertyElementName)]; set { - base["PropertyElementName"] = value; + base[nameof(PropertyElementName)] = value; } } [ConfigurationProperty("RootElementName")] public string RootElementName { - get => (string)base["RootElementName"]; + get => (string)base[nameof(RootElementName)]; set { - base["RootElementName"] = value; + base[nameof(RootElementName)] = value; } } [ConfigurationProperty("SequenceElementName")] public string SequenceElementName { - get => (string)base["SequenceElementName"]; + get => (string)base[nameof(SequenceElementName)]; set { - base["SequenceElementName"] = value; + base[nameof(SequenceElementName)] = value; } } [ConfigurationProperty("StructureElementName")] public string StructureElementName { - get => (string)base["StructureElementName"]; + get => (string)base[nameof(StructureElementName)]; set { - base["StructureElementName"] = value; + base[nameof(StructureElementName)] = value; } } [ConfigurationProperty("UsePropertyKeyAsElementName")] public string UsePropertyKeyAsElementName { - get => (string)base["UsePropertyKeyAsElementName"]; + get => (string)base[nameof(UsePropertyKeyAsElementName)]; set { - base["UsePropertyKeyAsElementName"] = value; + base[nameof(UsePropertyKeyAsElementName)] = value; } } diff --git a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigTimeStamp.cs b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigTimeStamp.cs index 4747c915..cf0b73a3 100644 --- a/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigTimeStamp.cs +++ b/src/Serilog.Sinks.MSSqlServer/Configuration/Implementations/System.Configuration/StandardColumnConfigTimeStamp.cs @@ -21,10 +21,10 @@ public override string ColumnName [ConfigurationProperty("ConvertToUtc")] public string ConvertToUtc { - get => (string)base["ConvertToUtc"]; + get => (string)base[nameof(ConvertToUtc)]; set { - base["ConvertToUtc"] = value; + base[nameof(ConvertToUtc)] = value; } } diff --git a/src/Serilog.Sinks.MSSqlServer/GlobalSuppressions.cs b/src/Serilog.Sinks.MSSqlServer/GlobalSuppressions.cs new file mode 100644 index 00000000..9f84e517 --- /dev/null +++ b/src/Serilog.Sinks.MSSqlServer/GlobalSuppressions.cs @@ -0,0 +1,59 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Major Code Smell", "S2589:Boolean expressions should not be gratuitous", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerAuditSink.#ctor(System.String,System.String,System.IFormatProvider,System.Boolean,Serilog.Sinks.MSSqlServer.ColumnOptions,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerAuditSink.Emit(Serilog.Events.LogEvent)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerAuditSink.Emit(Serilog.Events.LogEvent)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.SetProperty.IfNotNull``1(System.String,Serilog.Sinks.MSSqlServer.SetProperty.PropertySetter{``0})")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.SetProperty.IfNotNull``1(System.String,Serilog.Sinks.MSSqlServer.SetProperty.PropertySetter{``0})")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnOptions.ExceptionColumnOptions")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnOptions.IdColumnOptions")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnOptions.LevelColumnOptions")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnOptions.LogEventColumnOptions")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnOptions.MessageColumnOptions")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnOptions.MessageTemplateColumnOptions")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnOptions.PropertiesColumnOptions")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1034:Nested types should not be visible", Justification = "", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnOptions.TimeStampColumnOptions")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.JsonLogEventFormatter.Format(Serilog.Events.LogEvent,System.IO.TextWriter)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSink.EmitBatchAsync(System.Collections.Generic.IEnumerable{Serilog.Events.LogEvent})~System.Threading.Tasks.Task")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSinkTraits.ConvertPropertiesToXmlStructure(System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,Serilog.Events.LogEventPropertyValue}})~System.String")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSinkTraits.TryChangeType(System.Object,System.Type,System.Object@)~System.Boolean")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.SqlTableCreator.GetColumnDDL(Serilog.Sinks.MSSqlServer.SqlColumn)~System.String")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.XmlPropertyFormatter.Simplify(Serilog.Events.LogEventPropertyValue,Serilog.Sinks.MSSqlServer.ColumnOptions.PropertiesColumnOptions)~System.String")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1305:Specify IFormatProvider", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.XmlPropertyFormatter.SimplifyScalar(System.Object)~System.String")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Vulnerability", "S1104:Fields should not have public accessibility", Justification = "", Scope = "member", Target = "~F:Serilog.Sinks.MSSqlServer.SqlDataTypes.NotSupported")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.LoggerConfigurationMSSqlServerExtensions.MSSqlServer(Serilog.Configuration.LoggerSinkConfiguration,System.String,System.String,Microsoft.Extensions.Configuration.IConfiguration,Serilog.Events.LogEventLevel,System.Int32,System.Nullable{System.TimeSpan},System.IFormatProvider,System.Boolean,Serilog.Sinks.MSSqlServer.ColumnOptions,Microsoft.Extensions.Configuration.IConfigurationSection,System.String)~Serilog.LoggerConfiguration")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSink.#ctor(System.String,System.String,System.Int32,System.TimeSpan,System.IFormatProvider,System.Boolean,Serilog.Sinks.MSSqlServer.ColumnOptions,System.String)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSink.EmitBatchAsync(System.Collections.Generic.IEnumerable{Serilog.Events.LogEvent})~System.Threading.Tasks.Task")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.SqlColumn.#ctor(System.Data.DataColumn)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.XmlPropertyFormatter.Simplify(Serilog.Events.LogEventPropertyValue,Serilog.Sinks.MSSqlServer.ColumnOptions.PropertiesColumnOptions)~System.String")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSink.EmitBatchAsync(System.Collections.Generic.IEnumerable{Serilog.Events.LogEvent})~System.Threading.Tasks.Task")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSinkTraits.#ctor(System.String,System.String,System.String,Serilog.Sinks.MSSqlServer.ColumnOptions,System.IFormatProvider,System.Boolean)")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSinkTraits.ConvertPropertiesToXmlStructure(System.Collections.Generic.IEnumerable{System.Collections.Generic.KeyValuePair{System.String,Serilog.Events.LogEventPropertyValue}})~System.String")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.MSSqlServerSinkTraits.TryChangeType(System.Object,System.Type,System.Object@)~System.Boolean")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.SqlColumn.AsDataColumn~System.Data.DataColumn")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S1125:Boolean literals should not be redundant", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.ColumnOptions.FinalizeConfigurationForSinkConstructor")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S1125:Boolean literals should not be redundant", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.JsonLogEventFormatter.Format(Serilog.Events.LogEvent,System.IO.TextWriter)")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Info Code Smell", "S1135:Track uses of \"TODO\" tags", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.ApplyMicrosoftExtensionsConfiguration.ConfigureColumnOptions(Serilog.Sinks.MSSqlServer.ColumnOptions,Microsoft.Extensions.Configuration.IConfigurationSection)~Serilog.Sinks.MSSqlServer.ColumnOptions")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.LoggerConfigurationMSSqlServerExtensions.MSSqlServer(Serilog.Configuration.LoggerAuditSinkConfiguration,System.String,System.String,Microsoft.Extensions.Configuration.IConfiguration,Serilog.Events.LogEventLevel,System.IFormatProvider,System.Boolean,Serilog.Sinks.MSSqlServer.ColumnOptions,Microsoft.Extensions.Configuration.IConfigurationSection,System.String)~Serilog.LoggerConfiguration")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.ColumnCollection.GetElementKey(System.Configuration.ConfigurationElement)~System.Object")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.SetProperty.IfProvided``1(System.Configuration.ConfigurationElement,System.String,Serilog.Sinks.MSSqlServer.SetProperty.PropertySetter{``0})")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.SetProperty.IfProvidedNotEmpty``1(System.Configuration.ConfigurationElement,System.String,Serilog.Sinks.MSSqlServer.SetProperty.PropertySetter{``0})")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.StandardColumnCollection.GetElementKey(System.Configuration.ConfigurationElement)~System.Object")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1010:Collections should implement generic interface", Justification = "", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.ColumnCollection")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1010:Collections should implement generic interface", Justification = "", Scope = "type", Target = "~T:Serilog.Sinks.MSSqlServer.StandardColumnCollection")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Vulnerability", "S1104:Fields should not have public accessibility", Justification = "", Scope = "member", Target = "~F:Serilog.LoggerConfigurationMSSqlServerExtensions.AppConfigSectionName")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "", Scope = "member", Target = "~M:Serilog.LoggerConfigurationMSSqlServerExtensions.MSSqlServer(Serilog.Configuration.LoggerAuditSinkConfiguration,System.String,System.String,Microsoft.Extensions.Configuration.IConfiguration,Serilog.Events.LogEventLevel,System.IFormatProvider,System.Boolean,Serilog.Sinks.MSSqlServer.ColumnOptions,Microsoft.Extensions.Configuration.IConfigurationSection,System.String)~Serilog.LoggerConfiguration")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "", Scope = "member", Target = "~M:Serilog.LoggerConfigurationMSSqlServerExtensions.MSSqlServer(Serilog.Configuration.LoggerSinkConfiguration,System.String,System.String,Microsoft.Extensions.Configuration.IConfiguration,Serilog.Events.LogEventLevel,System.Int32,System.Nullable{System.TimeSpan},System.IFormatProvider,System.Boolean,Serilog.Sinks.MSSqlServer.ColumnOptions,Microsoft.Extensions.Configuration.IConfigurationSection,System.String)~Serilog.LoggerConfiguration")] + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.ApplyMicrosoftExtensionsConfiguration.GetConnectionString(System.String,Microsoft.Extensions.Configuration.IConfiguration)~System.String")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1307:Specify StringComparison", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.ApplySystemConfiguration.GetConnectionString(System.String)~System.String")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA2100:Review SQL queries for security vulnerabilities", Justification = "", Scope = "member", Target = "~M:Serilog.Sinks.MSSqlServer.SqlTableCreator.CreateTable~System.Int32")] \ No newline at end of file diff --git a/src/Serilog.Sinks.MSSqlServer/Serilog.Sinks.MSSqlServer.csproj b/src/Serilog.Sinks.MSSqlServer/Serilog.Sinks.MSSqlServer.csproj index 230e71b0..f9689e1b 100644 --- a/src/Serilog.Sinks.MSSqlServer/Serilog.Sinks.MSSqlServer.csproj +++ b/src/Serilog.Sinks.MSSqlServer/Serilog.Sinks.MSSqlServer.csproj @@ -4,7 +4,7 @@ A Serilog sink that writes events to Microsoft SQL Server 5.1.3 Michiel van Oudheusden;Serilog Contributors - netstandard2.0;net452;netcoreapp2.0;net461 + netstandard2.0;netcoreapp2.0;net461 true true Serilog.Sinks.MSSqlServer @@ -22,12 +22,16 @@ false false false + true + false + false + - - - + + + @@ -45,34 +49,23 @@ - + - - - + + + - - - - - + + + + + - - - - - - - - - - - diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/FinalizeConfigurationForSinkConstructor.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/FinalizeConfigurationForSinkConstructor.cs index d290162d..1f2f428c 100644 --- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/FinalizeConfigurationForSinkConstructor.cs +++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/FinalizeConfigurationForSinkConstructor.cs @@ -75,7 +75,7 @@ internal void FinalizeConfigurationForSinkConstructor() configurationFinalized = true; } - private void ColumnstoreCompatibilityCheck(SqlColumn column) + private static void ColumnstoreCompatibilityCheck(SqlColumn column) { if (!SqlDataTypes.ColumnstoreCompatible.Contains(column.DataType)) throw new ArgumentException($"Columnstore indexes do not support data type \"{column.DataType.ToString()}\" declared for column \"{column.ColumnName}\"."); diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/IdColumnOptions.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/IdColumnOptions.cs index 0dd2cf6c..4118a478 100644 --- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/IdColumnOptions.cs +++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/ColumnOptions/IdColumnOptions.cs @@ -38,7 +38,7 @@ public IdColumnOptions() : base() /// /// The Id column must never allow null values (it is an auto-incremnting identity value and normally the primary key). /// - public new bool AllowNull // shadow base class with "new" to prevent accidentally setting this to true + public new static bool AllowNull // shadow base class with "new" to prevent accidentally setting this to true { get => false; set diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/JsonLogEventFormatter.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/JsonLogEventFormatter.cs index ba427d11..6b53a655 100644 --- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/JsonLogEventFormatter.cs +++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/JsonLogEventFormatter.cs @@ -31,7 +31,7 @@ internal class JsonLogEventFormatter : ITextFormatter static readonly JsonValueFormatter ValueFormatter = new JsonValueFormatter(typeTagName: null); private const string COMMA_DELIMITER = ","; - MSSqlServerSinkTraits traits; + readonly MSSqlServerSinkTraits traits; /// /// Constructor. A reference to the parent Traits object is used so that JSON @@ -142,25 +142,28 @@ static void WriteRenderings(IEnumerable> tokens output.Write(":["); var fdelim = ""; - foreach (var format in ptoken) + var sw = new StringWriter(); + using (sw) { - output.Write(fdelim); - fdelim = COMMA_DELIMITER; + foreach (var format in ptoken) + { + output.Write(fdelim); + fdelim = COMMA_DELIMITER; + + output.Write("{\"Format\":"); + JsonValueFormatter.WriteQuotedJsonString(format.Format, output); - output.Write("{\"Format\":"); - JsonValueFormatter.WriteQuotedJsonString(format.Format, output); + output.Write(",\"Rendering\":"); + format.Render(properties, sw); + JsonValueFormatter.WriteQuotedJsonString(sw.ToString(), output); + output.Write('}'); + } - output.Write(",\"Rendering\":"); - var sw = new StringWriter(); - format.Render(properties, sw); - JsonValueFormatter.WriteQuotedJsonString(sw.ToString(), output); - output.Write('}'); + output.Write(']'); } - output.Write(']'); + output.Write('}'); } - - output.Write('}'); } } } diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerAuditSink.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerAuditSink.cs index 19669782..7fddadca 100644 --- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerAuditSink.cs +++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerAuditSink.cs @@ -18,8 +18,8 @@ using System; using System.Data; using System.Data.SqlClient; -using System.Linq; using System.Text; +using Microsoft.Azure.Services.AppAuthentication; namespace Serilog.Sinks.MSSqlServer { @@ -40,25 +40,32 @@ public class MSSqlServerAuditSink : ILogEventSink, IDisposable /// Supplies culture-specific formatting information, or null. /// Create log table with the provided name on destination sql server. /// Options that pertain to columns + /// Option to use MSI + /// Resource required in AzureServiceTokenProvider.GetAccessTokenAsync(azureServiceTokenProviderResource). This will error if null, and useMsi is st to true public MSSqlServerAuditSink( string connectionString, string tableName, IFormatProvider formatProvider, bool autoCreateSqlTable = false, ColumnOptions columnOptions = null, - string schemaName = "dbo" - ) + string schemaName = "dbo", + bool useMsi = false, + string azureServiceTokenProviderResource = null) { columnOptions.FinalizeConfigurationForSinkConstructor(); +#pragma warning disable S2589 // Boolean expressions should not be gratuitous if (columnOptions != null) +#pragma warning restore S2589 // Boolean expressions should not be gratuitous { +#pragma warning disable S1066 // Collapsible "if" statements should be merged if (columnOptions.DisableTriggers) +#pragma warning restore S1066 // Collapsible "if" statements should be merged throw new NotSupportedException($"The {nameof(ColumnOptions.DisableTriggers)} option is not supported for auditing."); } - _traits = new MSSqlServerSinkTraits(connectionString, tableName, schemaName, columnOptions, formatProvider, autoCreateSqlTable); - + _traits = new MSSqlServerSinkTraits(connectionString, tableName, schemaName, columnOptions, formatProvider, autoCreateSqlTable, useMsi, azureServiceTokenProviderResource); + } /// Emit the provided log event to the sink. @@ -67,10 +74,16 @@ public void Emit(LogEvent logEvent) { try { - using (SqlConnection connection = new SqlConnection(_traits.connectionString)) + using (var cn = new SqlConnection(_traits.connectionString)) { - connection.Open(); - using (SqlCommand command = connection.CreateCommand()) + if (_traits.useMsi) + { + cn.AccessToken = new AzureServiceTokenProvider() + .GetAccessTokenAsync(_traits.azureServiceTokenProviderResource).Result; + } + + cn.Open(); + using (SqlCommand command = cn.CreateCommand()) { command.CommandType = CommandType.Text; @@ -90,7 +103,7 @@ public void Emit(LogEvent logEvent) parameterList.Append("@P"); parameterList.Append(index); - SqlParameter parameter = new SqlParameter($"@P{index}", field.Value ?? DBNull.Value); + SqlParameter parameter = new SqlParameter($"@P{index}", field.Value ?? DBNull.Value); // The default is SqlDbType.DateTime, which will truncate the DateTime value if the actual // type in the database table is datetime2. So we explicitly set it to DateTime2, which will @@ -117,7 +130,7 @@ public void Emit(LogEvent logEvent) { SelfLog.WriteLine("Unable to write log event to the database due to following error: {1}", ex.Message); throw; - } + } } /// diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSink.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSink.cs index 928864eb..57857e67 100644 --- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSink.cs +++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSink.cs @@ -14,11 +14,11 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Data; using System.Data.SqlClient; using System.Linq; using System.Threading.Tasks; +using Microsoft.Azure.Services.AppAuthentication; using Serilog.Debugging; using Serilog.Events; using Serilog.Sinks.PeriodicBatching; @@ -43,6 +43,8 @@ public class MSSqlServerSink : PeriodicBatchingSink private readonly MSSqlServerSinkTraits _traits; + + /// /// Construct a sink posting to the specified database. /// @@ -54,6 +56,8 @@ public class MSSqlServerSink : PeriodicBatchingSink /// Supplies culture-specific formatting information, or null. /// Create log table with the provided name on destination sql server. /// Options that pertain to columns + /// Option to use MSI + /// Resource required in AzureServiceTokenProvider.GetAccessTokenAsync(azureServiceTokenProviderResource). This will error if null, and useMsi is st to true public MSSqlServerSink( string connectionString, string tableName, @@ -62,12 +66,13 @@ public MSSqlServerSink( IFormatProvider formatProvider, bool autoCreateSqlTable = false, ColumnOptions columnOptions = null, - string schemaName = "dbo" - ) + string schemaName = "dbo", + bool useMsi = false, + string azureServiceTokenProviderResource = null) : base(batchPostingLimit, period) { columnOptions.FinalizeConfigurationForSinkConstructor(); - _traits = new MSSqlServerSinkTraits(connectionString, tableName, schemaName, columnOptions, formatProvider, autoCreateSqlTable); + _traits = new MSSqlServerSinkTraits(connectionString, tableName, schemaName, columnOptions, formatProvider, autoCreateSqlTable, useMsi, azureServiceTokenProviderResource); } /// @@ -88,6 +93,11 @@ protected override async Task EmitBatchAsync(IEnumerable events) { using (var cn = new SqlConnection(_traits.connectionString)) { + if (_traits.useMsi) + { + cn.AccessToken = new AzureServiceTokenProvider() + .GetAccessTokenAsync(_traits.azureServiceTokenProviderResource).Result; + } await cn.OpenAsync().ConfigureAwait(false); using (var copy = _traits.columnOptions.DisableTriggers ? new SqlBulkCopy(cn) diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSinkTraits.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSinkTraits.cs index 71648933..73141433 100644 --- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSinkTraits.cs +++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSinkTraits.cs @@ -17,7 +17,6 @@ using System; using System.Collections.Generic; using System.Data; -using System.IO; using System.Linq; using System.Text; @@ -27,6 +26,8 @@ namespace Serilog.Sinks.MSSqlServer /// Contains common functionality and properties used by both MSSqlServerSinks. internal sealed class MSSqlServerSinkTraits : IDisposable { + public bool useMsi { get; } = false; + public string azureServiceTokenProviderResource { get; } = null; public string connectionString { get; } public string tableName { get; } public string schemaName { get; } @@ -37,14 +38,19 @@ internal sealed class MSSqlServerSinkTraits : IDisposable public DataTable eventTable { get; } public ISet standardColumnNames { get; } - public MSSqlServerSinkTraits(string connectionString, string tableName, string schemaName, ColumnOptions columnOptions, IFormatProvider formatProvider, bool autoCreateSqlTable) + public MSSqlServerSinkTraits(string connectionString, string tableName, string schemaName, ColumnOptions columnOptions, IFormatProvider formatProvider, bool autoCreateSqlTable, bool useMsi = false, string azureServiceTokenProviderResource = null) { + if (useMsi && string.IsNullOrWhiteSpace(azureServiceTokenProviderResource)) + throw new ArgumentNullException(nameof(azureServiceTokenProviderResource), "If useMsi is set to true, you must also provide an azureServiceTokenProviderResource"); + if (string.IsNullOrWhiteSpace(connectionString)) throw new ArgumentNullException(nameof(connectionString)); if (string.IsNullOrWhiteSpace(tableName)) throw new ArgumentNullException(nameof(tableName)); + this.useMsi = useMsi; + this.azureServiceTokenProviderResource = azureServiceTokenProviderResource; this.connectionString = connectionString; this.tableName = tableName; this.schemaName = schemaName; @@ -93,7 +99,7 @@ public IEnumerable> GetColumnsAndValues(LogEvent lo foreach (var column in columnOptions.Store) { // skip Id (auto-incrementing identity) - if(column != StandardColumn.Id) + if (column != StandardColumn.Id) yield return GetStandardColumnNameAndValue(column, logEvent); } @@ -128,7 +134,9 @@ internal KeyValuePair GetStandardColumnNameAndValue(StandardColu case StandardColumn.LogEvent: return new KeyValuePair(columnOptions.LogEvent.ColumnName, LogEventToJson(logEvent)); default: - throw new ArgumentOutOfRangeException(); +#pragma warning disable S3928 // Parameter names used into ArgumentException constructors should match an existing one + throw new ArgumentOutOfRangeException($"{nameof(column)} is an invalid case."); +#pragma warning restore S3928 // Parameter names used into ArgumentException constructors should match an existing one } } @@ -260,13 +268,13 @@ private DataTable CreateDataTable() var standardOpts = columnOptions.GetStandardColumnOptions(standardColumn); var dataColumn = standardOpts.AsDataColumn(); eventsTable.Columns.Add(dataColumn); - if(standardOpts == columnOptions.PrimaryKey) + if (standardOpts == columnOptions.PrimaryKey) eventsTable.PrimaryKey = new DataColumn[] { dataColumn }; } if (columnOptions.AdditionalColumns != null) { - foreach(var addCol in columnOptions.AdditionalColumns) + foreach (var addCol in columnOptions.AdditionalColumns) { var dataColumn = addCol.AsDataColumn(); eventsTable.Columns.Add(dataColumn); diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlColumn.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlColumn.cs index 935cb9b3..1f88c81d 100644 --- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlColumn.cs +++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlColumn.cs @@ -95,10 +95,13 @@ public SqlDbType DataType /// public bool NonClusteredIndex { get; set; } = false; - // Set by the constructors of the Standard Column classes that inherit from this; - // allows Standard Columns and user-defined columns to coexist but remain identifiable - // and allows casting back to the Standard Column without a lot of switch gymnastics. + +#pragma warning disable S125 // Sections of code should not be commented out + // Set by the constructors of the Standard Column classes that inherit from this; + // allows Standard Columns and user-defined columns to coexist but remain identifiable + // and allows casting back to the Standard Column without a lot of switch gymnastics. internal StandardColumn? StandardColumnIdentifier { get; set; } = null; +#pragma warning restore S125 // Sections of code should not be commented out internal Type StandardColumnType { get; set; } = null; /// diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlDataTypes.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlDataTypes.cs index 223b5756..71c12257 100644 --- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlDataTypes.cs +++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlDataTypes.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.SqlTypes; namespace Serilog.Sinks.MSSqlServer { @@ -10,11 +9,15 @@ namespace Serilog.Sinks.MSSqlServer /// public static class SqlDataTypes { +#pragma warning disable S2223 // Non-constant static fields should not be visible +#pragma warning disable CA2211 // Non-constant fields should not be visible /// /// SqlDbType doesn't have anything like "None" so we indicate an unsupported type by /// referencing a type we can guarantee the rest of the sink will never recognize. /// public static SqlDbType NotSupported = SqlDbType.Variant; +#pragma warning restore CA2211 // Non-constant fields should not be visible +#pragma warning restore S2223 // Non-constant static fields should not be visible /// /// A collection keyed on the SqlDbType enum with values representing the equivalent DataColumn .NET type. diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlTableCreator.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlTableCreator.cs index 1601850d..30d7530b 100644 --- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlTableCreator.cs +++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/SqlTableCreator.cs @@ -2,30 +2,40 @@ using System.Data; using System.Data.SqlClient; using System.Text; +using Microsoft.Azure.Services.AppAuthentication; namespace Serilog.Sinks.MSSqlServer { internal class SqlTableCreator { private readonly string connectionString; - private string tableName; - private string schemaName; - private DataTable dataTable; - private ColumnOptions columnOptions; - - public SqlTableCreator(string connectionString, string schemaName, string tableName, DataTable dataTable, ColumnOptions columnOptions) + private readonly string tableName; + private readonly string schemaName; + private readonly DataTable dataTable; + private readonly ColumnOptions columnOptions; + private readonly bool useMsi; + private readonly string azureServiceTokenProviderResource; + + public SqlTableCreator(string connectionString, string schemaName, string tableName, DataTable dataTable, ColumnOptions columnOptions, bool useMsi = false, string azureServiceTokenProviderResource = null) { this.connectionString = connectionString; this.schemaName = schemaName; this.tableName = tableName; this.dataTable = dataTable; this.columnOptions = columnOptions; + this.useMsi = useMsi; + this.azureServiceTokenProviderResource = azureServiceTokenProviderResource; } public int CreateTable() { - using (var conn = new SqlConnection(connectionString)) + using (var conn = new SqlConnection(this.connectionString)) { + if (useMsi) + { + conn.AccessToken = new AzureServiceTokenProvider() + .GetAccessTokenAsync(azureServiceTokenProviderResource).Result; + } string sql = GetSqlFromDataTable(); using (SqlCommand cmd = new SqlCommand(sql, conn)) { @@ -94,7 +104,7 @@ private string GetSqlFromDataTable() // Examples of possible output: // [Id] BIGINT IDENTITY(1,1) NOT NULL // [Message] VARCHAR(1024) NULL - private string GetColumnDDL(SqlColumn column) + private static string GetColumnDDL(SqlColumn column) { var sb = new StringBuilder(); diff --git a/src/Serilog.Sinks.MSSqlServer/packages.config b/src/Serilog.Sinks.MSSqlServer/packages.config index 4bb6d6db..569e1bea 100644 --- a/src/Serilog.Sinks.MSSqlServer/packages.config +++ b/src/Serilog.Sinks.MSSqlServer/packages.config @@ -1,5 +1,3 @@  - - diff --git a/test/Serilog.Sinks.MSSqlServer.Tests/App.config b/test/Serilog.Sinks.MSSqlServer.Tests/App.config index 4fa31336..571ecf14 100644 --- a/test/Serilog.Sinks.MSSqlServer.Tests/App.config +++ b/test/Serilog.Sinks.MSSqlServer.Tests/App.config @@ -29,5 +29,9 @@ - + + + + + diff --git a/test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Microsoft.Extensions.Configuration/ConfigurationExtensions.cs b/test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Microsoft.Extensions.Configuration/ConfigurationExtensions.cs index de702e49..381d9678 100644 --- a/test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Microsoft.Extensions.Configuration/ConfigurationExtensions.cs +++ b/test/Serilog.Sinks.MSSqlServer.Tests/Configuration/Microsoft.Extensions.Configuration/ConfigurationExtensions.cs @@ -14,11 +14,15 @@ public class ConfigurationExtensions : IDisposable { static string ConnectionStringName = "NamedConnection"; static string ColumnOptionsSection = "CustomColumnNames"; + static string DatabaseTokenProviderSection = "DatabaseTokenProviderSettings"; IConfiguration TestConfiguration() => new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { + { $"{DatabaseTokenProviderSection}:UseMsi", "true" }, + { $"{DatabaseTokenProviderSection}:Resource", "https://database.windows.net/" }, + { $"ConnectionStrings:{ConnectionStringName}", DatabaseFixture.LogEventsConnectionString }, { $"{ColumnOptionsSection}:message:columnName", "CustomMessage" }, @@ -30,6 +34,26 @@ IConfiguration TestConfiguration() => }) .Build(); + [Fact] + public void AzureTokenProviderResourceByName() + { + var appConfig = TestConfiguration(); + + var loggerConfiguration = new LoggerConfiguration(); + Log.Logger = loggerConfiguration.WriteTo.MSSqlServer( + connectionString: ConnectionStringName, + tableName: DatabaseFixture.LogTableName, + autoCreateSqlTable: true, + appConfiguration: appConfig, + useMsi: true, + azureServiceTokenProviderResource: "DatabaseTokenProviderSettings:Resource") + .CreateLogger(); + + // should not throw + + Log.CloseAndFlush(); + } + [Fact] public void ConnectionStringByName() { diff --git a/test/Serilog.Sinks.MSSqlServer.Tests/Configuration/System.Configuration/ConfigurationExtensions.cs b/test/Serilog.Sinks.MSSqlServer.Tests/Configuration/System.Configuration/ConfigurationExtensions.cs index 273c7b70..0036959f 100644 --- a/test/Serilog.Sinks.MSSqlServer.Tests/Configuration/System.Configuration/ConfigurationExtensions.cs +++ b/test/Serilog.Sinks.MSSqlServer.Tests/Configuration/System.Configuration/ConfigurationExtensions.cs @@ -1,10 +1,12 @@ -using System; +using System.Collections.Generic; +using System.Data.SqlClient; +using System.Linq; using Dapper; using Xunit; using FluentAssertions; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; +using Microsoft.Extensions.Configuration; +using System; + // Because System.Configuration is static and config is loaded automatically, // the tests alter the static AppConfigSectionName string value exposed by the @@ -18,6 +20,26 @@ namespace Serilog.Sinks.MSSqlServer.Tests [Collection("LogTest")] public class ConfigurationExtensions : IDisposable { + [Fact] + public void AzureTokenProviderResourceByName() + { + string ConnectionStringName = "NamedConnection"; + string DatabaseTokenProviderResourceName = "DatabaseTokenProviderResource"; + + var loggerConfiguration = new LoggerConfiguration(); + Log.Logger = loggerConfiguration.WriteTo.MSSqlServer( + connectionString: ConnectionStringName, + tableName: DatabaseFixture.LogTableName, + autoCreateSqlTable: true, + useMsi: true, + azureServiceTokenProviderResource: DatabaseTokenProviderResourceName) + .CreateLogger(); + + // should not throw + + Log.CloseAndFlush(); + } + [Fact] public void ConnectionStringByName() { diff --git a/test/Serilog.Sinks.MSSqlServer.Tests/Serilog.Sinks.MSSqlServer.Tests.csproj b/test/Serilog.Sinks.MSSqlServer.Tests/Serilog.Sinks.MSSqlServer.Tests.csproj index 09b851a4..7c8d7959 100644 --- a/test/Serilog.Sinks.MSSqlServer.Tests/Serilog.Sinks.MSSqlServer.Tests.csproj +++ b/test/Serilog.Sinks.MSSqlServer.Tests/Serilog.Sinks.MSSqlServer.Tests.csproj @@ -13,24 +13,30 @@ --> - netcoreapp2.0;net452 - netstandard2.0 + netcoreapp2.1 + netstandard2.0 Serilog.Sinks.MSSqlServer.Tests - ../../assets/Serilog.snk - true + Serilog.snk + false true Serilog.Sinks.MSSqlServer.Tests true win + false - - - + + - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -41,30 +47,26 @@ - - - - - - - - - + - + - - - + + + all runtime; build; native; contentfiles; analyzers + + + + Always diff --git a/test/Serilog.Sinks.MSSqlServer.Tests/TestTriggersOnLogTable.cs b/test/Serilog.Sinks.MSSqlServer.Tests/TestTriggersOnLogTable.cs index 61a5809c..e77bcd9c 100644 --- a/test/Serilog.Sinks.MSSqlServer.Tests/TestTriggersOnLogTable.cs +++ b/test/Serilog.Sinks.MSSqlServer.Tests/TestTriggersOnLogTable.cs @@ -99,6 +99,7 @@ public void TestAuditTriggerOnLogTableFire() logTriggerEvents.Should().NotBeNullOrEmpty(); } + Assert.True(true); } [Fact] diff --git a/test/Serilog.Sinks.MSSqlServer.Tests/TestUseMsi.cs b/test/Serilog.Sinks.MSSqlServer.Tests/TestUseMsi.cs new file mode 100644 index 00000000..e3876bf9 --- /dev/null +++ b/test/Serilog.Sinks.MSSqlServer.Tests/TestUseMsi.cs @@ -0,0 +1,45 @@ +using System; +using Xunit; + +namespace Serilog.Sinks.MSSqlServer.Tests +{ + [Collection("LogTest")] + public class TestUseMsi : IDisposable + { + [Fact] + public void TestIfUseMsiTrueAndAzureServiceTokenProviderResourceNotSet_ThrowsArgumentNullException() + { + // arrange + var loggerConfiguration = new LoggerConfiguration(); + Assert.Throws(() => loggerConfiguration.AuditTo.MSSqlServer( + connectionString: DatabaseFixture.LogEventsConnectionString, + tableName: DatabaseFixture.LogTableName, + autoCreateSqlTable: true, + columnOptions: new ColumnOptions(), + useMsi: true) + .CreateLogger()); + } + + + [Fact] + public void TestIfUseMsiTrueAndAzureServiceTokenProviderResourceIsSet_CreatesLogger() + { + // arrange + var loggerConfiguration = new LoggerConfiguration(); + var logger = loggerConfiguration.AuditTo.MSSqlServer( + connectionString: DatabaseFixture.LogEventsConnectionString, + tableName: DatabaseFixture.LogTableName, + autoCreateSqlTable: true, + columnOptions: new ColumnOptions(), + useMsi: true, + azureServiceTokenProviderResource: "http://a.com") + .CreateLogger(); + Assert.True(true); + } + + public void Dispose() + { + DatabaseFixture.DropTable(); + } + } +}