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.

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), enabling developers to log data in a more meaningful, query-friendly format. It supports various sinks, allowing the logs to be written to files, databases, and other storage systems.

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

To implement Serilog in an ASP.NET Core Web API, we follow a clear sequence of steps. The idea is: we first add the required NuGet packages, then configure Serilog in appsettings.json, and finally plug it into the ASP.NET Core hosting pipeline in Program.cs. At a high level, the steps are:

  1. Install Serilog NuGet packages: Add the core Serilog ASP.NET Core integration and one or more sinks (Console, File, etc.), plus configuration support and async support.
  2. Add Serilog configuration to appsettings.json: Define minimum log levels, which sinks to use (console, file, etc.), how the output should look, and any global properties like Application and Server.
  3. Set up CorrelationId middleware (optional but recommended): Add a middleware that generates a CorrelationId (Unique Id per Request) for each request and pushes it into Serilog’s LogContext, so it appears automatically in all logs.
  4. Configure Serilog in Program.cs: Build the Log.Logger from configuration, call builder.Host.UseSerilog() so ASP.NET Core uses Serilog instead of the default logging system, and then build and run the app normally.
  5. Use ILogger<T> in controllers and services: You continue using ILogger<OrdersController> and ILogger<OrderService> as usual, but now all logs are processed by Serilog and follow the configuration you defined.

Let us proceed to understand the step-by-step process for implementing Serilog in an ASP.NET Core Web API project. We will be working with the same project.

Install Required Serilog NuGet Packages

These packages give Serilog everything it needs to run inside ASP.NET Core and send logs to your desired destinations:

  • Serilog.AspNetCore: This is the main integration package. It provides the UseSerilog() extension for the host and ensures that Serilog is used as the primary logging provider for ASP.NET Core. Without this package, your Web API project will continue to use the default logging system.
  • Serilog.Sinks.Console: This sink writes log events to the console (e.g., Visual Studio’s Debug Console or dotnet run terminal). It’s very useful during development because you can see logs immediately as requests are being processed. You can customize the output format using an outputTemplate.
  • Serilog.Sinks.File: This sink writes log events to text files on disk. It supports rolling files (e.g., new file per day), size limits, and retention settings. This is useful for production, where you want persistent logs that can be archived, shared with support teams, or shipped to log aggregators.
  • Serilog.Settings.Configuration: This package allows Serilog to be configured from your appsettings.json file. Instead of writing all the configuration in C# code, you describe it in JSON and use ReadFrom.Configuration(builder.Configuration) to load it. This makes it easy to change log levels, switch sinks, or modify output formats without recompiling the application.
  • Serilog.Sinks.Async: This sink wraps other sinks and writes logs asynchronously. Logging is handled on a background thread, helping keep your API responsive even when there’s a lot of logging. You typically wrap console/file/database sinks with Async so they don’t block the main request pipeline.
  • Serilog.Sinks.MSSqlServer: This sink writes log events to SQL Server database.

You install them either via NuGet UI or using the 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
  • Install-Package Serilog.Sinks.MSSqlServer

What is a Sink, and what are the Commonly Used Sinks in Serilog?

A sink is a destination where Serilog writes log events. Every log generated by your application must be sent somewhere, either to a file, console, database, or cloud service. Sinks give developers complete control over where logs are stored, tailored to each environment’s needs.

Common Serilog Sinks

Console Sink: Writes logs to the terminal window. Useful during development because it displays all logs in real time.

File Sink: Stores logs inside text files. Supports rolling files, meaning a new file can be created daily or when size limits are reached, and retention policies for automatic cleanup.

Debug Sink: Outputs logs to Visual Studio’s Debug window. Great for debugging during development.

Database Sinks: Serilog provides support for storing logs in:

  • SQL Server
  • PostgreSQL
  • MongoDB
  • Elasticsearch

Database sinks are especially valuable for analytics, dashboards, event tracing, and long-term storage.

Asynchronous Sink: Wraps sinks within an async pipeline to prevent logs from slowing down your API. This is recommended for production environments where performance matters.

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 configure log settings, such as minimum log levels, destinations (sinks), and additional properties included in every log message. So, please modify the appsettings.json file as follows.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=OrderManagementDB_Evening;Trusted_Connection=True;TrustServerCertificate=True;"
  },
  "Serilog": {
    // Global Minimum Log Level Configuration
    "MinimumLevel": {
      // "Information" is the default logging level for the entire app.
      // Debug and Verbose (Trace) logs will be ignored unless overridden.
      "Default": "Information",

      // Override logging behavior for specific namespaces.
      "Override": {
        // Reduce noise from Microsoft framework logs → log only Warning and above.
        "Microsoft": "Error",

        // Only log System-level errors (ignore info/debug/verbose).
        "System": "Error"
      }
    },

    // Sink Configuration - Where the Logs Will Be Written
    "WriteTo": [
      {
        // Async Sink - wraps all child sinks for high performance.
        // Prevents blocking the request thread due to heavy logging.
        "Name": "Async",

        "Args": {
          "configure": [
            // Console Sink (Async + Debug Level)
            {
              "Name": "Console",
              "Args": {
                // Only Debug and higher logs will appear in the console.
                "restrictedToMinimumLevel": "Debug",

                // Defines how the console log line will be formatted.
                "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{Application}/{Server}] [CorrelationId: {CorrelationId}] {Message:lj}{NewLine}{Exception}"
              }
            },

            // File Sink (Async + Daily Rolling + Retention Policy)
            {
              "Name": "File",
              "Args": {
                // Only Information+ logs should be written to file.
                "restrictedToMinimumLevel": "Information",

                // Log file location and naming pattern.
                // Example: Logs/OrderManagementAPI-20251210.txt
                "path": "Logs/OrderManagementAPI-.txt",

                // Create a new file daily.
                "rollingInterval": "Day",

                // Keep only last 30 log files (older ones auto-deleted).
                "retainedFileCountLimit": 30,

                // Maximum size of each log file = 10 MB.
                "fileSizeLimitBytes": 10485760,

                // When file exceeds size limit → create a new file automatically.
                "rollOnFileSizeLimit": true,

                // Log message format in the text file.
                "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{Application}/{Server}] [CorrelationId: {CorrelationId}] [{SourceContext}] {Message:lj} {NewLine}{Exception}"
              }
            },

            // SQL Server Sink - Store Logs Inside Database Table
            {
              "Name": "MSSqlServer",
              "Args": {
                // Connection string used by MSSqlServer sink.
                "connectionString": "Server=LAPTOP-6P5NK25R\\SQLSERVER2022DEV;Database=OrderManagementDB;Trusted_Connection=True;TrustServerCertificate=True;",

                // Only "Warning" and above logs go to the database.
                "restrictedToMinimumLevel": "Warning",

                // Sink-specific configuration section.
                "sinkOptionsSection": {
                  // Table name where logs will be inserted.
                  "tableName": "Logs",

                  // Serilog will auto-create the Logs table if missing.
                  "autoCreateSqlTable": true
                }
              }
            }
          ]
        }
      }
    ],

    // Global Enricher Properties - added to Every Log Event
    "Properties": {
      // Application Name (helps identify microservice in distributed systems)
      "Application": "App-OrderManagementAPI",

      // Machine/Server Name (useful in load-balanced or cloud environments)
      "Server": "Server-125.08.13.1"
    }
  },

  "AllowedHosts": "*"
}
Modifying the CorrelationIdMiddleware

The CorrelationIdMiddleware is responsible for generating a unique identifier for each incoming HTTP request and ensuring that identifier is available in all logs and visible to clients. LogContext.PushProperty tells Serilog: For all logs written during this request, attach a property called CorrelationId with this value. Please modify the CorrelationIdMiddleware as follows.

using Serilog.Context;
namespace OrderManagementAPI.Middlewares
{
    // Middleware that ensures every incoming request has a unique Correlation ID.
    // This ID is used to trace a request end-to-end across multiple layer
    public class CorrelationIdMiddleware
    {
        // The next middleware in the request pipeline.
        private readonly RequestDelegate _next;

        // The HTTP header name used to carry the correlation ID.
        private const string CorrelationIdHeader = "X-Correlation-ID";

        // Constructor injects the next delegate in the pipeline.
        public CorrelationIdMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        // Invoked once per HTTP request. 
        // Responsible for generating a Correlation ID and injecting it into:
        // 1. The request header (for downstream services)
        // 2. The response header (for clients)
        public async Task InvokeAsync(HttpContext context, ILogger<CorrelationIdMiddleware> logger)
        {
            // Generate a new correlation ID for each request
            var correlationId = Guid.NewGuid().ToString().ToUpper();

            // Store in HttpContext for downstream code
            context.Items[CorrelationIdHeader] = correlationId;

            // Ensure the same correlation ID is returned to the client
            // in the response header
            context.Response.Headers[CorrelationIdHeader] = correlationId;

            // Push CorrelationId into Serilog's log context for the entire request
            using (LogContext.PushProperty("CorrelationId", correlationId))
            {
                await _next(context);
            }
        }
    }

    // Extension method for registering the CorrelationIdMiddleware
    // in a fluent and readable way inside Program.cs (app.UseCorrelationId()).
    public static class CorrelationIdMiddlewareExtensions
    {
        // Adds the CorrelationIdMiddleware to the ASP.NET Core request pipeline.
        public static IApplicationBuilder UseCorrelationId(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<CorrelationIdMiddleware>();
        }
    }
}
Configure Serilog in Program.cs

Next, we need to configure Serilog as the Logging Provider. Please modify the Program.cs file as follows. The following code is self-explanatory, so please read the comment lines for a better understanding of Serilog Configuration.

using Microsoft.EntityFrameworkCore;
using OrderManagementAPI.Data;
using OrderManagementAPI.Middlewares;
using OrderManagementAPI.Services;
using Serilog;
using static System.Net.Mime.MediaTypeNames;

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

            // Add controller support and configure JSON serialization
            builder.Services.AddControllers()
            .AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.PropertyNamingPolicy = null;
            });

            // Configure Serilog manually before the app starts.
            // Creates a new Serilog logger configuration object.
            // Assigning to Log.Logger ensures: Serilog captures logs during application startup.
            // ASP.NET Core replaces default logging with Serilog later via UseSerilog().
            Log.Logger = new LoggerConfiguration()

                // Reads all Serilog settings (sinks, levels, properties, enrichers)
                // from appsettings.json → "Serilog" section.
                // This allows you to control logging behavior WITHOUT touching the code.
                .ReadFrom.Configuration(builder.Configuration)

                // Adds contextual properties to every log event.
                // Enrich log events with any properties stored in Serilog's LogContext,
                // such as CorrelationId pushed by our CorrelationIdMiddleware.
                // This makes those contextual values automatically appear on every log
                // written during the lifetime of the current request.
                .Enrich.FromLogContext()

                // Builds and creates the Serilog logger instance.
                // After this, Serilog becomes ready to capture logs.
                .CreateLogger();

            // Disable Default Logging Providers
            builder.Logging.ClearProviders();

            // Replace default logging with Serilog
            builder.Host.UseSerilog(); 

            // Enable Swagger for API documentation
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            // Register DbContext for main app usage
            builder.Services.AddDbContext<OrderManagementDbContext>(options =>
                options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

            // Allow services to access HttpContext (used for CorrelationId, etc.)
            builder.Services.AddHttpContextAccessor();

            // Register application services
            builder.Services.AddScoped<IOrderService, OrderService>();
            builder.Services.AddSingleton<ICorrelationIdAccessor, CorrelationIdAccessor>();

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

            // Middleware Pipeline Configuration
            // Enable Swagger UI (only in Development environment)
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            // Enforce HTTPS for all requests
            app.UseHttpsRedirection();

            // Attach a Correlation ID to each request for tracing
            app.UseCorrelationId();

            // Handle Authorization if enabled
            app.UseAuthorization();

            // Map controller endpoints to routes
            app.MapControllers();

            // Start the application
            app.Run();
        }
    }
}
Modifying the Orders Controller to Use Structured Logging:

Please modify the OrdersController as follows to use Structured Logging. We should not use string interpolation; instead, we need to use placeholders. Secondly, any complex object placeholder needs to be created with @ symbol which will tell the Serilog to convert the corresponding object into JSON, instead of apply the ToString method.

using Microsoft.AspNetCore.Mvc;
using OrderManagementAPI.DTOs;
using OrderManagementAPI.Services;
using System.Diagnostics;

namespace OrderManagementAPI.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class OrdersController : ControllerBase
    {
        private readonly ILogger<OrdersController> _logger;
        private readonly IOrderService _orderService;

        public OrdersController(
            ILogger<OrdersController> logger,
            IOrderService orderService)
        {
            _logger = logger;
            _orderService = orderService;

            // Simple informational log – no extra properties needed here.
            _logger.LogInformation("OrdersController instantiated.");
        }

        // POST: api/orders
        [HttpPost]
        public async Task<IActionResult> CreateOrder([FromBody] CreateOrderDTO dto)
        {
            var stopwatch = Stopwatch.StartNew();

            // Structured logging of incoming request body
            _logger.LogInformation(
                "HTTP POST /api/orders called. Payload: {@Request}",
                dto);

            if (!ModelState.IsValid)
            {
                // Log ModelState as structured data (key-value pairs for validation errors)
                _logger.LogWarning(
                    "Model validation failed for CreateOrder request. ModelState: {@ModelState}",
                    ModelState);

                return BadRequest(ModelState);
            }

            try
            {
                var created = await _orderService.CreateOrderAsync(dto!);
                stopwatch.Stop();

                // Structured success log with separate fields for querying
                _logger.LogInformation(
                    "Order {OrderId} created successfully via API in {ElapsedMs} ms. Response: {@Response}",
                    created.Id,
                    stopwatch.ElapsedMilliseconds,
                    created);

                return CreatedAtAction(nameof(GetOrderById), new { id = created.Id }, created);
            }
            catch (ArgumentException ex)
            {
                stopwatch.Stop();

                // Structured validation error with exception attached
                _logger.LogWarning(
                    ex,
                    "Validation error while creating order for CustomerId {CustomerId}. ElapsedMs: {ElapsedMs} ms. ErrorMessage: {ErrorMessage}",
                    dto?.CustomerId,
                    stopwatch.ElapsedMilliseconds,
                    ex.Message);

                return BadRequest(new { message = ex.Message });
            }
            catch (Exception ex)
            {
                stopwatch.Stop();

                // Structured unexpected error with timing info
                _logger.LogError(
                    ex,
                    "Unexpected error while creating order for CustomerId {CustomerId}. ElapsedMs: {ElapsedMs} ms.",
                    dto?.CustomerId,
                    stopwatch.ElapsedMilliseconds);

                return StatusCode(500, new { message = "An unexpected error occurred." });
            }
        }

        // GET: api/orders/{id}
        [HttpGet("{id:int}")]
        public async Task<IActionResult> GetOrderById(int id)
        {
            var stopwatch = Stopwatch.StartNew();

            // Structured – OrderId is a separate property
            _logger.LogInformation(
                "HTTP GET /api/orders/{OrderId} called.",
                id);

            var order = await _orderService.GetOrderByIdAsync(id);
            stopwatch.Stop();

            if (order == null)
            {
                _logger.LogWarning(
                    "Order with ID {OrderId} not found. Execution time: {ElapsedMs} ms.",
                    id,
                    stopwatch.ElapsedMilliseconds);

                return NotFound(new { message = $"Order with id {id} not found." });
            }

            // Log returned order object as structured data
            _logger.LogInformation(
                "Order {OrderId} fetched successfully. Execution time: {ElapsedMs} ms. Response: {@Response}",
                id,
                stopwatch.ElapsedMilliseconds,
                order);

            return Ok(order);
        }

        // GET: api/orders/customer/{customerId}
        [HttpGet("customer/{customerId:int}")]
        public async Task<IActionResult> GetOrdersForCustomer(int customerId)
        {
            var stopwatch = Stopwatch.StartNew();

            _logger.LogInformation(
                "HTTP GET /api/orders/customer/{CustomerId} called.",
                customerId);

            var orders = await _orderService.GetOrdersForCustomerAsync(customerId);
            stopwatch.Stop();

            // OrderCount / CustomerId / ElapsedMs come out as separate properties
            _logger.LogInformation(
                "{OrderCount} orders returned for CustomerId {CustomerId} in {ElapsedMs} ms.",
                orders.Count(),
                customerId,
                stopwatch.ElapsedMilliseconds);

            return Ok(orders);
        }
    }
}
Modifying the Orders Service to Use Structured Logging:

Please modify the Order Service as follows to use Structured Logging.

using Microsoft.EntityFrameworkCore;
using OrderManagementAPI.Data;
using OrderManagementAPI.DTOs;
using OrderManagementAPI.Entities;

namespace OrderManagementAPI.Services
{
    public class OrderService : IOrderService
    {
        private readonly OrderManagementDbContext _dbContext;
        private readonly ILogger<OrderService> _logger;

        public OrderService(
            OrderManagementDbContext dbContext,
            ILogger<OrderService> logger)
        {
            _dbContext = dbContext;
            _logger = logger;
        }

        public async Task<OrderDTO> CreateOrderAsync(CreateOrderDTO dto)
        {
            // Structured: CustomerId, ItemCount and Request are separate properties.
            _logger.LogInformation(
                "Creating order for CustomerId {CustomerId} with {ItemCount} items. Payload: {@Request}",
                dto.CustomerId,
                dto.Items?.Count ?? 0,
                dto);

            // 1. Validate Customer
            var customer = await _dbContext.Customers.FindAsync(dto.CustomerId);
            if (customer == null)
            {
                _logger.LogWarning(
                    "Cannot create order: CustomerId {CustomerId} not found.",
                    dto.CustomerId);

                throw new ArgumentException($"Customer with id {dto.CustomerId} not found.");
            }

            // Extra safety: ensure we actually have items
            if (dto.Items == null || dto.Items.Count == 0)
            {
                _logger.LogWarning(
                    "Cannot create order: no items provided for CustomerId {CustomerId}.",
                    dto.CustomerId);

                throw new ArgumentException("Order must contain at least one item.");
            }

            // 2. Get all product IDs from DTO and fetch from DB in one shot
            var productIds = dto.Items.Select(i => i.ProductId).Distinct().ToList();

            var products = await _dbContext.Products
                .Where(p => productIds.Contains(p.Id) && p.IsActive)
                .ToListAsync();

            if (products.Count != productIds.Count)
            {
                var missingIds = productIds.Except(products.Select(p => p.Id)).ToList();

                _logger.LogWarning(
                    "Cannot create order: some products not found or inactive. MissingIds: {MissingProductIds}",
                    missingIds);

                throw new ArgumentException("One or more products are invalid or not active.");
            }

            // 3. Create Order and OrderItems
            var order = new Order
            {
                CustomerId = dto.CustomerId,
                OrderDate = DateTime.UtcNow
            };

            decimal total = 0;

            foreach (var itemDto in dto.Items)
            {
                var product = products.Single(p => p.Id == itemDto.ProductId);

                var unitPrice = product.Price;
                var lineTotal = unitPrice * itemDto.Quantity;

                var orderItem = new OrderItem
                {
                    ProductId = product.Id,
                    Quantity = itemDto.Quantity,
                    UnitPrice = unitPrice,
                    LineTotal = lineTotal
                };

                order.Items.Add(orderItem);
                total += lineTotal;

                _logger.LogDebug(
                    "Added item to order. ProductId={ProductId}, Quantity={Quantity}, UnitPrice={UnitPrice}, LineTotal={LineTotal}.",
                    product.Id,
                    itemDto.Quantity,
                    unitPrice,
                    lineTotal);
            }

            order.TotalAmount = total;

            _logger.LogDebug(
                "Total amount for CustomerId {CustomerId} calculated as {TotalAmount}.",
                dto.CustomerId,
                order.TotalAmount);

            // 4. Save to DB with error logging
            try
            {
                _dbContext.Orders.Add(order);
                await _dbContext.SaveChangesAsync();

                _logger.LogInformation(
                    "Order {OrderId} created successfully for CustomerId {CustomerId}.",
                    order.Id,
                    dto.CustomerId);

                // Load navigation properties for mapping (Customer + Items + Product)
                await _dbContext.Entry(order).Reference(o => o.Customer).LoadAsync();
                await _dbContext.Entry(order).Collection(o => o.Items).LoadAsync();
                foreach (var item in order.Items)
                {
                    await _dbContext.Entry(item).Reference(i => i.Product).LoadAsync();
                }

                var dtoResult = MapToOrderDto(order);

                // Log the resulting DTO as structured data
                _logger.LogInformation(
                    "Order {OrderId} data prepared for response. DTO: {@OrderDto}",
                    order.Id,
                    dtoResult);

                return dtoResult;
            }
            catch (Exception ex)
            {
                _logger.LogError(
                    ex,
                    "Error occurred while saving order for CustomerId {CustomerId}.",
                    dto.CustomerId);

                throw; // let controller decide response
            }
        }

        public async Task<OrderDTO?> GetOrderByIdAsync(int id)
        {
            _logger.LogInformation(
                "Fetching order with OrderId {OrderId}.",
                id);

            var order = await _dbContext.Orders
                .Include(o => o.Customer)
                .Include(o => o.Items)
                    .ThenInclude(i => i.Product)
                .AsNoTracking()
                .SingleOrDefaultAsync(o => o.Id == id);

            if (order == null)
            {
                _logger.LogWarning(
                    "Order with OrderId {OrderId} not found.",
                    id);

                return null;
            }

            _logger.LogDebug(
                "Order {OrderId} found for CustomerId {CustomerId}.",
                order.Id,
                order.CustomerId);

            var dto = MapToOrderDto(order);

            _logger.LogInformation(
                "Order {OrderId} mapped to DTO. DTO: {@OrderDto}",
                id,
                dto);

            return dto;
        }

        public async Task<IEnumerable<OrderDTO>> GetOrdersForCustomerAsync(int customerId)
        {
            _logger.LogInformation(
                "Fetching orders for CustomerId {CustomerId}.",
                customerId);

            var orders = await _dbContext.Orders
                .Include(o => o.Customer)
                .Include(o => o.Items)
                    .ThenInclude(i => i.Product)
                .AsNoTracking()
                .Where(o => o.CustomerId == customerId)
                .ToListAsync();

            _logger.LogInformation(
                "Found {OrderCount} orders for CustomerId {CustomerId}.",
                orders.Count,
                customerId);

            var dtos = orders.Select(MapToOrderDto).ToList();

            _logger.LogDebug(
                "Orders for CustomerId {CustomerId} mapped to DTO list. DTOCount={DtoCount}.",
                customerId,
                dtos.Count);

            return dtos;
        }

        // Helper: Entity -> DTO mapping
        private static OrderDTO MapToOrderDto(Order order)
        {
            return new OrderDTO
            {
                Id = order.Id,
                CustomerId = order.CustomerId,
                CustomerName = order.Customer?.FullName ?? string.Empty,
                OrderDate = order.OrderDate,
                TotalAmount = order.TotalAmount,
                Status = order.Status,
                Items = order.Items.Select(i => new OrderItemDTO
                {
                    Id = i.Id,
                    ProductId = i.ProductId,
                    ProductName = i.Product?.Name ?? string.Empty,
                    Quantity = i.Quantity,
                    UnitPrice = i.UnitPrice,
                    LineTotal = i.LineTotal
                }).ToList()
            };
        }
    }
}

Serilog delivers a reliable and efficient logging solution for ASP.NET Core applications, enabling structured logging, centralized configuration, and multi-sink output. Its flexibility and rich feature set simplify troubleshooting and improve observability across the system. By adopting Serilog, applications gain a more maintainable, scalable, and professional logging foundation.

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 Applications with Examples. In this article, I explain how to Implement Logging Using Serilog in an 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 *