Tutorials
Skim Introduction, Configuration, and Services first. This page collects copy-pastable patterns for your own code: ILogger, DevKit redaction attributes on commands and HTTP endpoints, and combinations that keep secrets out of structured logs.
Manual logging with ILogger
Use Microsoft.Extensions.Logging.ILogger or ILogger<TCategory> anywhere DI can supply it:
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.");
}
}
If you inherit BaseCommandHandler or BaseQueryHandler, a _logger field is already available—call LogDebug, LogInformation, and so on from Handle the same way DevKit’s base types do (for example when publishing domain events).
[DoNotLog] — redact commands, queries, DTOs, and events
- Namespace:
CodeBlock.DevKit.Core.Attributes
When the whole type has [DoNotLog], MediatRDispatcher skips logging the serialized command/query/event payload. When only properties are marked, Serializer.RedactSensitiveData replaces those values with ***REDACTED*** in logs. For serializer helpers, see Helpers and extensions.
// 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 { }
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; }
}
Single property:
using CodeBlock.DevKit.Core.Attributes;
public sealed class UserProfileDto
{
public string DisplayName { get; set; }
[DoNotLog]
public string ApiKey { get; set; }
}
[DoNotLogQueryResult] — hide query result payloads
- Namespace:
CodeBlock.DevKit.Application.Queries
The dispatcher still logs that the query finished and how long it took, but it does not append the serialized Result<T> (useful for large or sensitive results). You can combine [DoNotLog] on the query type with [DoNotLogQueryResult] to hide both input and output.
// 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 { }
using CodeBlock.DevKit.Application.Queries;
[DoNotLogQueryResult]
internal sealed class GetCustomerSecretsQuery : BaseQuery<SecretsDto>
{
public string CustomerId { get; init; }
}
[DoNotLogRequestBody] — redact HTTP request body and form
- Namespace:
CodeBlock.DevKit.Web.Filters
HttpRequestLoggingMiddleware replaces logged body and form with Sensitive data redacted when this metadata is present on the endpoint.
// 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
}
Single 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
}
Entire API controller:
using CodeBlock.DevKit.Web.Filters;
[ApiController]
[DoNotLogRequestBody]
public class PaymentsController : ControllerBase
{
// all actions skip body/form in HTTP request logs
}
[DoNotLogResponseBody] — redact API response JSON
- Namespace:
CodeBlock.DevKit.Web.Filters
The API HttpResponseLogging pipeline checks for this metadata and skips dumping response JSON when it is present.
// 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 { }
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
}
HttpResponseLogging on your own API base type
If you replace BaseApiController but still want response-body logging, apply the same filter DevKit uses:
// 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);
}
}
See also
- Monitoring module introduction — dashboards, traces, metrics, and Mongo-backed log views.
- Exception handling —
Result, notifications, and errors. - REST API in the template — API controllers and filters.
- Helpers and extensions —
Serializer,Result, and shared helpers.