Skip to main content

Overview

The Intent.AspNetCore.Logging.Serilog module adds structured logging capabilities to your ASP.NET Core application using Serilog, a popular .NET logging library. Unlike traditional text-based logging, Serilog captures log events as structured data, making logs queryable, filterable, and far more useful for diagnosing issues in production environments.

What is Serilog?

Serilog is a diagnostic logging library for .NET applications that:
  • Captures structured data instead of plain text
  • Supports multiple sinks (Console, File, Graylog, Application Insights, etc.)
  • Enriches log events with contextual information
  • Provides powerful filtering and configuration options
  • Integrates seamlessly with ASP.NET Core
For more information, visit the official Serilog documentation and GitHub repository.

What Gets Generated

Serilog Configuration

Startup configuration that wires up Serilog with selected sinks and enrichers:
Program.cs
using Serilog;
using Serilog.Events;

Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(configuration)
    .Enrich.FromLogContext()
    .WriteTo.Console(
        outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
    .WriteTo.File(
        path: "logs/log-.txt",
        rollingInterval: RollingInterval.Day,
        outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
    .CreateLogger();

builder.Host.UseSerilog();

Bounded Logging Destructuring Policy

A custom destructuring policy that prevents excessive logging by:
  • Limiting collection sizes
  • Truncating objects with too many properties
  • Replacing problematic types (streams, byte arrays) with placeholders
  • Handling exceptions during property access
Infrastructure/Logging/BoundedLoggingDestructuringPolicy.cs
using Serilog.Core;
using Serilog.Events;

public class BoundedLoggingDestructuringPolicy : IDestructuringPolicy
{
    public bool TryDestructure(
        object? value, 
        ILogEventPropertyValueFactory propertyValueFactory, 
        out LogEventPropertyValue? result)
    {
        if (value is null or string)
        {
            result = null;
            return false;
        }

        // Limit collections to 30 items
        if (value is IDictionary dict && dict.Count > 30)
        {
            result = new DictionaryValue(
                dict.OfType<DictionaryEntry>()
                    .Take(30)
                    .Append(new DictionaryEntry("", $"... (and {dict.Count - 30} more entries)"))
                    .Select(s => new KeyValuePair<ScalarValue, LogEventPropertyValue>(
                        new ScalarValue(s.Key), 
                        propertyValueFactory.CreatePropertyValue(s.Value, true)
                    ))
            );
            return true;
        }

        // Similar logic for IEnumerable, objects with many properties, etc.
        // ...
    }
}

Configuration Settings

Additional configuration in appsettings.json:
appsettings.json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information",
        "System": "Warning"
      }
    }
  }
}

Installation

Prerequisites

  • An ASP.NET Core application in Intent Architect

Installation Steps

1

Install the module

In Intent Architect, right-click on your application and select Manage Modules. Search for Intent.AspNetCore.Logging.Serilog and install it.
2

Configure sinks

In Application Settings, navigate to Serilog and select which sinks to include (Console, File, Graylog, Application Insights).
3

Run the Software Factory

Execute the Software Factory to generate the Serilog configuration and destructuring policy.
4

Configure log levels

Update appsettings.json to set appropriate log levels for different namespaces.

Configuration

Sinks

The module supports multiple logging destinations:

Console

Writes logs to the console with colored output for different log levels.

File

Writes logs to rolling files with daily rotation (e.g., log-20260307.txt).

Graylog

Sends structured logs to Graylog for centralized log management.

Application Insights

Integrates with Azure Application Insights for cloud-based log analytics.
Configure in Application Settings:
  1. Open Application Settings in Intent Architect
  2. Navigate to Serilog Settings
  3. Select the sinks you want to enable
  4. Run the Software Factory
Only the selected sinks will be added to your logging pipeline.

Log Levels

Configure log levels in appsettings.json:
appsettings.json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft.AspNetCore": "Warning",
        "Microsoft.EntityFrameworkCore": "Information",
        "YourApp.Domain": "Debug"
      }
    }
  }
}
Available levels: Verbose, Debug, Information, Warning, Error, Fatal

Environment-Specific Configuration

Create environment-specific settings:
appsettings.Development.json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Debug"  // More verbose in development
    }
  }
}
appsettings.Production.json
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Warning"  // Less noise in production
    },
    "WriteTo": [
      {
        "Name": "Graylog",
        "Args": {
          "hostnameOrAddress": "graylog.production.com",
          "port": 12201
        }
      }
    ]
  }
}

Usage Examples

Basic Logging

public class OrderService
{
    private readonly ILogger<OrderService> _logger;

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

    public async Task<Order> CreateOrder(CreateOrderCommand command)
    {
        _logger.LogInformation("Creating order for customer {CustomerId}", command.CustomerId);

        try
        {
            var order = new Order { CustomerId = command.CustomerId };
            await _repository.AddAsync(order);
            
            _logger.LogInformation(
                "Order {OrderId} created successfully with {ItemCount} items",
                order.Id,
                order.Items.Count
            );

            return order;
        }
        catch (Exception ex)
        {
            _logger.LogError(
                ex,
                "Failed to create order for customer {CustomerId}",
                command.CustomerId
            );
            throw;
        }
    }
}
Structured output:
{
  "@t": "2026-03-07T10:30:15.1234567Z",
  "@mt": "Creating order for customer {CustomerId}",
  "@l": "Information",
  "CustomerId": "12345",
  "SourceContext": "OrderService"
}

Enriching with Context

using Serilog.Context;

public async Task<IActionResult> PlaceOrder([FromBody] CreateOrderCommand command)
{
    using (LogContext.PushProperty("UserId", User.Identity.Name))
    using (LogContext.PushProperty("CorrelationId", HttpContext.TraceIdentifier))
    {
        _logger.LogInformation("Processing order placement");
        var order = await _orderService.CreateOrder(command);
        return Ok(order);
    }
}
All logs within the using block will include UserId and CorrelationId properties.

Performance-Sensitive Logging

Use log level checks to avoid expensive operations:
if (_logger.IsEnabled(LogLevel.Debug))
{
    var detailedInfo = ExpensiveSerializationMethod(complexObject);
    _logger.LogDebug("Detailed order info: {DetailedInfo}", detailedInfo);
}

Exception Logging

try
{
    await ProcessPayment(order);
}
catch (PaymentException ex)
{
    _logger.LogError(
        ex,
        "Payment failed for order {OrderId} with amount {Amount:C}",
        order.Id,
        order.TotalAmount
    );
    throw;
}
Serilog captures:
  • The exception type, message, and stack trace
  • Structured properties (OrderId, Amount)
  • The format specifier (:C for currency formatting)

Serilog vs OpenTelemetry

Understanding when to use each:
FeatureSerilogOpenTelemetry
Primary FocusApplication log events (structured text)Traces, metrics, and logs
Use CaseDetailed application loggingDistributed system observability
Data FormatLog events with propertiesSpans, metrics, log records
Best ForApplication diagnostics, auditingPerformance monitoring, distributed tracing
IntegrationEasy .NET integrationCross-platform, vendor-neutral
Use both for a complete observability stack: Serilog for detailed application logs and OpenTelemetry for distributed tracing and metrics.

Best Practices

Use Structured Logging

Always use message templates with properties, not string interpolation:
// Good
_logger.LogInformation("User {UserId} logged in", userId);

// Bad
_logger.LogInformation($"User {userId} logged in");

Log at Appropriate Levels

  • Verbose/Debug: Developer diagnostics
  • Information: General application flow
  • Warning: Unexpected but handled situations
  • Error: Failures requiring attention
  • Fatal: Critical system failures

Avoid Sensitive Data

Never log passwords, tokens, or personally identifiable information (PII).

Use Scoped Context

Add contextual properties with LogContext.PushProperty() for related operations.

Bounded Destructuring Policy

The generated BoundedLoggingDestructuringPolicy prevents:
  • Runaway memory from logging large collections
  • Performance issues from serializing complex object graphs
  • Log noise from excessive property logging
Example:
var largeList = Enumerable.Range(1, 1000).ToList();
_logger.LogInformation("Processing {Items}", largeList);

// Output:
// "Processing [1, 2, 3, ..., 10, ... (and 990 more items)]"

Integration with Other Modules

OpenTelemetry

Serilog logs are automatically included in traces when OpenTelemetry logging is enabled.

ASP.NET Core

Serilog integrates with the ASP.NET Core logging pipeline and middleware.

Entity Framework Core

Log SQL queries and database operations with appropriate log levels.

Azure Application Insights

Send structured logs to Application Insights for powerful querying with KQL.

Troubleshooting

Logs Not Appearing

Check log level configuration: Ensure the minimum log level allows your log statements:
{
  "Serilog": {
    "MinimumLevel": {
      "Default": "Debug"  // Lower the level temporarily
    }
  }
}

File Sink Not Writing

Verify file path and permissions:
.WriteTo.File(
    path: "logs/log-.txt",  // Relative to application directory
    rollingInterval: RollingInterval.Day
)
Ensure the logs directory exists or Serilog has permission to create it.

Too Much Logging

Suppress noisy namespaces:
{
  "Serilog": {
    "MinimumLevel": {
      "Override": {
        "Microsoft.EntityFrameworkCore.Database.Command": "Warning"
      }
    }
  }
}

Next Steps

OpenTelemetry

Add distributed tracing and metrics

Application Insights

Send logs to Azure for analysis

Health Checks

Monitor application health

Build docs developers (and LLMs) love