Logging Integration

Comprehensive guide to integrating EasyMessages with ASP.NET Core logging infrastructure (ILogger, structured logging, and log aggregation).


Table of Contents

  1. Logging Basics
  2. Manual Logging
  3. Structured Logging
  4. Log Levels
  5. Third-Party Integrations
  6. Best Practices

Logging Basics

EasyMessages integrates seamlessly with ASP.NET Core’s ILogger interface via the .Log(ILogger) extension method, providing:

  • Manual logging via .Log(logger) — available directly from the Core package
  • Structured logging with message properties
  • Log level mapping based on message type

The .Log(ILogger) extension method is chainable — it logs the message and returns it, so you can continue chaining.


Manual Logging

Basic Usage

Controller:

using Microsoft.AspNetCore.Mvc;
using RecurPixel.EasyMessages;

public class UsersController : ControllerBase
{
    private readonly ILogger<UsersController> _logger;

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

    [HttpPost]
    public IActionResult Create(CreateUserDto dto)
    {
        var user = _userService.Create(dto);

        var msg = Msg.Crud.Created("User")
            .WithData(user)
            .Log(_logger);  // ← Manual logging
        return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
    }
}

Console Output:

info: YourApp.Controllers.UsersController[0]
      [CRUD_001] User Created: User has been created successfully.

Chaining with Fluent API

The .Log() method returns the message, allowing method chaining:

var msg = Msg.Crud.Created("User")
    .WithData(user)
    .WithMetadata("source", "WebAPI")
    .Log(_logger);       // ← Logs and returns message
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());

Logging with Metadata

Metadata is automatically included in structured logs:

var notFound = Msg.Crud.NotFound("User")
    .WithMetadata("userId", id)
    .WithMetadata("requestedBy", User.Identity.Name)
    .Log(_logger);
return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject());

Console Output:

warn: YourApp.Controllers.UsersController[0]
      [CRUD_004] User Not Found: The requested User could not be found.
      => userId: 999
      => requestedBy: john.doe@example.com

Conditional Logging

Log only when a condition is met:

[HttpGet("{id}")]
public IActionResult GetById(int id)
{
    var user = _userService.FindById(id);

    if (user == null)
    {
        // Always log not found
        var notFound = Msg.Crud.NotFound("User")
            .WithMetadata("userId", id)
            .Log(_logger);
        return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject());
    }

    // Only log if verbose logging enabled
    var message = Msg.Crud.Retrieved("User").WithData(user);

    if (_configuration.GetValue<bool>("VerboseLogging"))
    {
        message.Log(_logger);
    }

    return StatusCode(message.HttpStatusCode ?? 200, message.ToJsonObject());
}

Structured Logging

Message Properties in Logs

EasyMessages automatically includes message properties in structured logs:

var msg = Msg.Crud.Created("User")
    .WithData(new { Id = 123, Name = "John Doe" })
    .WithMetadata("source", "WebAPI")
    .WithCorrelationId("abc-123")
    .Log(_logger);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());

Structured Log Output (Serilog):

{
  "Timestamp": "2026-01-09T14:30:00.000Z",
  "Level": "Information",
  "MessageTemplate": "[{Code}] {Title}: {Description}",
  "Properties": {
    "Code": "CRUD_001",
    "Title": "User Created",
    "Description": "User has been created successfully.",
    "CorrelationId": "abc-123",
    "Metadata": {
      "source": "WebAPI"
    }
  }
}

Custom Log Scope

Add custom log scope for additional context:

using (_logger.BeginScope(new Dictionary<string, object>
{
    ["UserId"] = currentUserId,
    ["Action"] = "CreateUser"
}))
{
    var msg = Msg.Crud.Created("User").WithData(user).Log(_logger);
    return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());
}

Output:

info: YourApp.Controllers.UsersController[0]
      => UserId: 456
      => Action: CreateUser
      [CRUD_001] User Created: User has been created successfully.

Log Levels

Message Type to Log Level Mapping

EasyMessages automatically maps message types to appropriate log levels:

MessageType LogLevel When to Use
Success Information Successful operations
Info Information Informational messages
Warning Warning Warnings, non-critical issues
Error Error Operation failures
Critical Critical System failures

Log Level Examples

Success → Information:

Msg.Crud.Created("User").Log(_logger);
// Logged at: Information

Warning → Warning:

Msg.Crud.NoChangesDetected().Log(_logger);
// Logged at: Warning

Error → Error:

Msg.Auth.LoginFailed().Log(_logger);
// Logged at: Error

Critical → Critical:

Msg.Database.ConnectionFailed().Log(_logger);
// Logged at: Critical

Filtering by Log Level

appsettings.json:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information",
      "EasyMessages": "Information",
      "YourApp.Controllers": "Debug"
    }
  }
}

Production Settings:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "EasyMessages": "Warning"
    }
  }
}

Third-Party Integrations

Serilog Integration

Install Serilog:

dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File

Program.cs:

using Serilog;

var builder = WebApplication.CreateBuilder(args);

// Configure Serilog
Log.Logger = new LoggerConfiguration()
    .ReadFrom.Configuration(builder.Configuration)
    .Enrich.FromLogContext()
    .WriteTo.Console()
    .WriteTo.File("logs/easymessages-.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();

builder.Host.UseSerilog();

var app = builder.Build();
app.Run();

appsettings.json:

{
  "Serilog": {
    "Using": ["Serilog.Sinks.Console", "Serilog.Sinks.File"],
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning"
      }
    },
    "WriteTo": [
      { "Name": "Console" },
      {
        "Name": "File",
        "Args": {
          "path": "logs/easymessages-.txt",
          "rollingInterval": "Day",
          "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
        }
      }
    ],
    "Enrich": ["FromLogContext", "WithMachineName", "WithThreadId"]
  }
}

Usage:

// ILogger injected by Serilog
var msg = Msg.Crud.Created("User").WithData(user).Log(_logger);
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());

Console Output:

2026-01-09 14:30:00.123 +00:00 [INF] [CRUD_001] User Created: User has been created successfully.

Application Insights Integration

Install Application Insights:

dotnet add package Microsoft.ApplicationInsights.AspNetCore

Program.cs:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationInsightsTelemetry();
var app = builder.Build();
app.Run();

appsettings.json:

{
  "ApplicationInsights": {
    "ConnectionString": "InstrumentationKey=your-key-here"
  }
}

Usage:

// ILogger automatically routes to Application Insights
var msg = Msg.Database.TransactionFailed()
    .WithMetadata("error", ex.Message)
    .Log(_logger);
return StatusCode(msg.HttpStatusCode ?? 500, msg.ToJsonObject());

Result: All EasyMessages logs automatically sent to Application Insights with structured properties.


Seq Integration

Install Serilog Seq Sink:

dotnet add package Serilog.Sinks.Seq

Program.cs:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Seq("http://localhost:5341")
    .CreateLogger();

builder.Host.UseSerilog();

Result: Structured logs sent to Seq for powerful querying and visualization.


Elasticsearch Integration

Install Serilog Elasticsearch Sink:

dotnet add package Serilog.Sinks.Elasticsearch

Program.cs:

Log.Logger = new LoggerConfiguration()
    .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
    {
        AutoRegisterTemplate = true,
        IndexFormat = "easymessages-{0:yyyy.MM}"
    })
    .CreateLogger();

builder.Host.UseSerilog();

Best Practices

[✓] DO:

  1. Log errors and warnings
    // Good - Always log errors
    var failed = Msg.Auth.LoginFailed()
        .WithMetadata("email", dto.Email)
        .Log(_logger);  // Log failures
    return StatusCode(failed.HttpStatusCode ?? 401, failed.ToJsonObject());
    
  2. Add contextual metadata
    // Good - Metadata helps debugging
    var notFound = Msg.Crud.NotFound("User")
        .WithMetadata("userId", id)
        .WithMetadata("requestedBy", User.Identity.Name)
        .Log(_logger);
    return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject());
    
  3. Use structured logging
    // Good - Structured properties for querying
    _logger.LogInformation(
        "User {UserId} created by {CreatedBy}",
        user.Id,
        User.Identity.Name
    );
    
  4. Use correlation IDs for tracing
    // Good - Add correlation ID for distributed tracing
    var msg = Msg.System.Error()
        .WithCorrelationId(HttpContext.TraceIdentifier)
        .Log(_logger);
    return StatusCode(msg.HttpStatusCode ?? 500, msg.ToJsonObject());
    

[ ] DON’T:

  1. Don’t log sensitive data
    // Bad - Logging password!
    var msg = Msg.Auth.LoginFailed()
        .WithMetadata("password", dto.Password)  // Security risk!
        .Log(_logger);
    
    // Good - Only log non-sensitive info
    var msg = Msg.Auth.LoginFailed()
        .WithMetadata("email", dto.Email)  // Safe
        .Log(_logger);
    
  2. Don’t ignore exceptions
    // Bad - Silent failure
    try { _userService.Delete(id); }
    catch { }  // No logging!
    
    // Good - Log exceptions
    try
    {
        _userService.Delete(id);
    }
    catch (Exception ex)
    {
        var err = Msg.System.Error()
            .WithMetadata("error", ex.Message)
            .Log(_logger);
        return StatusCode(err.HttpStatusCode ?? 500, err.ToJsonObject());
    }
    
  3. Don’t log PII without consent
    // Bad - Logging PII
    var msg = Msg.Crud.Created("User")
        .WithMetadata("email", user.Email)      // PII
        .WithMetadata("phone", user.Phone)      // PII
        .Log(_logger);
    
    // Good - Use user ID only
    var msg = Msg.Crud.Created("User")
        .WithMetadata("userId", user.Id)  // No PII
        .Log(_logger);
    

Logging Patterns

Pattern 1: Manual Logging

Use when: You want explicit control over what gets logged

var msg = Msg.Crud.Created("User")
    .WithData(user)
    .Log(_logger);  // ← Explicit logging
return StatusCode(msg.HttpStatusCode ?? 200, msg.ToJsonObject());

Pattern 2: Log Only Errors

Use when: You want clean logs, only logging failures

[HttpGet("{id}")]
public IActionResult GetById(int id)
{
    var user = _userService.FindById(id);

    if (user == null)
    {
        var notFound = Msg.Crud.NotFound("User")
            .WithMetadata("userId", id)
            .Log(_logger);  // Only log failures
        return StatusCode(notFound.HttpStatusCode ?? 404, notFound.ToJsonObject());
    }

    // No logging for successful retrieval
    var ok = Msg.Crud.Retrieved("User").WithData(user);
    return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject());
}

Pattern 3: Log Everything in Try/Catch

Use when: Global error logging in controllers

[HttpPost]
public IActionResult Create(CreateUserDto dto)
{
    try
    {
        var user = _userService.Create(dto);
        var ok = Msg.Crud.Created("User").WithData(user).Log(_logger);
        return StatusCode(ok.HttpStatusCode ?? 200, ok.ToJsonObject());
    }
    catch (ValidationException ex)
    {
        var err = Msg.Validation.Failed()
            .WithMetadata("errors", ex.Errors)
            .Log(_logger);
        return StatusCode(err.HttpStatusCode ?? 422, err.ToJsonObject());
    }
    catch (Exception ex)
    {
        var err = Msg.System.Error()
            .WithMetadata("error", ex.Message)
            .Log(_logger);
        return StatusCode(err.HttpStatusCode ?? 500, err.ToJsonObject());
    }
}

Next Steps


Questions? Ask in Discussions