using Microsoft.Data.SqlClient;
namespace VanDerHeijden.Logging.Sql;
///
/// Writes log entries to a SQL Server table in bulk using SqlBulkCopy.
/// Expected table schema:
/// CREATE TABLE Logs (
/// Id BIGINT IDENTITY PRIMARY KEY,
/// Timestamp DATETIME2 NOT NULL,
/// Level NVARCHAR(20) NOT NULL,
/// Category NVARCHAR(256) NOT NULL,
/// Message NVARCHAR(MAX) NOT NULL,
/// Exception NVARCHAR(MAX) NULL,
/// Path NVARCHAR(1024) NULL,
/// Method NVARCHAR(10) NULL,
/// ClientIp NVARCHAR(45) NULL,
/// Referer NVARCHAR(2048) NULL,
/// UserAgent NVARCHAR(512) NULL
/// );
///
public sealed class SqlLogWriter(string connectionString, string tableName = "Logs") : IBatchedLogWriter
{
///
/// Bulk-inserts all entries into the configured SQL Server table using .
///
/// The log entries to insert.
/// A token that can cancel the operation.
public async Task WriteBatchAsync(List entries, CancellationToken ct)
{
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(ct);
using var bulkCopy = new SqlBulkCopy(connection)
{
DestinationTableName = tableName,
BulkCopyTimeout = 30
};
bulkCopy.ColumnMappings.Add(nameof(SqlLogEntry.Timestamp), "Timestamp");
bulkCopy.ColumnMappings.Add(nameof(SqlLogEntry.Level), "Level");
bulkCopy.ColumnMappings.Add(nameof(SqlLogEntry.Category), "Category");
bulkCopy.ColumnMappings.Add(nameof(SqlLogEntry.Message), "Message");
bulkCopy.ColumnMappings.Add(nameof(SqlLogEntry.Exception), "Exception");
bulkCopy.ColumnMappings.Add(nameof(SqlLogEntry.Path), "Path");
bulkCopy.ColumnMappings.Add(nameof(SqlLogEntry.Method), "Method");
bulkCopy.ColumnMappings.Add(nameof(SqlLogEntry.ClientIp), "ClientIp");
bulkCopy.ColumnMappings.Add(nameof(SqlLogEntry.Referer), "Referer");
bulkCopy.ColumnMappings.Add(nameof(SqlLogEntry.UserAgent), "UserAgent");
var table = ToDataTable(entries);
await bulkCopy.WriteToServerAsync(table, ct);
}
///
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
private static System.Data.DataTable ToDataTable(List entries)
{
var table = new System.Data.DataTable();
table.Columns.Add("Timestamp", typeof(DateTime));
table.Columns.Add("Level", typeof(string));
table.Columns.Add("Category", typeof(string));
table.Columns.Add("Message", typeof(string));
table.Columns.Add("Exception", typeof(string));
table.Columns.Add("Path", typeof(string));
table.Columns.Add("Method", typeof(string));
table.Columns.Add("ClientIp", typeof(string));
table.Columns.Add("Referer", typeof(string));
table.Columns.Add("UserAgent", typeof(string));
foreach (var e in entries)
table.Rows.Add(
e.Timestamp, e.Level, e.Category, e.Message,
(object?)e.Exception ?? DBNull.Value,
(object?)e.Path ?? DBNull.Value,
(object?)e.Method ?? DBNull.Value,
(object?)e.ClientIp ?? DBNull.Value,
(object?)e.Referer ?? DBNull.Value,
(object?)e.UserAgent ?? DBNull.Value);
return table;
}
}