How to Implement Serilog in ASP.NET Core Web API

How to Implement Serilog in ASP.NET Core Web API

In this article, I will discuss how to implement Serilog in ASP.NET Core Web API Applications with examples. Please read our previous article discussing How to Implement a Custom Logging Provider in ASP.NET Core Web API with an Example. At the end of this article, you will understand the following poiners:

  • What is Serilog in ASP.NET Core?
  • How Do We Implement Serilog in ASP.NET Core Web API?
  • What is Structured Logging?
  • How Do We Implement Structured Logging with Serilog in ASP.NET Core?
  • How Do We Centralize the Serilog Configuration in AppSettings.Json File?
  • How Do We Remove the Older Text files using Serilog in ASP.NET Core Web API with Configurations?
  • What is Asynchronous Logging?
  • How Do We Implement Asynchronous Logging with Serilog in ASP.NET Core Web API?
What is Serilog in ASP.NET Core?

Logging is an essential component of any robust web application. In ASP.NET Core Web API, Serilog has become popular among developers because of its simplicity, flexibility, and advanced functionalities, such as structured and asynchronous logging. Serilog is a popular, high-performance, structured logging third-party library for .NET applications, including ASP.NET Core Web API, that provides developers with a powerful and flexible way to capture, store, and analyze log data.

Unlike traditional logging frameworks (text-based logs), Serilog offers structured logging (JSON or key-value pairs), allowing developers to log data in a more meaningful and query-friendly format. It supports various sinks, enabling logs to be written to files, databases, and other storage systems.

How Do We Implement Serilog in ASP.NET Core Web API?

Let us proceed to understand the step-by-step process for implementing Serilog in an ASP.NET Core Web API project. First, create a new ASP.NET Core Web API Project named LoggingDemo.

Install Required Serilog NuGet Packages

Before we start coding, we need to install the necessary Serilog packages. These packages allow logging to various destinations (sinks) and enable configuration through JSON. These packages include:

  • Serilog.AspNetCore: Integrates Serilog with ASP.NET Core, replacing the default logging provider.
  • Serilog.Sinks.Console: Enables log output to the console, which is very useful during development for real-time log monitoring.
  • Serilog.Sinks.File: Enables logging to files on disk. This is useful for persistent storage, auditing, and post-event analysis.
  • Serilog.Settings.Configuration: Allows Serilog to be configured via the appsettings.json file so you can change log settings without modifying code.
  • Serilog.Sinks.Async: Wraps logging sinks with asynchronous functionality to improve performance by offloading log processing to a background thread.

You can install these packages using the NuGet Package Manager or by executing the following commands in the Visual Studio Package Manager Console:

  • Install-Package Serilog.AspNetCore
  • Install-Package Serilog.Sinks.Console
  • Install-Package Serilog.Sinks.File
  • Install-Package Serilog.Settings.Configuration
  • Install-Package Serilog.Sinks.Async

Once installed, you can verify these packages under the Dependencies/Packages folder in your project. You should see the following:

How Do We Implement Serilog in ASP.NET Core Web API?

What is a Sink and what are commonly provided sinks by Serilog in ASP.NET Core?

A sink in Serilog refers to an output destination where log events are written. When our application generates logs, the sink determines where those logs will end up. For example, you might want to see logs in the console while developing, store them in files for long-term analysis, or send them to a database or a cloud-based logging service.

So, when you configure Serilog, you can specify one or more sinks to determine where the log data should go. Each of these destinations is considered a different sink. The following are some commonly used sinks in ASP.NET Core when working with Serilog:

  • Console Sink: Outputs log messages directly to the console. This is especially useful during development for real-time monitoring.
  • File Sink: Writes log events to a file on disk. It often supports features like rolling files (creating a new file based on a time interval) for managing log file sizes and retention.
  • Debug Sink: Sends log events to the debug output window (e.g., Visual Studio’s Output window) which is useful when debugging your application.
  • Database Sinks (e.g., SQL Server, PostgreSQL, MongoDB): These store logs in a database. They are suitable for environments where logs need to be queried or archived in a structured format.
  • Asynchronous Sink: Wraps any of the above sinks so that log events are processed asynchronously, improving application performance under heavy logging loads
How to Configure Serilog in Program.cs Class File:

Next, we need to configure Serilog so that it initializes before the host is built. This ensures that all log events right from application startup are also captured. So, here, we are creating a Serilog logger, setting it up to read configurations from appsettings.json, and specifying where logs should be written (e.g., console and files). Finally, we are telling ASP.NET Core to use Serilog as the primary logging provider.

So, please modify the Program.cs file as follows to initialize Serilog before building the host. The following code is self-explained, so please read the comment lines for a better understanding of Serilog Configuration.

using Serilog; // Import the Serilog namespace

namespace LoggingDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // Create the builder for the WebApplication
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the built-in Dependency Injection container.
            builder.Services.AddControllers();
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            // Configure Serilog:
            // - Read configuration from appsettings.json using builder.Configuration.
            // - Set up logging to the console and to a file.
            Log.Logger = new LoggerConfiguration()             // Create a new Serilog logger configuration
                .ReadFrom.Configuration(builder.Configuration) // Read settings from appsettings.json
                .WriteTo.Console()                             // Log output to the console
                .WriteTo.File("logs/MyAppLog.txt")             // Log output to a file
                .CreateLogger();                               // Build the logger

            // Replace the default logging provider with Serilog
            builder.Host.UseSerilog(); // This ensures Serilog handles all logging

            // Build the application
            var app = builder.Build();

            // Configure middleware and HTTP request pipeline
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();
            app.UseAuthorization();
            app.MapControllers();

            // Start the application
            app.Run();
        }
    }
}
Configure Serilog in appsettings.json

The appsettings.json file specifies how and where Serilog should log messages. This file serves as a central place to adjust log settings, such as minimum log levels, destinations (sinks), and any additional properties that get included in every log message. So, please modify the appsettings.json file as follows.

{
  "Serilog": {
    // Set the minimum level of log events to record 
    // Only log Information and more severe events (Warning, Error, Fatal)
    "MinimumLevel": "Information",

    "WriteTo": [
      {
        // Logs will be written to the console
        "Name": "Console"
      },
      {
        // Logs will be written to file
        "Name": "File",
        "Args": {
          // File name pattern with a placeholder for dynamic content (like date)
          "path": "logs/MyAppLog-.txt",
          // Creates a new log file each day
          "rollingInterval": "Day" 
        }
      }
    ],
    "Properties": {
      // Global properties attached to every log event
      "Application": "App-LoggingDemo", // Global property: Application name
      "Server": "Server-125.08.13.1" // Global property: Server identifier
    }
  }
}
Understanding the Configuration:

Serilog: This is the root element for Serilog configuration settings within the appsettings.json file, indicating that all configurations within it are meant for Serilog.

“MinimumLevel”: This sets the minimum log level for events to be written to the configured sinks. Serilog has various log levels (Verbose, Debug, Information, Warning, Error, Fatal) in ascending order of severity. In this example, it’s set to “Information,” which means that only log events with a level of Information, Warning, Error, or Fatal will be logged. Verbose and Debug level events will be ignored.

“WriteTo”:

This is an array of sinks and their settings. Each sink can be further configured with its own set of arguments. Each sink has a Name, and some sinks have additional Args.

  • Name: Console: This entry specifies that logs should be written to the console. It doesn’t require any additional arguments.
  • Name: File: This specifies that logs should also be written to a file. It includes arguments for further configuration, which needs to be configured within the Args object as follows:
    1. path: Defines the path and file name pattern for the log files. “logs/MyAppLog-.txt” suggests that logs will be written to the logs directory, with files named starting with MyAppLog-. The hyphen (-) is a placeholder for dynamic content, such as date or file number, based on the rolling interval settings.
    2. rollingInterval: Specifies how often a new log file should be created. In this case, “Day” means a new file will be created daily. Other options include Hour, Minute, Month, Year, etc.

“Properties”: This section defines global properties that will be included with every log event. Here, we have added two properties, i.e., Application and Server. These are useful for filtering or identifying logs coming from a specific application or server, especially if multiple applications log into the same sink.

Use Logging in Controller and Services

After configuring Serilog, we can inject the ILogger<T> interface into our controllers or services to log messages. So, create an API empty Controller named TestController within the Controllers folder and then copy and paste the following code. The following API controller demonstrates logs at various levels:

using Microsoft.AspNetCore.Mvc;

namespace LoggingDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TestController : ControllerBase
    {
        // Inject the logger for this controller
        private readonly ILogger<TestController> _logger;

        // Constructor injection of ILogger<TestController>
        public TestController(ILogger<TestController> logger)
        {
            _logger = logger;
        }

        [HttpGet("all-logs")]
        public IActionResult LogAllLevels()
        {
            // Log a Trace-level message
            _logger.LogTrace("LogTrace: Entering the LogAllLevels endpoint.");

            // Simulate a calculation and log it
            int calculation = 5 * 10;
            _logger.LogTrace("LogTrace: Calculation value is {Calculation}", calculation);

            // Log a Debug-level message with additional context
            _logger.LogDebug("LogDebug: Initializing debug-level logs for debugging purposes.");
            var debugInfo = new { Action = "LogAllLevels", Status = "Debugging" };
            _logger.LogDebug("LogDebug: Debug information: {@DebugInfo}", debugInfo);

            // Log an Information-level message
            _logger.LogInformation("LogInformation: The LogAllLevels endpoint was reached successfully.");

            // Log a Warning if a certain condition is met
            bool resourceLimitApproaching = true;
            if (resourceLimitApproaching)
            {
                _logger.LogWarning("LogWarning: Resource usage is nearing the limit.");
            }

            try
            {
                // Simulate an error scenario
                int x = 0;
                int result = 10 / x;
            }
            catch (Exception ex)
            {
                // Log an Error-level message with exception details
                _logger.LogError(ex, "LogError: An error occurred while processing the request.");
            }

            // Log a Critical-level message if a critical failure is detected
            bool criticalFailure = true;
            if (criticalFailure)
            {
                _logger.LogCritical("LogCritical: A critical system failure has been detected.");
            }

            return Ok("All logging levels demonstrated in this endpoint.");
        }
    }
}
Run and Test

Run the application and access the above endpoint (e.g., https://localhost:{port}/api/test/all-logs). You should see logs appearing in the console and log files in the logs directory. In the Console Window, you will see the following:

What is a Sink and what are commonly provided sinks by Serilog in ASP.NET Core?

Verifying Logs in File:

Now, it should have created the logs folder in your project, and inside the logs folder, two log files will be created. This is because we have configured Serilog in both the Programs.cs class file and the appsettings.json file.

  • MyAppLog.txt: Generated from the configuration in Program.cs.
  • MyAppLog-YYYYMMDD.txt: Generated by the appsettings.json configuration (with the date replacing the placeholder).

So, in the logs folder, you should see the following two files. These log files ensure that both configuration approaches are working correctly and that our application is capturing all log events as expected.

how to implement Serilog in ASP.NET Core Web API Applications with examples

Note: Using two file sinks (one configured in code and one in appsettings.json) demonstrates that multiple configurations can coexist. In a production environment, you typically choose one configuration approach for clarity.

What is Structured Logging?

Structured logging in ASP.NET Core involves logging objects and their properties in a way that retains their structure. This means that instead of just writing plain text log messages, we provide structured data (like a JSON representation) that can be easily searched, filtered, and analyzed. Using Serilog, this is straightforward: we need to include the object directly in the log message and use Serilog’s built-in features to preserve the structure. For example:

What is Structured Logging?

How Do We Implement Structured Logging with Serilog in ASP.NET Core?

Let us see an example of implementing structured logging using Serilog in the ASP.NET Core Web API application. In this example, we build a simple Book Management application. We will define a Book model and a BooksController that logs structured information about operations performed, such as adding and retrieving books.

Create a Model

First, create a Book model. In your project root directory, add a folder named Models and then create a class file named Book.cs and copy and paste the following code. This model represents the data structure for a book. Each property in the Book model represents a piece of information that we want to capture for every book instance.

namespace LoggingDemo.Models
{
    // Define the Book model with properties for Id, Title, Author, and YearPublished
    public class Book
    {
        public int Id { get; set; } // Unique identifier for the book
        public string Title { get; set; } // Title of the book
        public string Author { get; set; } // Author of the book
        public int YearPublished { get; set; } // Publication year
    }
}
Create a Controller for Managing Books

Next, create an API Empty Controller named BooksController within the Controllers folder and then copy and paste the following code. This controller will manage adding and retrieving books while logging structured data for each operation.

using LoggingDemo.Models;
using Microsoft.AspNetCore.Mvc;

namespace LoggingDemo.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BooksController : ControllerBase
    {
        // A static list to simulate a data store for books.
        private static List<Book> Books = new List<Book>()
        {
            new Book(){ Id = 1001, Title = "ASP.NET Core", Author = "Pranaya", YearPublished = 2019 },
            new Book(){ Id = 1002, Title = "SQL Server", Author = "Pranaya", YearPublished = 2022 }
        };

        // Inject the ILogger for BooksController to capture logs
        private readonly ILogger<BooksController> _logger;

        // Constructor injection of ILogger<T>
        public BooksController(ILogger<BooksController> logger)
        {
            _logger = logger;
        }

        // POST api/books
        // This action adds a new book to the list
        [HttpPost]
        public IActionResult AddBook([FromBody] Book book)
        {
            Books.Add(book); // Add the book to our static list

            // Log the newly added book using structured logging.
            // The '@' operator tells Serilog to serialize the object as structured data.
            // {@Book} tells Serilog to capture all properties as structured data
            _logger.LogInformation("Added a new book {@Book}", book);

            return Ok();
        }

        // GET api/books
        // This action retrieves all books in the list
        [HttpGet]
        public IActionResult GetBooks()
        {
            // Log the retrieval of books and include the entire collection as structured data.
            // Logs the list of books as structured data
            _logger.LogInformation("Retrieved all books. Books: {@Books}", Books);

            return Ok(Books);
        }
    }
}
What is the @ operator in LogInformation?

In Serilog, the @ operator tells the logger to serialize the object as structured data. For example, @Book means the logger will capture the Book object’s properties (e.g., Id, Title, Author) in a structured format, such as JSON, rather than converting the object to a simple string.

Why Use Structured Logging?

Structured logging makes it easier to filter and search logs. Instead of scanning through raw text, you can query logs based on specific fields (like ID or Author) when using a logging platform that supports structured data. This approach is especially useful when logs are stored in services like Seq, Elasticsearch, or Application Insights, where structured data can be queried and visualized.

When to use structured logging?

Use structured logging when you want to capture detailed information about an object or event. For example, when adding a book, logging the entire book object provides clear context about what data was processed. Similarly, when retrieving all books, logging the collection helps trace what was returned to the client.

How Do We Centralize the Serilog Configuration in AppSettings.Json File?

Centralizing Serilog configuration means placing all the logging settings in the appsettings.json file rather than spreading them across both appsettings.json and Program.cs. This approach simplifies maintenance and makes it easier to adjust log levels, sinks, and formats without modifying code. It also enables better separation of concerns, as the code no longer directly specifies logging details. This is possible by using the Serilog.Settings.Configuration package, which we have already installed.

Configure appsettings.json for Centralized Logging

Modify the appsettings.json file as follows to include all the Serilog settings. The following configuration is self-explained, so please read the comment lines for a better understanding.

{
  "Serilog": {
    "MinimumLevel": {
      // Global minimum log level. Only events with this level or higher will be logged.
      "Default": "Information",
      // Override minimum levels for specific namespaces to reduce noise
      "Override": {
        "Microsoft": "Warning", // Only log Microsoft libraries at Warning level and above
        "System": "Error" // Only log System namespaces at Error level and above
      }
    },
    "WriteTo": [
      {
        // Configure the Console sink for real-time logging
        "Name": "Console",
        "Args": {
          // Defines the output format for the console logs.
          // The template includes a timestamp, log level, custom properties, message, new line, and exception details.
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss } [{Level:u3}] [{Application}/{Server}] {Message:lj}{NewLine}{Exception}"
        }
      },
      {
        // Configure the File sink for persistent logging
        "Name": "File",
        "Args": {
          // Log file path with a placeholder (-) that will be replaced based on the rolling interval.
          "path": "logs/MyAppLog-.txt",
          // Create a new log file daily.
          "rollingInterval": "Day",
          // Use the same output format as the Console sink.
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss } [{Level:u3}] [{Application}/{Server}] {Message:lj}{NewLine}{Exception}"
        }
      }
    ],
    // Global properties to include additional context with every log event.
    "Properties": {
      "Application": "App-LoggingDemo",
      "Server": "Server-125.08.13.1"
    }
  }
}
Understanding the above Modified Configurations:
MinimumLevel:

Default: Sets the global minimum log level. For example, Information means only logs of Information level and above (Warning, Error, Critical) will be recorded.

Override: Allows us to set different minimum log levels for specific namespaces or categories. In the example:

  • “Microsoft”: “Warning” means logs from Microsoft-related components will only appear if they are at least Warning level.
  • “System”: “Error” means logs from System namespaces will only appear if they are at least Error level.
WriteTo:

Defines an array of sinks and their settings:

Console Sink:

Outputs logs to the console. The outputTemplate defines how each log entry is formatted.

  • {Timestamp:yyyy-MM-dd HH:mm:ss }: A timestamp with date, and time.
  • [{Level:u3}]: The log level abbreviated to three characters (e.g., INF for Information, ERR for Error).
  • {Message:lj}: The log message in literal JSON format. lj stands for “literal JSON”, ensuring that structured data is logged as JSON literals.
  • {NewLine}{Exception}: A newline followed by exception details, if any.
  • [{Application}/{Server}]: Placeholders {Application} and {Server} have been added so that the properties defined under “Properties” are included in each log message.
File Sink:

Outputs log to a file. The path specifies the log file location and naming pattern, and rollingInterval: Day ensures a new log file is created daily. The outputTemplate is similar to the console sink’s template.

Properties:

Additional context (like application name or server) is automatically included in every log entry. This can help when analyzing logs from multiple sources or instances.

Initialize Serilog in Program.cs

Now that all the Serilog logging configuration is centralized in appsettings.json, please modify the Program.cs class as follows to load these settings from the appsettings.json file when building the host. With this approach, we don’t need to configure each sink manually in code. The following code is self-explained, so please read the comment lines for a better understanding.

using Serilog; 
namespace LoggingDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // Create the application builder.
            var builder = WebApplication.CreateBuilder(args);

            // Add required services to the container.
            builder.Services.AddControllers();
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            // Configure the host to use Serilog as the logging provider before building the host.
            // This setup reads all the Serilog settings from appsettings.json.
            
            // context: An instance of HostBuilderContext that provides access to the application's configuration and environment, such as environment variables or appsettings.json values.
            // services:  The application's IServiceProvider, used for dependency injection.
            // configuration: An instance of LoggerConfiguration used to configure Serilog.
            builder.Host.UseSerilog((context, services, configuration) =>
            {
                // Reads configuration settings for Serilog from the appsettings.json file or any other configuration source
                // This enables setting options such as log levels, sinks, and output formats directly from configuration files.
                configuration.ReadFrom.Configuration(context.Configuration);

                // Integrate with the dependency injection container, enabling sinks to use other registered services.
                // This is useful if any of the logging sinks require dependencies such as database or HTTP context.
                configuration.ReadFrom.Services(services);
            });

            // Build the application.
            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();
            app.UseAuthorization();
            app.MapControllers();

            // Run the application.
            app.Run();
        }
    }
}
Testing and Observing Structured Logs

Run the application and use a tool like Postman or Fiddler to test the API endpoints. For example, to add a new book, send a POST request to https://localhost:7223/api/books with the following JSON body:

{
    "id": 1003,
    "title": "C#.NET",
    "author": "Pranaya",
    "yearPublished": 2023
}
Observe the Output:

Once you access the endpoint with the above request body, now, you can verify the Console window and Log file:

Console Window: You should see log messages formatted with the timestamp, log level, application/server context, message, and any exception details.

How Do We Centralize the Serilog Configuration in AppSettings.Json File?

Log Files: Open the generated log file (e.g., MyAppLog-20240320.txt under the logs folder). The log entries should include the placeholders {Application} and {Server} as specified in the output template.

How to Implement Logging using Serilog in ASP.NET Core Web API

Note: If you do not see the Application and Server properties in your log output, ensure that your outputTemplate in both the Console and File sink configurations includes the placeholders ([{Application}/{Server}]). Using the properties is optional. If you don’t want them, you can remove them.

How Do We Remove the Older Text files using Serilog in ASP.NET Core Web API with Configurations?

Managing log files is essential to maintain your application’s performance and disk health. As your application runs, logs can accumulate and consume significant disk space if left unmanaged. Serilog’s rolling file mechanism provides an automatic way to archive or delete older log files based on specified retention policies such as file age, size, etc. By configuring Serilog correctly, we can ensure that only a limited number of recent log files are kept. This approach not only prevents disk space issues but also improves overall log management and performance.

Configuring Log Retention in appsettings.json

By centralizing Serilog configuration in appsettings.json, we can define policies to automatically create new log files and remove old ones for the file sink. For a better understanding, please modify the appsettings.json file as follows.

The following configuration creates a new log file daily, retains only the latest 30 log files, and rolls over to a new file if the current file exceeds 10 MB. Older log files will be automatically deleted once the limit is reached. The value for fileSizeLimitBytes must be set correctly. For a 10 MB limit, use 10485760 (10 × 1024 × 1024) bytes.

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Error"
      }
    },
    "WriteTo": [
      {
        "Name": "File",
        "Args": {
          "path": "logs/MyAppLog-.txt", // Log file path with a placeholder for the rolling file name.
          "rollingInterval": "Day", // Creates a new log file every day.
          "retainedFileCountLimit": 30, // Retains only the latest 30 log files; older files are automatically deleted.
          "fileSizeLimitBytes": 10485760, // Maximum file size set to 10 MB per file.
          "rollOnFileSizeLimit": true, // Rolls over to a new file if the current file exceeds the size limit.
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
          // Formats each log entry with timestamp, level, message, newline, and exception details.
        }
      }
    ]
  }
}
Explanation of Key Parameters for Log Retention Management

path: Defines where the log files will be stored. The hyphen (-) acts as a placeholder for additional information (like date) when rolling files.

  • rollingInterval: Specifies how frequently a new log file is created. Setting this to “Day” instructs Serilog to create a new log file daily. Other options include Minute, Hour, Month, etc.
  • retainedFileCountLimit: Determines the number of log files to keep. When this limit is reached (e.g., 30 files), the oldest log files are automatically deleted. This helps prevent disk space from filling up with outdated logs.
  • fileSizeLimitBytes: Sets the maximum size for each log file. In this configuration, it is set to 10 MB (10 * 1024 * 1024 = 10485760 bytes). Once a log file reaches this size, a new file is created even if the rolling interval has not been met.
  • rollOnFileSizeLimit: When set to true, this option ensures that if a file exceeds the specified size, logging will automatically roll over to a new file, maintaining manageable file sizes.
  • outputTemplate: Defines the format of each log entry. The given template displays the timestamp, log level (using a three-letter abbreviation), the log message, a newline, and exception details (if any).
What is Asynchronous Logging?

Asynchronous logging refers to the process of writing log messages in a non-blocking manner, meaning that the application’s main workflow doesn’t have to wait for log operations to complete before moving on. Log entries are handled on a separate thread or background task rather than on the main application thread.

In traditional (synchronous) logging, the application might pause to write log entries to a disk, a database, or a remote service, which can introduce delays, especially under heavy load. Asynchronous logging solves this problem by queuing log messages and processing them on a background thread. This improves the application performance, especially in high-traffic applications, by offloading the I/O operations associated with logging to background threads.

How Do We Implement Asynchronous Logging with Serilog in ASP.NET Core Web API?

In ASP.NET Core applications, Serilog does not perform asynchronous logging by default. However, you can enable it by using the Serilog.Sinks.Async package, which we have already installed. Let us proceed and understand how to configure Serilog for asynchronous logging in our ASP.NET Core application.

Configure Asynchronous Logging in appsettings.json

In the appsettings.json file, we can configure asynchronous logging by wrapping our log sinks (like Console and File) with an Async sink. This tells Serilog to process log events asynchronously for the specified sinks. So, for a better understanding, please modify the appsettings.json file as follows:

{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Error"
      }
    },
    "WriteTo": [
      {
        // Wrap sinks with the Async sink to enable asynchronous logging.
        "Name": "Async", 
        "Args": {
          "configure": [
            {
              // Asynchronously log to the Console.
              "Name": "Console",
              "Args": {
                // Console sink configuration
                "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{Application}/{Server}] {Message:lj}{NewLine}{Exception}"
              }
            },
            {
              // Asynchronously log to a file with rolling, retention, and file size limit settings.
              "Name": "File",
              "Args": {
                // File sink configuration
                "path": "logs/MyAppLog-.txt",
                "rollingInterval": "Day",
                "retainedFileCountLimit": 30,
                "fileSizeLimitBytes": 10485760, // 10 MB per file (10 * 1024 * 1024 bytes)
                "rollOnFileSizeLimit": true,
                "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{Application}/{Server}] {Message:lj}{NewLine}{Exception}"
              }
            }
          ]
        }
      }
    ],
    "Properties": {
      // Global properties to add additional information to each log event.
      "Application": "App-LoggingDemo",
      "Server": "Server-125.08.13.1"
    }
  }
}

As you can see in the above configuration, the Async sink wraps around each logging sink (e.g., Console and File), ensuring that both will be processed asynchronously. Now, run the application and it should work as expected.

Configuring Asynchronous Logging in Program.cs

In addition to configuring asynchronous logging via appsettings.json, you can also configure it in the Program.cs file. This approach is useful when you want to programmatically ensure that all sinks are wrapped asynchronously. For a better understanding, please modify the Program.cs class file as follows. The following code shows how to load the configuration and wrap the sinks asynchronously.

using Serilog; 

namespace LoggingDemo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            // Create the WebApplication builder.
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the DI container.
            builder.Services.AddControllers();
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            // Configure the host to use Serilog as the logging provider.
            builder.Host.UseSerilog((context, services, configuration) =>
            {
                // Read Serilog settings from the appsettings.json file.
                configuration.ReadFrom.Configuration(context.Configuration);

                // Integrate with dependency injection for any sinks that require additional services.
                configuration.ReadFrom.Services(services);

                // Optionally, enable asynchronous logging for additional sinks defined in code.
                configuration.WriteTo.Async(a =>
                {
                    a.Console();  // Wrap Console sink asynchronously.
                    a.File("logs/log-.txt",
                           rollingInterval: RollingInterval.Day,
                           retainedFileCountLimit: 30);  // Wrap File sink asynchronously.
                });
            });

            // Build the WebApplication.
            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();
            app.UseAuthorization();
            app.MapControllers();

            // Run the application.
            app.Run();
        }
    }
}

Serilog is a powerful and flexible logging framework that brings advanced features such as structured and asynchronous logging to ASP.NET Core Web API applications. By integrating Serilog, we can centralize logging configuration in appsettings.json, implement structured logging with message templates for enhanced queryability, and benefit from asynchronous logging for improved performance. Additionally, managing log storage becomes much easier with features such as automatic log file retention.

In the next article, I will discuss Logging into a SQL Server Database using Entity Framework Core with Serilog in ASP.NET Core Web API Application with Examples. In this article, I explain How to Implement Logging Using Serilog in ASP.NET Core Web API Application with Examples. I hope you enjoy this article, How to Implement Logging Using Serilog in ASP.NET Core Web API.

Leave a Reply

Your email address will not be published. Required fields are marked *