Logging Integration
Comprehensive guide to integrating EasyMessages with ASP.NET Core logging infrastructure (ILogger, structured logging, and log aggregation).
Table of Contents
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:
- 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()); - 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()); - Use structured logging
// Good - Structured properties for querying _logger.LogInformation( "User {UserId} created by {CreatedBy}", user.Id, User.Identity.Name ); - 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:
- 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); - 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()); } - 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
- Overview - ASP.NET Core integration overview
- Setup and Configuration - Complete setup guide
- API Response Patterns - REST API patterns
- How-To Guides - Practical recipes
Questions? Ask in Discussions