RedactaLog 0.1.0
dotnet add package RedactaLog --version 0.1.0
NuGet\Install-Package RedactaLog -Version 0.1.0
<PackageReference Include="RedactaLog" Version="0.1.0" />
<PackageVersion Include="RedactaLog" Version="0.1.0" />
<PackageReference Include="RedactaLog" />
paket add RedactaLog --version 0.1.0
#r "nuget: RedactaLog, 0.1.0"
#:package RedactaLog@0.1.0
#addin nuget:?package=RedactaLog&version=0.1.0
#tool nuget:?package=RedactaLog&version=0.1.0
RedactaLog
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 name —
Authorization,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:
- Buffers the request body (
EnableBuffering) so reading it doesn't consume it for your handler. - Temporarily swaps the response stream, captures what's written, then replays it to the client untouched.
- 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
MaxBodyCharactersand 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 | Versions 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. |
-
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 |