RedactaLog 0.1.0

dotnet add package RedactaLog --version 0.1.0
                    
NuGet\Install-Package RedactaLog -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RedactaLog" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RedactaLog" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="RedactaLog" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RedactaLog --version 0.1.0
                    
#r "nuget: RedactaLog, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RedactaLog@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RedactaLog&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=RedactaLog&version=0.1.0
                    
Install as a Cake Tool

RedactaLog

NuGet

Request/response logging for ASP.NET Core that automatically redacts sensitive data.

Logging full HTTP traffic is invaluable for debugging — until you realise your logs are now full of passwords, bearer tokens, and credit-card numbers. RedactaLog is a drop-in middleware that logs the traffic you want while scrubbing the data you must not keep.

  • 🔒 Redacts by field name — JSON properties like password, token, apiKey (configurable, case-insensitive).
  • 🪪 Redacts by header nameAuthorization, Cookie, Set-Cookie, … out of the box.
  • 🧠 Built-in PII patterns — credit-card numbers and e-mail addresses, plus your own regexes.
  • 🔌 Provider-agnostic — writes through Microsoft.Extensions.Logging, so it works with Serilog, NLog, console, Seq, etc.
  • 🧱 Structure-preserving — JSON stays valid JSON; only the sensitive values change.
  • Low-friction — two lines of setup, sensible defaults, zero changes to your DTOs.

Targets .NET 8, 9, and 10.

Install

dotnet add package RedactaLog

Quick start

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRedactaLog();   // 1. register

var app = builder.Build();

app.UseRedactaLog();                // 2. add early in the pipeline

app.MapControllers();
app.Run();

Given a request like:

POST /login
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json

{ "username": "bob", "password": "hunter2", "card": "4111111111111111" }

RedactaLog logs:

RedactaLog -> [a1b2c3d4…] POST /login | Headers: Authorization: ***REDACTED*** | Body: {"username":"bob","password":"***REDACTED***","card":"***REDACTED***"}
RedactaLog <- [a1b2c3d4…] 200 in 12.3ms | Headers: Content-Type: application/json | Body: {"status":"ok"}

The downstream app and the caller still see the original, unmodified request and response — only the log is redacted.

Where your logs go (sinks)

RedactaLog does not decide where logs are written — it writes through Microsoft.Extensions.Logging, so the destination is whatever logging provider you configure. The RedactaLog setup never changes; only your logging configuration does.

// Console (built in — nothing extra to configure)
builder.Services.AddRedactaLog();
// File, via Serilog
builder.Host.UseSerilog((ctx, cfg) => cfg
    .WriteTo.File("logs/api-.log", rollingInterval: RollingInterval.Day));

builder.Services.AddRedactaLog();
// Elasticsearch, via Serilog
builder.Host.UseSerilog((ctx, cfg) => cfg
    .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
    {
        AutoRegisterTemplate = true,
        IndexFormat = "myapi-logs-{0:yyyy.MM.dd}",
    }));

builder.Services.AddRedactaLog();

Because the entries are structured ({Method}, {StatusCode}, {ElapsedMs}, {CorrelationId}, …), sinks like Elasticsearch and Seq expose them as queryable fields rather than flat text.

RedactaLog logs under the category RedactaLog.RedactaLogMiddleware, so you can route just its entries to a dedicated sink (e.g. send HTTP traffic to its own file while the rest goes to the console).

Correlation IDs

Every request is tagged with a correlation id that appears on both the request and response log lines, is attached as a logging scope (so your own logs during the request carry it too), and is echoed back on the response header. An inbound X-Correlation-ID is reused if present; otherwise one is generated.

RedactaLog -> [a1b2c3d4…] POST /login | Headers: … | Body: …
RedactaLog <- [a1b2c3d4…] 200 in 12.3ms | Headers: … | Body: …
builder.Services.AddRedactaLog(options =>
{
    options.IncludeCorrelationId          = true;             // default
    options.CorrelationIdHeaderName        = "X-Correlation-ID"; // default
    options.WriteCorrelationIdToResponse   = true;             // echo back to the client
});

Inbound correlation ids are attacker-controllable, so they're sanitized (control characters stripped, length capped) before being logged — header and query values are stripped of CR/LF for the same reason.

Configuration

builder.Services.AddRedactaLog(options =>
{
    // Add/remove sensitive JSON property & query-key names (case-insensitive)
    options.SensitiveProperties.Add("nationalId");

    // Add/remove sensitive header names
    options.SensitiveHeaders.Add("X-Internal-Token");

    // Bring your own PII regex
    options.Patterns.Add(new RedactionPattern(
        "UkSortCode",
        new Regex(@"\b\d{2}-\d{2}-\d{2}\b")));

    options.Placeholder       = "***";       // default: ***REDACTED***
    options.LogRequestBody    = true;
    options.LogResponseBody   = true;
    options.LogHeaders        = true;
    options.MaxBodyCharacters = 16 * 1024;    // bodies longer than this are truncated
    options.LogLevel          = LogLevel.Information;

    options.ExcludedPaths.Add("/metrics");    // skipped entirely
});

Defaults at a glance

Setting Default
Sensitive properties password, pwd, secret, clientSecret, token, accessToken, refreshToken, apiKey, authorization, ssn, creditCard, cardNumber, cvv, pin, …
Sensitive headers Authorization, Proxy-Authorization, Cookie, Set-Cookie, X-Api-Key, X-Auth-Token
PII patterns credit card, e-mail
Excluded paths /health, /healthz, /swagger
Loggable content anything containing json, text/plain, xml, x-www-form-urlencoded
Placeholder ***REDACTED***
Correlation id enabled, header X-Correlation-ID, echoed on response

Marking fields with [Redact]

If you'd rather declare sensitive data next to your models than maintain a central name list, annotate properties with [Redact] and scan your assembly at startup:

public class RegisterRequest
{
    public string Username { get; set; }

    [Redact]
    public string Password { get; set; }

    [Redact]
    [JsonPropertyName("ccn")]          // serialized name is honored
    public string CreditCard { get; set; }
}
builder.Services
    .AddRedactaLog()
    .ScanForRedactAttributes(typeof(Program).Assembly);   // harvest [Redact] names once, at startup

Both styles compose — names from config and names harvested from [Redact] are merged. [JsonPropertyName] (System.Text.Json) and [JsonProperty] (Newtonsoft.Json) are both respected when resolving the serialized name; the Newtonsoft attribute is read reflectively, so RedactaLog takes no dependency on Newtonsoft.

Note: redaction is name-based — the marked member's name is added to the global sensitive-name set, so a same-named property on another type will also be redacted. For most apps this is exactly what you want; if you need per-type precision, fall back to explicit config.

How it works

UseRedactaLog() adds a single middleware that:

  1. Buffers the request body (EnableBuffering) so reading it doesn't consume it for your handler.
  2. Temporarily swaps the response stream, captures what's written, then replays it to the client untouched.
  3. Redacts headers (by name), the query string (by key), and bodies before writing one structured log entry per request and per response.

JSON bodies are parsed and walked: values of sensitive properties are masked, and every string value is run through the PII patterns. Non-JSON text bodies get pattern redaction. Binary/non-text content types are never logged.

Notes & limitations

  • The built-in credit-card pattern matches digits without separators (4111111111111111). Add a custom pattern if you need dashed/spaced variants.
  • Bodies are read up to MaxBodyCharacters and then truncated — RedactaLog is for observability, not full payload archival.
  • Attacker-controlled single-line fields (headers, query values, correlation id) are stripped of control characters to prevent log forging. Bodies are logged verbatim (they're legitimately multi-line); prefer a structured sink (Elastic, Seq, JSON file), where the body is a single field and embedded newlines can't forge a fake log line.
  • Redaction is best-effort defence in depth. Keep it paired with TLS, access-controlled log storage, and good key hygiene.

License

MIT © Ahmad Taghiyev

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.
  • net8.0

    • No dependencies.
  • net9.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 43 6/2/2026