Skip to content

Filtering and Redacting Diagram Content

Aryeh Citron edited this page May 1, 2026 · 1 revision

Filtering and Redacting Diagram Content

This page provides copy-paste recipes for the most common diagram content problems, organised by what you're trying to fix. For the full formatting pipeline reference (pre/mid/post processors, DiagramsFetcherOptions), see Content Formatting.


My Diagrams Are Too Wide

Large HTTP bodies — JWT tokens, base64 blobs, verbose JSON — make diagram notes stretch horizontally, pushing the sequence diagram well beyond the viewport.

Quick Fix: Hard Truncation (1 line)

Set a maximum character limit on all captured content. Anything longer is trimmed with a marker:

// In test setup (e.g. fixture constructor or GlobalSetup)
RequestResponseLogger.MaxContentLength = 2000;

Content exceeding 2,000 characters is trimmed to: …truncated (N chars total). This applies globally to all extensions (HTTP, Cosmos, Redis, Kafka, etc.) since truncation happens in the core Log() method.

Split Long Words

Tokens and base64 strings are single unbroken words that prevent line wrapping. The built-in SplitLongWords() extension inserts zero-width spaces so PlantUML can wrap them:

new ReportConfigurationOptions
{
    RequestResponsePostProcessor = content => content.SplitLongWords()
};

Hide Non-Focused Fields (DiagramFocus Hidden Mode)

When you only care about specific JSON fields, hide everything else:

new ReportConfigurationOptions
{
    FocusDeEmphasis = FocusDeEmphasis.Hidden
};

Then in your test, declare which fields to show:

var response = await client.WithDiagramFocus()
    .OnResponse<OrderResponse>(x => x.Total, x => x.Status)
    .GetAsync("/api/orders/123");

Non-focused fields are completely removed from the diagram note, replaced with .... See Content Formatting#DiagramFocus (Field Highlighting) for full details.

Exclude Noisy Headers

Authorization and correlation headers add width without value. Remove them from diagrams entirely:

new ReportConfigurationOptions
{
    ExcludedHeaders = ["Authorization", "X-Request-Id", "X-Correlation-Id", "Cookie"]
};

See Excluded Headers for more details.

Diagram Splitting (Too Many Interactions)

If the problem is too many arrows (not content width), the library automatically splits diagrams when they exceed height/URL thresholds. See Large Response and Diagram Handling for configuration.


I Want to Hide Sensitive Data

Tokens, secrets, PII, and API keys should not appear in diagrams — especially if reports are shared or stored in CI artifacts.

Bearer Token Redaction (3 lines)

The simplest and most common recipe. Use a mid-processor for clean regex without PlantUML markup interference:

new ReportConfigurationOptions
{
    RequestResponseMidProcessor = content =>
        Regex.Replace(content, @"Bearer [A-Za-z0-9\-._~+/]+=*", "Bearer ***")
};

JSON Token Fields (access_token, refresh_token, id_token)

Token endpoints return tokens in JSON bodies. Target them by property name:

private static readonly Regex AccessTokenRegex = new("(?<=\"access_token\": \")[^\"]+(?=\")");
private static readonly Regex RefreshTokenRegex = new("(?<=\"refresh_token\": \")[^\"]+(?=\")");
private static readonly Regex IdTokenRegex = new("(?<=\"id_token\": \")[^\"]+(?=\")");

new ReportConfigurationOptions
{
    RequestResponseMidProcessor = content => content
        .RegexReplace(AccessTokenRegex, "***")
        .RegexReplace(RefreshTokenRegex, "***")
        .RegexReplace(IdTokenRegex, "***")
};

Cookie / Session Redaction

new ReportConfigurationOptions
{
    RequestResponsePostProcessor = content =>
        Regex.Replace(content, @"(?<=\[(?:Set-)?Cookie=)[^\]]{50,}(?=])", match =>
            match.Value[..20] + "…REDACTED")
};

PII Redaction (Email, Phone)

private static readonly Regex EmailRegex = new("(?<=\"email\": \")[^\"]+(?=\")");
private static readonly Regex PhoneRegex = new("(?<=\"phone\": \")[^\"]+(?=\")");

new ReportConfigurationOptions
{
    RequestResponseMidProcessor = content => content
        .RegexReplace(EmailRegex, "***@***.***")
        .RegexReplace(PhoneRegex, "***-***-****")
};

Exclude the Authorization Header Entirely

If you don't need to see the token format at all, exclude the header from diagrams:

new ReportConfigurationOptions
{
    ExcludedHeaders = ["Authorization"]
};

Exposing JWT Claims While Hiding the Token

If you want to show what the token contains (for debugging) without the 500-character noise, decode the JWT payload and replace the raw token:

RequestResponseMidProcessor = content =>
    Regex.Replace(content, @"Bearer ([A-Za-z0-9\-._~+/]+=*)", match =>
    {
        try
        {
            var parts = match.Groups[1].Value.Split('.');
            if (parts.Length != 3) return "Bearer ***";
            var payload = Encoding.UTF8.GetString(Convert.FromBase64String(
                parts[1].PadRight(parts[1].Length + (4 - parts[1].Length % 4) % 4, '=')));
            var json = JsonSerializer.Deserialize<JsonElement>(payload);
            var sub = json.TryGetProperty("sub", out var s) ? s.GetString() : "?";
            var role = json.TryGetProperty("role", out var r) ? r.GetString() : null;
            return role != null ? $"Bearer [sub={sub}, role={role}]" : $"Bearer [sub={sub}]";
        }
        catch { return "Bearer ***"; }
    })

Beyond Basic Redaction — Hash-Based Token Shortening

Sometimes you need to correlate which requests share the same token across a sequence diagram, but the full token is too long to display. Hash-based shortening replaces each token with a short deterministic hash — the same token always produces the same short string, so you can visually trace token reuse across diagram arrows.

The Pattern

Bearer eyJhbGciOiJSUzI1NiIs...  →  Bearer #a3f29c01
Bearer eyJhbGciOiJSUzI1NiIs...  →  Bearer #a3f29c01  (same token = same hash)
Bearer eyJhbGciOiJIUzI1NiIs...  →  Bearer #7b1e4d92  (different token = different hash)

Recipe: XxHash64 (fastest, 8-char output)

using System.IO.Hashing;

RequestResponseMidProcessor = content =>
    Regex.Replace(content, @"Bearer [A-Za-z0-9\-._~+/]+=*", match =>
    {
        var bytes = Encoding.UTF8.GetBytes(match.Value);
        var hash = XxHash64.HashToUInt64(bytes);
        return $"Bearer #{hash:x8}";  // 8 hex chars, deterministic
    })

Recipe: SHA256 (cryptographically strong, 8-char output)

using System.Security.Cryptography;

RequestResponseMidProcessor = content =>
    Regex.Replace(content, @"Bearer [A-Za-z0-9\-._~+/]+=*", match =>
    {
        var hash = SHA256.HashData(Encoding.UTF8.GetBytes(match.Value));
        return $"Bearer #{Convert.ToHexString(hash)[..8].ToLower()}";
    })

When to Use Which Approach

Approach Use when Example output
Full redaction (***) You never need to distinguish tokens Bearer ***
Hash shortening (#a3f29c01) You need to see which requests share a token Bearer #a3f29c01
RedactMiddle (keep start+end) You need to identify the token type/issuer Bearer eyJhbGc…KxwRJSM
JWT claims extraction You need to see the subject/role Bearer [sub=user1, role=admin]

Applying to JSON Token Fields Too

The same pattern works for access_token / refresh_token in JSON bodies:

RequestResponseMidProcessor = content =>
{
    // Hash bearer tokens in headers
    content = Regex.Replace(content, @"Bearer [A-Za-z0-9\-._~+/]+=*", match =>
    {
        var hash = XxHash64.HashToUInt64(Encoding.UTF8.GetBytes(match.Value));
        return $"Bearer #{hash:x8}";
    });

    // Hash JSON token fields
    content = Regex.Replace(content, "(?<=\"(?:access|refresh|id)_token\": \")[^\"]+(?=\")", match =>
    {
        var hash = XxHash64.HashToUInt64(Encoding.UTF8.GetBytes(match.Value));
        return $"#{hash:x8}";
    });

    return content;
}

I Want Less Noise in Setup Steps

Test setup (Given/Arrange) often involves seeding data, creating users, or configuring state — operations that clutter diagrams without adding value to the scenario under test.

Suppress Setup Tracking Entirely

new TestTrackingMessageHandlerOptions
{
    TrackDuringSetup = false
};

All HTTP calls made during the Setup phase are silently dropped.

Reduce Setup Detail (Keep Arrows, Remove Bodies)

new TestTrackingMessageHandlerOptions
{
    SetupVerbosity = Verbosity.Summarised
};

Setup interactions appear as arrows with method + URL, but request/response bodies are omitted. Action interactions retain full detail.

Explicit Phase Boundaries (Non-BDD Frameworks)

BDD frameworks (BDDfy, LightBDD, ReqNRoll) set the phase automatically from step keywords. For xUnit/NUnit/MSTest, mark the boundary explicitly:

// Setup
await SeedTestData();
await CreateUser("test-user");

// Mark the start of the action phase
trackingDiagramOverride.StartAction();

// Action — this is what we're testing
var response = await client.PostAsJsonAsync("/api/orders", order);

See Phase-Aware Tracking for full details on phase configuration, per-extension options, and BDD auto-detection.


I Have Custom Large Content (Blobs, HTML, XML)

Strip HTML Tags

If a response contains rendered HTML that's meaningless in a diagram:

RequestResponsePostProcessor = content =>
    Regex.Replace(content, "<[^>]+>", "").Trim()

Pretty-Print XML

The library only auto-formats JSON. For XML/SOAP responses, use a pre-processor:

new DiagramsFetcherOptions
{
    ResponsePreFormattingProcessor = body =>
    {
        try { return XDocument.Parse(body).ToString(); }
        catch { return body; }
    }
}

Replace Large Binary/Blob Content

If you're tracking blob storage operations, the binary content is useless in diagrams:

RequestResponseMidProcessor = content =>
    content.Length > 5000 ? $"[Binary content: {content.Length:N0} chars]" : content

Combine Multiple Processors

new ReportConfigurationOptions
{
    // Clean text: redact tokens, PII
    RequestResponseMidProcessor = content => content
        .RedactBearerTokens()
        .RedactAccessTokens()
        .RedactPii(),

    // Final layout: split long words that survived redaction
    RequestResponsePostProcessor = content => content
        .SplitLongWords()
};

See Also

Home


Demo


Getting Started

Common Tasks

Integration Guides

Extensions

Configuration

Features

Reference

Clone this wiki locally