Skip to content

Sql Connection factory #609

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

using System;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Serilog.Configuration;
using Serilog.Debugging;
Expand Down Expand Up @@ -167,6 +168,85 @@ internal static LoggerConfiguration MSSqlServerInternal(
return loggerConfiguration.Sink(periodicBatchingSink, restrictedToMinimumLevel, sinkOptions?.LevelSwitch);
}

/// <summary>
/// Adds a sink that writes log events to a table in a MSSqlServer database.
/// Create a database and execute the table creation script found here
/// https://gist.github.com/mivano/10429656
/// or use the autoCreateSqlTable option.
/// </summary>
/// <param name="loggerConfiguration">The logger configuration.</param>
/// <param name="sqlConnectionFactory">A function to initialize a connection to the database where to store the events.</param>
/// <param name="initialCatalog">The initial catalog within the database (used if AutoCreateSqlDatabase is enabled).</param>
/// <param name="sinkOptions">Supplies additional settings for the sink</param>
/// <param name="sinkOptionsSection">A config section defining additional settings for the sink</param>
/// <param name="appConfiguration">Additional application-level configuration. Required if connectionString is a name.</param>
/// <param name="restrictedToMinimumLevel">The minimum level for events passed through the sink. Ignored when LevelSwitch in <paramref name="sinkOptions"/> is specified.</param>
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
/// <param name="columnOptions">An externally-modified group of column settings</param>
/// <param name="columnOptionsSection">A config section defining various column settings</param>
/// <param name="logEventFormatter">Supplies custom formatter for the LogEvent column, or null</param>
/// <returns>Logger configuration, allowing configuration to continue.</returns>
/// <exception cref="ArgumentNullException">A required parameter is null.</exception>
public static LoggerConfiguration MSSqlServer(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just add the func parameter to the existing constructor since we already have a number of constructors here (some obsolete even).

this LoggerSinkConfiguration loggerConfiguration,
Func<SqlConnection> sqlConnectionFactory,
string initialCatalog,
MSSqlServerSinkOptions sinkOptions = null,
IConfigurationSection sinkOptionsSection = null,
IConfiguration appConfiguration = null,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
IFormatProvider formatProvider = null,
ColumnOptions columnOptions = null,
IConfigurationSection columnOptionsSection = null,
ITextFormatter logEventFormatter = null) =>
loggerConfiguration.MSSqlServerInternal(
sqlConnectionFactory: sqlConnectionFactory,
initialCatalog: initialCatalog,
sinkOptions: sinkOptions,
sinkOptionsSection: sinkOptionsSection,
appConfiguration: appConfiguration,
restrictedToMinimumLevel: restrictedToMinimumLevel,
formatProvider: formatProvider,
columnOptions: columnOptions,
columnOptionsSection: columnOptionsSection,
logEventFormatter: logEventFormatter,
applySystemConfiguration: new ApplySystemConfiguration(),
applyMicrosoftExtensionsConfiguration: new ApplyMicrosoftExtensionsConfiguration(),
sinkFactory: new MSSqlServerSinkFactory(),
batchingSinkFactory: new PeriodicBatchingSinkFactory());

// Internal overload with parameters used by tests to override the config section and inject mocks
internal static LoggerConfiguration MSSqlServerInternal(
this LoggerSinkConfiguration loggerConfiguration,
Func<SqlConnection> sqlConnectionFactory,
string initialCatalog,
MSSqlServerSinkOptions sinkOptions,
IConfigurationSection sinkOptionsSection,
IConfiguration appConfiguration,
LogEventLevel restrictedToMinimumLevel,
IFormatProvider formatProvider,
ColumnOptions columnOptions,
IConfigurationSection columnOptionsSection,
ITextFormatter logEventFormatter,
IApplySystemConfiguration applySystemConfiguration,
IApplyMicrosoftExtensionsConfiguration applyMicrosoftExtensionsConfiguration,
IMSSqlServerSinkFactory sinkFactory,
IPeriodicBatchingSinkFactory batchingSinkFactory)
{
if (loggerConfiguration == null)
throw new ArgumentNullException(nameof(loggerConfiguration));

ReadConfiguration(ref sinkOptions, sinkOptionsSection, appConfiguration,
ref columnOptions, columnOptionsSection, applySystemConfiguration, applyMicrosoftExtensionsConfiguration);

var sink = sinkFactory.Create(sqlConnectionFactory, initialCatalog, sinkOptions, formatProvider, columnOptions, logEventFormatter);

var periodicBatchingSink = batchingSinkFactory.Create(sink, sinkOptions);

return loggerConfiguration.Sink(periodicBatchingSink, restrictedToMinimumLevel, sinkOptions?.LevelSwitch);
}


/// <summary>
/// Adds a sink that writes log events to a table in a MSSqlServer database.
///
Expand Down Expand Up @@ -313,6 +393,40 @@ private static void ReadConfiguration(
connectionString = applyMicrosoftExtensionsConfiguration.GetConnectionString(connectionString, appConfiguration);
}

if (columnOptionsSection != null)
{
columnOptions = applyMicrosoftExtensionsConfiguration.ConfigureColumnOptions(columnOptions, columnOptionsSection);
}

if (sinkOptionsSection != null)
{
sinkOptions = applyMicrosoftExtensionsConfiguration.ConfigureSinkOptions(sinkOptions, sinkOptionsSection);
}
}

private static void ReadConfiguration(
ref MSSqlServerSinkOptions sinkOptions,
IConfigurationSection sinkOptionsSection,
IConfiguration appConfiguration,
ref ColumnOptions columnOptions,
IConfigurationSection columnOptionsSection,
IApplySystemConfiguration applySystemConfiguration,
IApplyMicrosoftExtensionsConfiguration applyMicrosoftExtensionsConfiguration)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rather check the connection string for null instead of having another overload of the method which is almost identical.

sinkOptions = sinkOptions ?? new MSSqlServerSinkOptions();
columnOptions = columnOptions ?? new ColumnOptions();

var serviceConfigSection = applySystemConfiguration.GetSinkConfigurationSection(AppConfigSectionName);
if (serviceConfigSection != null)
{
columnOptions = applySystemConfiguration.ConfigureColumnOptions(serviceConfigSection, columnOptions);
sinkOptions = applySystemConfiguration.ConfigureSinkOptions(serviceConfigSection, sinkOptions);

if (appConfiguration != null || columnOptionsSection != null || sinkOptionsSection != null)
SelfLog.WriteLine("Warning: Both System.Configuration (app.config or web.config) and Microsoft.Extensions.Configuration are being applied to the MSSQLServer sink.");
}


if (columnOptionsSection != null)
{
columnOptions = applyMicrosoftExtensionsConfiguration.ConfigureColumnOptions(columnOptions, columnOptionsSection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

using System;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Serilog.Configuration;
using Serilog.Events;
Expand Down Expand Up @@ -131,6 +132,54 @@ public static LoggerConfiguration MSSqlServer(
return loggerConfiguration.Sink(periodicBatchingSink, restrictedToMinimumLevel, sinkOptions?.LevelSwitch);
}


/// <summary>
/// Adds a sink that writes log events to a table in a MSSqlServer database.
/// Create a database and execute the table creation script found here
/// https://gist.github.com/mivano/10429656
/// or use the autoCreateSqlTable option.
/// </summary>
/// <param name="loggerConfiguration">The logger configuration.</param>
/// <param name="sqlConnectionFactory">A function to initialize a connection to the database where to store the events.</param>
/// <param name="initialCatalog">The initial catalog within the database (used if AutoCreateSqlDatabase is enabled).</param>
/// <param name="sinkOptions">Supplies additional settings for the sink</param>
/// <param name="sinkOptionsSection">A config section defining additional settings for the sink</param>
/// <param name="appConfiguration">Additional application-level configuration. Required if connectionString is a name.</param>
/// <param name="restrictedToMinimumLevel">The minimum level for events passed through the sink. Ignored when LevelSwitch in <paramref name="sinkOptions"/> is specified.</param>
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
/// <param name="columnOptions">An externally-modified group of column settings</param>
/// <param name="columnOptionsSection">A config section defining various column settings</param>
/// <param name="logEventFormatter">Supplies custom formatter for the LogEvent column, or null</param>
/// <returns>Logger configuration, allowing configuration to continue.</returns>
/// <exception cref="ArgumentNullException">A required parameter is null.</exception>
public static LoggerConfiguration MSSqlServer(
this LoggerSinkConfiguration loggerConfiguration,
Func<SqlConnection> sqlConnectionFactory,
string initialCatalog,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again: add to existing ctor with default null instead of a new ctor.

MSSqlServerSinkOptions sinkOptions = null,
IConfigurationSection sinkOptionsSection = null,
IConfiguration appConfiguration = null,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
IFormatProvider formatProvider = null,
ColumnOptions columnOptions = null,
IConfigurationSection columnOptionsSection = null,
ITextFormatter logEventFormatter = null)
{
if (loggerConfiguration == null)
throw new ArgumentNullException(nameof(loggerConfiguration));

ReadConfiguration(ref sinkOptions, appConfiguration, ref columnOptions,
columnOptionsSection, sinkOptionsSection);

IMSSqlServerSinkFactory sinkFactory = new MSSqlServerSinkFactory();
var sink = sinkFactory.Create(sqlConnectionFactory, initialCatalog, sinkOptions, formatProvider, columnOptions, logEventFormatter);

IPeriodicBatchingSinkFactory periodicBatchingSinkFactory = new PeriodicBatchingSinkFactory();
var periodicBatchingSink = periodicBatchingSinkFactory.Create(sink, sinkOptions);

return loggerConfiguration.Sink(periodicBatchingSink, restrictedToMinimumLevel, sinkOptions?.LevelSwitch);
}

/// <summary>
/// Adds a sink that writes log events to a table in a MSSqlServer database.
///
Expand Down Expand Up @@ -236,5 +285,20 @@ private static void ReadConfiguration(
columnOptions = microsoftExtensionsConfiguration.ConfigureColumnOptions(columnOptions, columnOptionsSection);
sinkOptions = microsoftExtensionsConfiguration.ConfigureSinkOptions(sinkOptions, sinkOptionsSection);
}

private static void ReadConfiguration(
ref MSSqlServerSinkOptions sinkOptions,
IConfiguration appConfiguration,
ref ColumnOptions columnOptions,
IConfigurationSection columnOptionsSection,
IConfigurationSection sinkOptionsSection)
{
sinkOptions = sinkOptions ?? new MSSqlServerSinkOptions();
columnOptions = columnOptions ?? new ColumnOptions();

IApplyMicrosoftExtensionsConfiguration microsoftExtensionsConfiguration = new ApplyMicrosoftExtensionsConfiguration();
columnOptions = microsoftExtensionsConfiguration.ConfigureColumnOptions(columnOptions, columnOptionsSection);
sinkOptions = microsoftExtensionsConfiguration.ConfigureSinkOptions(sinkOptions, sinkOptionsSection);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using Serilog.Formatting;
using Serilog.Core;
using Microsoft.Data.SqlClient;

namespace Serilog.Sinks.MSSqlServer.Configuration.Factories
{
Expand All @@ -12,5 +13,13 @@ IBatchedLogEventSink Create(
IFormatProvider formatProvider,
ColumnOptions columnOptions,
ITextFormatter logEventFormatter);

IBatchedLogEventSink Create(
Func<SqlConnection> sqlConnectionFactory,
string initialCatalog,
MSSqlServerSinkOptions sinkOptions,
IFormatProvider formatProvider,
ColumnOptions columnOptions,
ITextFormatter logEventFormatter);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using Serilog.Formatting;
using Serilog.Core;
using Microsoft.Data.SqlClient;

namespace Serilog.Sinks.MSSqlServer.Configuration.Factories
{
Expand All @@ -18,5 +19,20 @@ public IBatchedLogEventSink Create(
formatProvider,
columnOptions,
logEventFormatter);

public IBatchedLogEventSink Create(
Func<SqlConnection> sqlConnectionFactory,
string initialCatalog,
MSSqlServerSinkOptions sinkOptions,
IFormatProvider formatProvider,
ColumnOptions columnOptions,
ITextFormatter logEventFormatter) =>
new MSSqlServerSink(
sqlConnectionFactory,
initialCatalog,
sinkOptions,
formatProvider,
columnOptions,
logEventFormatter);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Microsoft.Data.SqlClient;
using Serilog.Formatting;
using Serilog.Sinks.MSSqlServer.Output;
using Serilog.Sinks.MSSqlServer.Platform;
Expand Down Expand Up @@ -60,6 +61,49 @@ internal static SinkDependencies Create(
sqlConnectionFactory, sqlCommandFactory, logEventDataGenerator)
};

return sinkDependencies;
}

internal static SinkDependencies Create(
Func<SqlConnection> sqlConnectionFactory,
string initialCatalog,
MSSqlServerSinkOptions sinkOptions,
IFormatProvider formatProvider,
ColumnOptions columnOptions,
ITextFormatter logEventFormatter)
{
columnOptions = columnOptions ?? new ColumnOptions();
columnOptions.FinalizeConfigurationForSinkConstructor();

var connectionFactory = new SqlConnectionFactory(sqlConnectionFactory);
var sqlCommandFactory = new SqlCommandFactory();
var dataTableCreator = new DataTableCreator(sinkOptions.TableName, columnOptions);
var sqlCreateTableWriter = new SqlCreateTableWriter(sinkOptions.SchemaName,
sinkOptions.TableName, columnOptions, dataTableCreator);

var logEventDataGenerator =
new LogEventDataGenerator(columnOptions,
new StandardColumnDataGenerator(columnOptions, formatProvider,
new XmlPropertyFormatter(),
logEventFormatter),
new AdditionalColumnDataGenerator(
new ColumnSimplePropertyValueResolver(),
new ColumnHierarchicalPropertyValueResolver()));
var sqlCreateDatabaseWriter = new SqlCreateDatabaseWriter(initialCatalog);
var sinkDependencies = new SinkDependencies
{
SqlDatabaseCreator = new SqlDatabaseCreator(
sqlCreateDatabaseWriter, connectionFactory, sqlCommandFactory),
SqlTableCreator = new SqlTableCreator(
sqlCreateTableWriter, connectionFactory, sqlCommandFactory),
SqlBulkBatchWriter = new SqlBulkBatchWriter(
sinkOptions.TableName, sinkOptions.SchemaName, columnOptions.DisableTriggers,
dataTableCreator, connectionFactory, logEventDataGenerator),
SqlLogEventWriter = new SqlInsertStatementWriter(
sinkOptions.TableName, sinkOptions.SchemaName,
connectionFactory, sqlCommandFactory, logEventDataGenerator)
};

return sinkDependencies;
}
}
Expand Down
21 changes: 21 additions & 0 deletions src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Serilog.Sinks.MSSqlServer.Dependencies;
using Serilog.Sinks.MSSqlServer.Platform;
using Serilog.Core;
using Microsoft.Data.SqlClient;

namespace Serilog.Sinks.MSSqlServer
{
Expand Down Expand Up @@ -100,6 +101,26 @@ public MSSqlServerSink(
{
}

/// <summary>
/// Construct a sink posting to the specified database.
/// </summary>
/// <param name="sqlConnectionFactory">Factory to initialize connection to the database.</param>
/// <param name="initialCatalog">The initial catalog within the database (used if AutoCreateSqlDatabase is enabled).</param>
/// <param name="sinkOptions">Supplies additional options for the sink</param>
/// <param name="formatProvider">Supplies culture-specific formatting information, or null.</param>
/// <param name="columnOptions">Options that pertain to columns</param>
/// <param name="logEventFormatter">Supplies custom formatter for the LogEvent column, or null</param>
public MSSqlServerSink(
Func<SqlConnection> sqlConnectionFactory,
string initialCatalog,
MSSqlServerSinkOptions sinkOptions,
IFormatProvider formatProvider = null,
ColumnOptions columnOptions = null,
ITextFormatter logEventFormatter = null)
: this(sinkOptions, SinkDependenciesFactory.Create(sqlConnectionFactory, initialCatalog, sinkOptions, formatProvider, columnOptions, logEventFormatter))
{
}

// Internal constructor with injectable dependencies for better testability
internal MSSqlServerSink(
MSSqlServerSinkOptions sinkOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ public SqlConnectionWrapper(string connectionString)
_sqlConnection = new SqlConnection(connectionString);
}

public SqlConnectionWrapper(Func<SqlConnection> connectionFactory)
{
_sqlConnection = connectionFactory();
}

public SqlConnection SqlConnection => _sqlConnection;

public void Open()
Expand Down
Loading
Loading