Why I built a tiny log‑anonymizer to replay production traces in staging (and the day it failed)

How I built a simple pipeline to scrub and replay production logs in staging so bugs are reproducible — the leak we found, why regexes aren't enough, and the maintenance tradeoffs.

Written by: Arjun Malhotra

A laptop on a table with code visible on the screen and hands typing
Photo by Mitchell Luo on Unsplash

It was 2:10 a.m. My co‑founder pinged me with a stack trace that only appeared when a real user flow hit our payments gateway. Devs could not reproduce it locally because the production logs had the user context we needed — and our legal team would not let us copy raw logs to staging.

We had two choices: try blind guesses at reproducing the chain of calls, or give the team better input without handing them raw PII. Guessing lost. So I built a tiny log anonymizer and replay pipeline that changed how we debugged payment flows. It cost me a weekend, a ₹300 VPS, and a hard lesson about base64.

Why I needed more than sampling or redaction

We already sampled logs and kept structured traces in S3. But sampling loses context across services. Full logs are noisy, and legal says: don’t give developers direct access to phone numbers, emails, or payment tokens.

Redaction libraries exist. But our logs were a mix: JSON traces, middleware logs with quoted JSON, and occasionally entire payloads base64-encoded (legacy microservice that saved blobs). A naive regex on “email=…” will miss those. Worse, redaction that strips fields breaks trace joins — hashes of user IDs were used for correlating across services.

So I set three practical goals:

What I actually built (short and practical)

I kept it minimal: a streaming scrubber + a small store of scrubbed slices.

Components

Why HMAC instead of hashing or removing fields

When you remove a user_id field, traces lose correlation. Plain hash is reversible if an attacker gets the salt. HMAC with a KMS‑backed key gives us deterministic pseudonyms we can revoke (rotate keys) and an auditable KMS access history. It cost us no more than a few API calls and some cheap cryptography.

The day it failed (and the real cost)

Two weeks after deploying, we got an alert: a scrubbed slice had a raw email visible. It came from a nested base64 payload. A service stored an encrypted/encoded blob (for legacy reasons) that contained user profile JSON. Our scrubber decoded nothing. The root cause: we assumed JSON or plain text.

The realistic tradeoffs:

Maintenance I accepted (and what I automated)

I didn’t want this to become another “works on my laptop” tool, so I automated three things:

Why teams in India should care

Two realities make this practical in our context: 1) bandwidth is precious — pulling a scrubbed 30‑minute trace as gzipped NDJSON is cheaper than shipping full logs, and 2) legal/contract obligations with Indian banks and PSPs mean you cannot be careless. The cost is small: a ₹300–₹1,000 monthly hosting/egress cost beats a compliance headache.

One honest constraint: it added friction to small bug hunts

This pipeline made big, tricky reproductions easier, but for quick developer experiments it added friction. Instead of “grab prod log” you now have to request a scrubbed slice or run the CLI. That friction is intended, but it slows the quick & dirty debugging loop. We accepted that tradeoff: if you want raw data you go through a documented process and an audit.

What I walked away with

Good redaction is not a single regex; it’s a set of conservatively auditable transformations plus a quarantine for anything unfamiliar. If you need production fidelity in staging, build a tiny, testable scrubber that preserves correlation keys (HMAC them) and blocks everything that doesn’t parse. Expect to invest a weekend, a small VPS, and ongoing rule updates — but you’ll save hours on bug hunts that otherwise turn into guesses.

If you try this, start with a single service and a single two‑hour window. Add tests. Add the panic button on day one. And plan for base64.