Skip to content

Instantly share code, notes, and snippets.

@MirzaLeka
Created April 1, 2025 23:47
Show Gist options
  • Save MirzaLeka/614f4021f14e198df9d9f96a2f66dd05 to your computer and use it in GitHub Desktop.
Save MirzaLeka/614f4021f14e198df9d9f96a2f66dd05 to your computer and use it in GitHub Desktop.

To log into SQL Server using Serilog and store logs in your custom logs table, follow these steps:


1. Install Required Packages

Ensure you have the necessary Serilog packages installed via NuGet:

Install-Package Serilog
Install-Package Serilog.Sinks.MSSqlServer
Install-Package Serilog.Settings.Configuration

2. Configure Serilog in appsettings.json

Modify appsettings.json to configure Serilog:

{
  "Serilog": {
    "Using": ["Serilog.Sinks.MSSqlServer"],
    "MinimumLevel": "Information",
    "WriteTo": [
      {
        "Name": "MSSqlServer",
        "Args": {
          "connectionString": "Server=YOUR_SERVER;Database=YOUR_DB;User Id=YOUR_USER;Password=YOUR_PASSWORD;",
          "tableName": "CustomLogs",
          "autoCreateSqlTable": false,
          "restrictedToMinimumLevel": "Information",
          "columnOptionsSection": {
            "message": { "columnName": "Message", "dataType": "nvarchar(max)" },
            "level": { "columnName": "LogLevel", "dataType": "nvarchar(50)" },
            "timestamp": { "columnName": "LogTime", "dataType": "datetime2" }
          }
        }
      }
    ]
  }
}

Make sure autoCreateSqlTable is false if you have a pre-existing custom logs table.


3. Create the Custom Logs Table

Ensure your database has a table for logging:

CREATE TABLE CustomLogs (
    Id INT IDENTITY(1,1) PRIMARY KEY,
    LogTime DATETIME2 NOT NULL,
    LogLevel NVARCHAR(50) NOT NULL,
    Message NVARCHAR(MAX) NOT NULL,
    Exception NVARCHAR(MAX) NULL
);

4. Initialize Serilog in Program.cs

Modify Program.cs (for .NET 6+):

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Microsoft.Extensions.Configuration;

var builder = WebApplication.CreateBuilder(args);

// Load Serilog from configuration
builder.Host.UseSerilog((context, config) =>
{
    config.ReadFrom.Configuration(context.Configuration);
});

var app = builder.Build();

// Use Serilog for logging
app.UseSerilogRequestLogging();

app.MapGet("/", () => "Hello World!");

app.Run();

5. Log Messages in Your Application

Use Serilog in your services, controllers, or any other place in your application:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

[ApiController]
[Route("api/logs")]
public class LogsController : ControllerBase
{
    private readonly ILogger<LogsController> _logger;

    public LogsController(ILogger<LogsController> logger)
    {
        _logger = logger;
    }

    [HttpGet("test")]
    public IActionResult TestLogging()
    {
        _logger.LogInformation("This is an info log.");
        _logger.LogWarning("This is a warning log.");
        _logger.LogError("This is an error log with exception", new Exception("Test exception"));

        return Ok("Logs written to SQL Server!");
    }
}

6. Verify Logs in SQL Server

Run the application, access the endpoint /api/logs/test, and check your CustomLogs table in SQL Server:

SELECT * FROM CustomLogs ORDER BY LogTime DESC;

Summary

  • Install Serilog and Serilog.Sinks.MSSqlServer
  • Configure Serilog in appsettings.json
  • Create a CustomLogs table
  • Initialize Serilog in Program.cs
  • Use _logger.LogInformation(), _logger.LogError(), etc.
  • Verify logs in the database

Let me know if you need any modifications! 🚀

===============================

To log additional columns (e.g., Request URI, Query Params, Headers, Status Code) into your custom logs table using Serilog, you need to:

  1. Modify the SQL Table to include these fields.
  2. Customize Serilog's Column Mappings using ColumnOptions.
  3. Enrich Log Context to capture request details.

1. Modify Your SQL Table

Update your CustomLogs table to include the additional columns:

ALTER TABLE CustomLogs ADD
    RequestUri NVARCHAR(2000) NULL,
    QueryParams NVARCHAR(MAX) NULL,
    RequestHeaders NVARCHAR(MAX) NULL,
    StatusCode INT NULL;

2. Configure Serilog with Column Mapping

Modify your appsettings.json to include these new columns:

{
  "Serilog": {
    "Using": ["Serilog.Sinks.MSSqlServer"],
    "MinimumLevel": "Information",
    "Enrich": ["FromLogContext"],
    "WriteTo": [
      {
        "Name": "MSSqlServer",
        "Args": {
          "connectionString": "Server=YOUR_SERVER;Database=YOUR_DB;User Id=YOUR_USER;Password=YOUR_PASSWORD;",
          "tableName": "CustomLogs",
          "autoCreateSqlTable": false,
          "restrictedToMinimumLevel": "Information",
          "columnOptionsSection": {
            "message": { "columnName": "Message", "dataType": "nvarchar(max)" },
            "level": { "columnName": "LogLevel", "dataType": "nvarchar(50)" },
            "timestamp": { "columnName": "LogTime", "dataType": "datetime2" },
            "exception": { "columnName": "Exception", "dataType": "nvarchar(max)" },
            "additionalColumns": [
              { "ColumnName": "RequestUri", "DataType": "nvarchar", "DataLength": 2000, "AllowNull": true },
              { "ColumnName": "QueryParams", "DataType": "nvarchar", "DataLength": -1, "AllowNull": true },
              { "ColumnName": "RequestHeaders", "DataType": "nvarchar", "DataLength": -1, "AllowNull": true },
              { "ColumnName": "StatusCode", "DataType": "int", "AllowNull": true }
            ]
          }
        }
      }
    ]
  }
}

3. Capture HTTP Request Data using Middleware

To log request details, create a Serilog middleware to enrich logs:

Middleware to Capture Request Details

Create a middleware class:

using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Serilog.Context;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

public class SerilogRequestLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<SerilogRequestLoggingMiddleware> _logger;

    public SerilogRequestLoggingMiddleware(RequestDelegate next, ILogger<SerilogRequestLoggingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task Invoke(HttpContext context)
    {
        // Capture Request Data
        var request = context.Request;
        var requestUri = $"{request.Scheme}://{request.Host}{request.Path}";
        var queryParams = request.QueryString.ToString();
        var headers = string.Join("; ", request.Headers.Select(h => $"{h.Key}: {h.Value}"));

        // Add request details to Serilog's LogContext
        using (LogContext.PushProperty("RequestUri", requestUri))
        using (LogContext.PushProperty("QueryParams", queryParams))
        using (LogContext.PushProperty("RequestHeaders", headers))
        {
            await _next(context);

            // Capture Response Status Code
            var statusCode = context.Response.StatusCode;
            LogContext.PushProperty("StatusCode", statusCode);

            _logger.LogInformation("Request completed with status code {StatusCode}", statusCode);
        }
    }
}

4. Register Middleware in Program.cs

Modify Program.cs to register Serilog and the request logging middleware:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Microsoft.Extensions.Configuration;

var builder = WebApplication.CreateBuilder(args);

// Load Serilog from configuration
builder.Host.UseSerilog((context, config) =>
{
    config.ReadFrom.Configuration(context.Configuration);
});

var app = builder.Build();

// Use custom request logging middleware
app.UseMiddleware<SerilogRequestLoggingMiddleware>();

app.UseSerilogRequestLogging(); // Default Serilog request logging

app.MapGet("/", () => "Hello World!");

app.Run();

5. Logging Requests in Controllers

Now, when logging in a controller, it will automatically include Request URI, Query Params, Headers, and Status Code:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

[ApiController]
[Route("api/logs")]
public class LogsController : ControllerBase
{
    private readonly ILogger<LogsController> _logger;

    public LogsController(ILogger<LogsController> logger)
    {
        _logger = logger;
    }

    [HttpGet("test")]
    public IActionResult TestLogging()
    {
        _logger.LogInformation("Test log entry with HTTP request details.");
        return Ok("Logs written to SQL Server!");
    }
}

6. Verify Logs in SQL Server

After hitting /api/logs/test, check the CustomLogs table:

SELECT * FROM CustomLogs ORDER BY LogTime DESC;

Summary

Added custom columns to store RequestUri, QueryParams, Headers, and StatusCode.
Configured Serilog to map these columns to SQL Server.
Created a Middleware to capture and enrich logs with HTTP request details.
Registered the Middleware in Program.cs.
Logged requests dynamically without modifying each controller manually.

This setup ensures structured logging with all HTTP request details in SQL Server! 🚀 Let me know if you need any improvements! 🔥

1. Good idea of using the middleware.

Glad you liked it! Middleware is a clean way to capture request details without modifying controllers. 🚀


2. Do I Need Entity Framework Configuration for This Table?

No, Entity Framework (EF) configuration is not needed because Serilog directly writes logs to SQL Server via the Serilog.Sinks.MSSqlServer sink.

EF Core is typically used for ORM-based data operations (inserting, querying, updating, etc.), but Serilog bypasses EF and writes logs directly via ADO.NET.

So, there's no need to create an Entity class or DbContext for the CustomLogs table.


3. Can I Log to Both Console and SQL Server?

Yes! You can configure multiple sinks to log both to the console and database at the same time.

Modify Program.cs to Include Console Logging

Update your UseSerilog configuration:

builder.Host.UseSerilog((context, config) =>
{
    config.ReadFrom.Configuration(context.Configuration)
          .WriteTo.Console() // Log to Console
          .WriteTo.MSSqlServer(
                connectionString: context.Configuration.GetConnectionString("LogDb"),
                tableName: "CustomLogs",
                autoCreateSqlTable: false,
                restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information,
                columnOptions: new Serilog.Sinks.MSSqlServer.ColumnOptions()
          );
});

Or Modify appsettings.json for Multi-Sink Logging

If you prefer configuring this via appsettings.json, update it as follows:

{
  "Serilog": {
    "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.MSSqlServer"],
    "MinimumLevel": "Information",
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {Message}{NewLine}{Exception}"
        }
      },
      {
        "Name": "MSSqlServer",
        "Args": {
          "connectionString": "Server=YOUR_SERVER;Database=YOUR_DB;User Id=YOUR_USER;Password=YOUR_PASSWORD;",
          "tableName": "CustomLogs",
          "autoCreateSqlTable": false,
          "restrictedToMinimumLevel": "Information"
        }
      }
    ]
  }
}

4. Full Working Example (Program.cs)

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Microsoft.Extensions.Configuration;

var builder = WebApplication.CreateBuilder(args);

// Load Serilog configuration
builder.Host.UseSerilog((context, config) =>
{
    config.ReadFrom.Configuration(context.Configuration)
          .WriteTo.Console() // Logs to Console
          .WriteTo.MSSqlServer(
                connectionString: context.Configuration.GetConnectionString("LogDb"),
                tableName: "CustomLogs",
                autoCreateSqlTable: false,
                restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information
          );
});

var app = builder.Build();

// Use middleware for request logging
app.UseMiddleware<SerilogRequestLoggingMiddleware>();

app.UseSerilogRequestLogging(); // Default Serilog request logging

app.MapGet("/", () => "Hello World!");

app.Run();

5. Now, Logs Will Be Stored in Two Places:

  1. Console Output (for debugging purposes)
  2. SQL Server Database (CustomLogs table)

This approach ensures structured logging while keeping logs available both for developers (via console) and for long-term storage (via SQL Server). 🚀

Let me know if you need additional enhancements! 🔥

================================

Yes! You can filter logs per sink so that:

  • Request details (e.g., Request URI, Params, Headers, Status Code) → Only logged in SQL Server
  • General logs (e.g., application messages) → Logged in Console & SQL Server

Solution: Use Filter.ByExcluding or Filter.ByIncludingOnly

Serilog allows you to filter log events per sink using the Serilog.Filters.Expressions package.

Modify Program.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
using Serilog.Filters;
using Microsoft.Extensions.Configuration;

var builder = WebApplication.CreateBuilder(args);

// Load Serilog configuration with filtering
builder.Host.UseSerilog((context, config) =>
{
    config.ReadFrom.Configuration(context.Configuration)
          .WriteTo.Console( // Logs only general messages, excludes requests
              outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {Message}{NewLine}{Exception}",
              restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information
          )
          .WriteTo.MSSqlServer( // Logs everything, including request details
                connectionString: context.Configuration.GetConnectionString("LogDb"),
                tableName: "CustomLogs",
                autoCreateSqlTable: false,
                restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information
          );
});

var app = builder.Build();

// Use middleware for request logging
app.UseMiddleware<SerilogRequestLoggingMiddleware>();

app.UseSerilogRequestLogging(); // Default Serilog request logging

app.MapGet("/", () => "Hello World!");

app.Run();

Better Filtering: Exclude Request Logs from Console

If you want to exclude logs with Request URI, Params, or Headers from the console, update the UseSerilog configuration:

builder.Host.UseSerilog((context, config) =>
{
    config.ReadFrom.Configuration(context.Configuration)
          .WriteTo.Console(
              restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information
          )
          .Filter.ByExcluding(log => log.Properties.ContainsKey("RequestUri")) // Exclude request logs from console
          .WriteTo.MSSqlServer(
                connectionString: context.Configuration.GetConnectionString("LogDb"),
                tableName: "CustomLogs",
                autoCreateSqlTable: false,
                restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Information
          );
});

Alternative: Use appsettings.json to Configure Sinks Separately

If you want to configure filters via appsettings.json, you can do:

{
  "Serilog": {
    "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.MSSqlServer", "Serilog.Filters.Expressions"],
    "MinimumLevel": "Information",
    "WriteTo": [
      {
        "Name": "Console",
        "Args": {
          "outputTemplate": "[{Timestamp:HH:mm:ss} {Level}] {Message}{NewLine}{Exception}",
          "restrictedToMinimumLevel": "Information"
        },
        "Filter": [
          {
            "Name": "ByExcluding",
            "Args": { "expression": "Contains(RequestUri)" }
          }
        ]
      },
      {
        "Name": "MSSqlServer",
        "Args": {
          "connectionString": "Server=YOUR_SERVER;Database=YOUR_DB;User Id=YOUR_USER;Password=YOUR_PASSWORD;",
          "tableName": "CustomLogs",
          "autoCreateSqlTable": false,
          "restrictedToMinimumLevel": "Information"
        }
      }
    ]
  }
}

Final Behavior

✔️ Console logs → General application logs (excludes request info)
✔️ SQL Server logs → Everything, including request details

This ensures that the console isn't spammed with unnecessary request metadata while keeping full request logs in the database for auditing/debugging. 🚀

Would you like to add additional filters based on request headers or status codes? 🔥

@MirzaLeka
Copy link
Author

MirzaLeka commented Apr 2, 2025

Different log levels:

    public IActionResult Index()
    {
        _logger.LogTrace("Trace Log Message");
        _logger.LogDebug("Debug Log Message");
        _logger.LogInformation("Information Log Message");
        _logger.LogWarning("Warning Log Message"); 
        _logger.LogError("Error Log Message"); // tvoj HTTP exception
        _logger.LogCritical("Critical Log Message"); // SQL Exception ili Exception ex

        return View();
    }

Docs:

https://blog.postsharp.net/serilog-log-levels
https://code-maze.com/csharp-different-log-levels-in-serilog/
https://mbarkt3sto.hashnode.dev/logging-with-serilog-and-sql-server

@MirzaLeka
Copy link
Author

How to read config from appsettings.json

image

image

@MirzaLeka
Copy link
Author

TImed Logs

image

@MirzaLeka
Copy link
Author

Serilog simple example

//using Microsoft.Extensions.Logging;
using Serilog;

namespace ReportFormatRules
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ILogger logger = new LoggerConfiguration()
                .WriteTo.File("log.txt")
                .WriteTo.Console()
                .CreateLogger();

            logger.Information("Hello World!");
		}
	}

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment