From b1b22c4b73d93e56ecd5d2500464183e448d58e1 Mon Sep 17 00:00:00 2001
From: Nathan Hart <nhart12@gmail.com>
Date: Sun, 16 Mar 2025 16:08:56 -0400
Subject: [PATCH 1/2] SqlConnection config action

Signed-off-by: Nathan Hart <nhart12@gmail.com>
---
 .../MSSqlServer/Dependencies/SinkDependenciesFactory.cs   | 4 ++--
 .../Sinks/MSSqlServer/MSSqlServerSinkOptions.cs           | 6 ++++++
 .../Platform/SqlClient/SqlConnectionWrapper.cs            | 6 +++++-
 .../Sinks/MSSqlServer/Platform/SqlConnectionFactory.cs    | 8 ++++++--
 4 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactory.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactory.cs
index f9a5cee7..ae84c4fd 100644
--- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactory.cs
+++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactory.cs
@@ -22,7 +22,7 @@ internal static SinkDependencies Create(
             // unless sink option EnlistInTransaction is set to true.
             var sqlConnectionStringBuilderWrapper = new SqlConnectionStringBuilderWrapper(
                 connectionString, sinkOptions.EnlistInTransaction);
-            var sqlConnectionFactory = new SqlConnectionFactory(sqlConnectionStringBuilderWrapper);
+            var sqlConnectionFactory = new SqlConnectionFactory(sqlConnectionStringBuilderWrapper, sinkOptions.ConnectionConfiguration);
             var sqlCommandFactory = new SqlCommandFactory();
             var dataTableCreator = new DataTableCreator(sinkOptions.TableName, columnOptions);
             var sqlCreateTableWriter = new SqlCreateTableWriter(sinkOptions.SchemaName,
@@ -34,7 +34,7 @@ internal static SinkDependencies Create(
                 InitialCatalog = ""
             };
             var sqlConnectionFactoryNoDb =
-                new SqlConnectionFactory(sqlConnectionStringBuilderWrapperNoDb);
+                new SqlConnectionFactory(sqlConnectionStringBuilderWrapperNoDb, sinkOptions.ConnectionConfiguration);
             var sqlCreateDatabaseWriter = new SqlCreateDatabaseWriter(sqlConnectionStringBuilderWrapper.InitialCatalog);
 
             var logEventDataGenerator =
diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSinkOptions.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSinkOptions.cs
index c53dba43..34c16445 100644
--- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSinkOptions.cs
+++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/MSSqlServerSinkOptions.cs
@@ -1,4 +1,5 @@
 using System;
+using Microsoft.Data.SqlClient;
 using Serilog.Core;
 
 namespace Serilog.Sinks.MSSqlServer
@@ -83,5 +84,10 @@ internal MSSqlServerSinkOptions(
         /// Flag to use <see cref="Microsoft.Data.SqlClient.SqlBulkCopy"/> instead of individual INSERT statements (default: true)
         /// </summary>
         public bool UseSqlBulkCopy { get; set; }
+
+        /// <summary>
+        /// Supplies an extension point for customizing the underyling SqlConnection
+        /// </summary>
+        public Action<SqlConnection> ConnectionConfiguration { get; set; }
     }
 }
diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/SqlConnectionWrapper.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/SqlConnectionWrapper.cs
index 638ae47d..5b398f8f 100644
--- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/SqlConnectionWrapper.cs
+++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlClient/SqlConnectionWrapper.cs
@@ -9,9 +9,13 @@ internal class SqlConnectionWrapper : ISqlConnectionWrapper
         private readonly SqlConnection _sqlConnection;
         private bool _disposedValue;
 
-        public SqlConnectionWrapper(string connectionString)
+        public SqlConnectionWrapper(string connectionString, Action<SqlConnection> connectionConfiguration = null)
         {
             _sqlConnection = new SqlConnection(connectionString);
+            if (connectionConfiguration != null)
+            {
+                connectionConfiguration(_sqlConnection);
+            }
         }
 
         public SqlConnection SqlConnection => _sqlConnection;
diff --git a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlConnectionFactory.cs b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlConnectionFactory.cs
index b18f7878..004e1d84 100644
--- a/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlConnectionFactory.cs
+++ b/src/Serilog.Sinks.MSSqlServer/Sinks/MSSqlServer/Platform/SqlConnectionFactory.cs
@@ -1,4 +1,5 @@
 using System;
+using Microsoft.Data.SqlClient;
 using Serilog.Sinks.MSSqlServer.Platform.SqlClient;
 
 namespace Serilog.Sinks.MSSqlServer.Platform
@@ -7,18 +8,21 @@ internal class SqlConnectionFactory : ISqlConnectionFactory
     {
         private readonly string _connectionString;
         private readonly ISqlConnectionStringBuilderWrapper _sqlConnectionStringBuilderWrapper;
+        private readonly Action<SqlConnection> _connectionConfiguration;
 
-        public SqlConnectionFactory(ISqlConnectionStringBuilderWrapper sqlConnectionStringBuilderWrapper)
+        public SqlConnectionFactory(ISqlConnectionStringBuilderWrapper sqlConnectionStringBuilderWrapper,
+            Action<SqlConnection> connectionConfiguration = null)
         {
             _sqlConnectionStringBuilderWrapper = sqlConnectionStringBuilderWrapper
                 ?? throw new ArgumentNullException(nameof(sqlConnectionStringBuilderWrapper));
 
             _connectionString = _sqlConnectionStringBuilderWrapper.ConnectionString;
+            _connectionConfiguration = connectionConfiguration;
         }
 
         public ISqlConnectionWrapper Create()
         {
-            return new SqlConnectionWrapper(_connectionString);
+            return new SqlConnectionWrapper(_connectionString, _connectionConfiguration);
         }
     }
 }

From ec3422e04a26b129e99022064c39ac3099a6776f Mon Sep 17 00:00:00 2001
From: Nathan Hart <nhart12@gmail.com>
Date: Mon, 17 Mar 2025 09:30:10 -0400
Subject: [PATCH 2/2] Readme and some basic tests

---
 README.md                                     |  5 +++++
 .../SinkDependenciesFactoryTests.cs           | 19 ++++++++++++++++++-
 .../Platform/SqlConnectionFactoryTests.cs     | 19 +++++++++++++++++++
 3 files changed, 42 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 7a0b7deb..686abcc4 100644
--- a/README.md
+++ b/README.md
@@ -249,6 +249,7 @@ Basic settings of the sink are configured using the properties in a `MSSqlServer
 * `EagerlyEmitFirstEvent`
 * `LevelSwitch`
 * `UseSqlBulkCopy`
+* `ConnectionConfiguration`
 
 ### TableName
 
@@ -299,6 +300,10 @@ A switch allowing the pass-through minimum level to be changed at runtime. If th
 A flag to use `SqlBulkCopy` instead of individual INSERT statements when writing log events.  The default is `true`.
 This setting is not used by the audit sink as it always uses INSERT statements to write events.
 
+### ConnectionConfiguration
+
+An optional action to customize the underlying SqlConnection object. Can be used to set properties such as `AccessTokenCallback`.
+
 ## ColumnOptions Object
 
 Features of the log table are defined by changing properties on a `ColumnOptions` object:
diff --git a/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactoryTests.cs b/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactoryTests.cs
index 72d1bd38..91fd202c 100644
--- a/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactoryTests.cs
+++ b/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Dependencies/SinkDependenciesFactoryTests.cs
@@ -1,7 +1,10 @@
-using Serilog.Sinks.MSSqlServer.Dependencies;
+using Moq;
+using System;
+using Serilog.Sinks.MSSqlServer.Dependencies;
 using Serilog.Sinks.MSSqlServer.Platform;
 using Serilog.Sinks.MSSqlServer.Tests.TestUtils;
 using Xunit;
+using Microsoft.Data.SqlClient;
 
 namespace Serilog.Sinks.MSSqlServer.Tests.Dependencies
 {
@@ -68,5 +71,19 @@ public void DefaultsColumnOptionsIfNull()
             // Act (should not throw)
             SinkDependenciesFactory.Create(_connectionString, _sinkOptions, null, null, null);
         }
+
+        [Fact]
+        public void CreatesSinkDependenciesWithSqlConnectionConfiguration()
+        {
+            // Arrange
+            var mockConfigurationAction = new Mock<Action<SqlConnection>>();
+            var sinkOptions = new MSSqlServerSinkOptions { TableName = "LogEvents", ConnectionConfiguration = mockConfigurationAction.Object };
+
+            // Act
+            var result = SinkDependenciesFactory.Create(_connectionString, sinkOptions, null, _columnOptions, null);
+
+            // Assert
+            Assert.NotNull(result.SqlDatabaseCreator);
+        }
     }
 }
diff --git a/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/SqlConnectionFactoryTests.cs b/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/SqlConnectionFactoryTests.cs
index f14674f1..6f49e1c7 100644
--- a/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/SqlConnectionFactoryTests.cs
+++ b/test/Serilog.Sinks.MSSqlServer.Tests/Sinks/MSSqlServer/Platform/SqlConnectionFactoryTests.cs
@@ -1,4 +1,5 @@
 using System;
+using Microsoft.Data.SqlClient;
 using Moq;
 using Serilog.Sinks.MSSqlServer.Platform;
 using Serilog.Sinks.MSSqlServer.Platform.SqlClient;
@@ -38,5 +39,23 @@ public void CreateConnectionReturnsConnectionWrapper()
                 Assert.IsAssignableFrom<ISqlConnectionWrapper>(connection);
             }
         }
+
+
+        [Fact]
+        public void CreateConnectionCallsCustomConfigurationAction()
+        {
+            // Arrange
+            var mockAction = new Mock<Action<SqlConnection>>();
+            var sut = new SqlConnectionFactory(_sqlConnectionStringBuilderWrapperMock.Object, mockAction.Object);
+
+            // Act
+            using (var connection = sut.Create())
+            {
+                // Assert
+                Assert.NotNull(connection);
+                Assert.IsAssignableFrom<ISqlConnectionWrapper>(connection);
+                mockAction.Verify(m => m.Invoke(connection.SqlConnection), Times.Once);
+            }
+        }
     }
 }