Logging
Automatic logging per HTTP request
Every HTTP request is logged for you. When a request enters the app, DevKit’s HttpRequestLoggingMiddleware writes a structured log line with method, URL, headers, query, form, and body (unless you mark the endpoint to skip body logging). After the rest of the pipeline runs, it logs status code, duration, and response headers.
While that request is processed, MediatRDispatcher (the DevKit IRequestDispatcher implementation) logs commands, queries, and domain events as they are received and after they finish—again with timing—so you get a continuous story from “request arrived” through “command/query ran” until “HTTP response completed”.
With normal ASP.NET Core hosting, log scopes attach identifiers such as TraceId to each request. That means every ILogger call in the pipeline for that request shares the same correlation context in structured logs, which is what you use to follow one trace end to end in your log viewer or in the Monitoring module when traces and dashboards are enabled.
You do not call the middleware yourself; it runs as part of the web host pipeline.
Manual logging with ILogger
Use Microsoft.Extensions.Logging.ILogger (or ILogger<TCategory>) anywhere the host registers services: inject it in constructors and call LogInformation, LogWarning, LogError, and so on.
DevKit’s dispatcher follows the same pattern. For example, MediatRDispatcher takes ILogger<MediatRDispatcher> and uses it when sending commands and queries:
- Namespace:
CodeBlock.DevKit.Infrastructure.Services
using Microsoft.Extensions.Logging;
namespace MyApp.Infrastructure;
public sealed class MyService
{
private readonly ILogger<MyService> _logger;
public MyService(ILogger<MyService> logger)
{
_logger = logger;
}
public void Run()
{
_logger.LogInformation("MyService is doing work.");
}
}
Use cases: _logger
If you write a command or query handler that inherits DevKit’s base types (BaseCommandHandler, BaseQueryHandler), a logger is already there as _logger:
Use it directly inside Handle (or helpers), for example _logger.LogDebug("Publishing domain events: {DomainEvents}", ...) as in BaseCommandHandler.
Logging attributes and HTTP response filter
The sections below list each marker DevKit uses to control what gets written to logs. Namespaces are called out so you can add the right using in your API or application projects.
[DoNotLog] — redact commands, queries, DTOs, and events in dispatcher / serializer logs
- Namespace:
CodeBlock.DevKit.Core.Attributes
When the whole type has [DoNotLog], MediatRDispatcher skips logging the serialized command/query/event payload for that type. When only properties are marked, Serializer.RedactSensitiveData (used when logging objects) replaces those property values with ***REDACTED***.
DoNotLogAttribute.cs:
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev
namespace CodeBlock.DevKit.Core.Attributes;
/// <summary>
/// Marks properties or classes to be excluded from logging operations.
/// When applied, sensitive data will be redacted and replaced with "***REDACTED***" in logs.
/// </summary>
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false)]
public class DoNotLogAttribute : Attribute { }
Example — entire command:
using CodeBlock.DevKit.Application.Commands;
using CodeBlock.DevKit.Core.Attributes;
[DoNotLog]
internal sealed class ChangePasswordRequest : BaseCommand
{
public string CurrentPassword { get; }
public string NewPassword { get; }
}
Example — single property on a DTO or command:
using CodeBlock.DevKit.Core.Attributes;
public sealed class UserProfileDto
{
public string DisplayName { get; set; }
[DoNotLog]
public string ApiKey { get; set; }
}
For Serializer helpers, see Extensions and helpers.
[DoNotLogQueryResult] — do not log the result of a query
- Namespace:
CodeBlock.DevKit.Application.Queries
When present on BaseQuery<T> implementations, MediatRDispatcher still logs that the query finished and how long it took, but it does not append the serialized Result<T> payload (useful for large or sensitive query results).
DoNotLogQueryResultAttribute.cs:
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev
namespace CodeBlock.DevKit.Application.Queries;
/// <summary>
/// Attribute to mark query classes that should not have their results logged.
/// Applied to query classes to prevent sensitive or large result data from being logged.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class DoNotLogQueryResultAttribute : Attribute { }
Example:
using CodeBlock.DevKit.Application.Queries;
[DoNotLogQueryResult]
internal sealed class GetCustomerSecretsQuery : BaseQuery<SecretsDto>
{
public string CustomerId { get; init; }
}
You can combine [DoNotLog] on the query type (hide the input) with [DoNotLogQueryResult] (hide the output).
[DoNotLogRequestBody] — hide request body and form in HTTP request logs
- Namespace:
CodeBlock.DevKit.Web.Filters
HttpRequestLoggingMiddleware reads this metadata from the endpoint. If it is present, the logged request body and form lines are replaced with Sensitive data redacted.
DoNotLogRequestBodyAttribute.cs:
// Copyright (c) CodeBlock.DevKit. All rights reserved.
// For more information visit https://codeblock.dev
namespace CodeBlock.DevKit.Web.Filters;
/// <summary>
/// Marker attribute that indicates request body content should not be logged.
/// Apply this attribute to controllers or actions to prevent logging of sensitive request data
/// such as passwords, credit card numbers, or other confidential information.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class DoNotLogRequestBodyAttribute : Attribute
{
// This is a marker attribute to indicate sensitive body data should not be logged
}
Example — action:
using CodeBlock.DevKit.Web.Filters;
using Microsoft.AspNetCore.Mvc;
[HttpPost]
[DoNotLogRequestBody]
public async Task<Result<CommandResult>> Login([FromBody] LoginDto input)
{
// body and form are not written to HTTP request logs
}
Example — entire API controller:
using CodeBlock.DevKit.Web.Filters;
[ApiController]
[DoNotLogRequestBody]
public class PaymentsController : ControllerBase
{
// all actions on this controller skip body/form in HTTP request logs
}
[DoNotLogResponseBody] — hide response body in API response logs
- Namespace:
CodeBlock.DevKit.Web.Filters
The DevKit API stack uses HttpResponseLoggingAttribute (see below). That filter checks endpoint metadata for [DoNotLogResponseBody]. If it is present, it logs that response-body logging is disabled instead of dumping JSON.
DoNotLogResponseBodyAttribute.cs:
// Copyright (c) CodeBlock.DevKit. All rights reserved.
// For more information visit https://codeblock.dev
namespace CodeBlock.DevKit.Web.Filters;
/// <summary>
/// Marker attribute that indicates response body content should not be logged.
/// Apply this attribute to controllers or actions to prevent logging of sensitive response data.
/// Currently not implemented in the logging middleware but available for future use.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
public class DoNotLogResponseBodyAttribute : Attribute { }
Example:
using CodeBlock.DevKit.Web.Filters;
[HttpGet("{id}")]
[DoNotLogResponseBody]
public async Task<Result<SecretsDto>> GetSecrets(string id)
{
// response JSON is not written to response-body logs
}
HttpResponseLoggingAttribute — log API response bodies (class-level)
- Namespace:
CodeBlock.DevKit.Web.Api.Filters
This is an IAsyncResultFilter applied on the DevKit BaseApiController so JSON response bodies are captured and logged after the action runs. It honors [DoNotLogResponseBody] on the controller or action. You only need to put [HttpResponseLogging] on your own base API controller if you replace BaseApiController and still want the same behavior.
HttpResponseLoggingAttribute.cs:
// Copyright (c) CodeBlock.Dev. All rights reserved.
// For more information visit https://codeblock.dev
using System.Text;
using CodeBlock.DevKit.Web.Filters;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace CodeBlock.DevKit.Web.Api.Filters;
/// <summary>
/// Attribute that logs HTTP response bodies for debugging and monitoring purposes.
/// Can be applied to controllers to automatically log all response content.
/// Respects the DoNotLogResponseBody attribute to skip logging when needed.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class HttpResponseLoggingAttribute : Attribute, IAsyncResultFilter
{
/// <summary>
/// Intercepts the response execution to capture and log the response body.
/// Temporarily replaces the response stream to capture content without affecting the client.
/// </summary>
/// <param name="context">The result execution context</param>
/// <param name="next">The next filter in the pipeline</param>
/// <returns>A task representing the asynchronous operation</returns>
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var loggerFactory = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<HttpResponseLoggingAttribute>();
// Check if the DoNotLogResponseBody attribute is present on the controller or action
var endpoint = context.HttpContext.GetEndpoint();
var hasDoNotLogAttr = endpoint?.Metadata.GetMetadata<DoNotLogResponseBodyAttribute>() != null;
// Swap out the response body stream with a temporary memory stream
var originalBodyStream = context.HttpContext.Response.Body;
using var temporaryStream = new MemoryStream();
context.HttpContext.Response.Body = temporaryStream;
// Execute the action and response
var resultContext = await next();
var responseBody = await GetResponseBody(context.HttpContext.Response);
// Skip logging if the attribute is present
if (!hasDoNotLogAttr)
{
logger.LogInformation("Response Body: {ResponseBody}", responseBody);
}
else
{
logger.LogInformation("Response Body: response body logging is disabled for this endpoint");
}
// Reset the stream position again to copy it back to the original stream
await temporaryStream.CopyToAsync(originalBodyStream);
context.HttpContext.Response.Body = originalBodyStream;
}
/// <summary>
/// Reads and formats the response body for logging purposes.
/// Handles stream positioning and JSON formatting for better readability.
/// </summary>
/// <param name="response">The HTTP response to read the body from</param>
/// <returns>A formatted string representation of the response body</returns>
private async Task<string> GetResponseBody(HttpResponse response)
{
if (!response.Body.CanRead)
return "Response body is not readable.";
response.Body.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(response.Body, Encoding.UTF8, leaveOpen: true);
var body = await reader.ReadToEndAsync();
response.Body.Seek(0, SeekOrigin.Begin);
if (string.IsNullOrWhiteSpace(body))
return "No Body Data!";
var jsonBody = JsonConvert.DeserializeObject(body);
return JsonConvert.SerializeObject(jsonBody, Formatting.Indented);
}
}
HttpRequestLoggingMiddleware — where request logging happens (not an attribute)
- Namespace:
CodeBlock.DevKit.Web.Filters
This type is internal to the package; you configure the host to use the middleware, then control sensitive bodies with [DoNotLogRequestBody] as above. It is listed here so you know where request lines are produced and why the marker attributes matter.
Related topics
- Monitoring module introduction — metrics, traces, and dashboards.
- Exception handling — errors,
Result, and notifications. - REST API in the template — API controllers and filters.
- Extensions and helpers —
Serializer,Result.