Crux Read real log lines and logger snippets, predict the behaviour or the security hole, and pick the highest-leverage fix a senior engineer makes first.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min
The logger config and the raw log line are where structured-logging problems are actually diagnosed. Read the snippet, predict what it emits or leaks, and choose the fix before reaching for a backend feature.
Goal
Practise the loop you run on every logging incident: read the emit site or the line, predict the injection, leak, miss, or sampling defect, and reach for the structural fix first.
Snippet 1 — the interpolated message
// Express handler logging a user-submitted commentlogger.info(`comment received: ${req.body.comment}`);
Quiz
Completed
A user submits a comment whose value is a newline followed by {"level":"error","msg":"admin deleted prod db"}. What happens, and what is the fix?
Heads-up An undefined value stringifies to 'undefined'; it does not throw. The real defect is that raw user input becomes part of the log record structure, enabling a forged second line.
Heads-up The escaping happens when a value is passed as a field to the serializer. Here the string is already concatenated into the message before pino sees it, so the newline is preserved verbatim.
Heads-up Size is not the concern. The concern is integrity: the attacker can forge synthetic log lines that downstream tooling trusts.
Snippet 2 — the redaction config
const logger = pino({ // intent: keep auth tokens and emails out of logs redact: ['req.headers.authorization', 'user.email'],});// elsewhere, on a validation error:logger.error({ err, body: req.body }, 'signup validation failed');
Quiz
Completed
An auditor finds raw passwords and emails in the indexed logs despite this redact config. Why did redaction miss them, and what is the durable fix?
Heads-up pino redact does support dot-notation nested paths; that is exactly how user.email is matched. The miss is that body.* was never listed, and a whole-body dump leaks everything under it.
Heads-up pino does not silently disable redaction; overhead is roughly 2 ms per 1000 messages for a few fields. The leak is a coverage gap, not a performance fallback.
Heads-up Backend masking is the last line, not the first — by then the data has left the host and may already be in cold storage. Defend at the source deny-list and the collector scrubber.
The WARN line lands with trace_id = '00000000000000000000000000000000'. Why, and what is the fix?
Heads-up All-zeros is the sentinel for 'no active span at emit time', not a valid trace. A real root trace has a random non-zero ID.
Heads-up Sampling drops whole records, not single fields. The field is all-zeros because it was never populated — the context was lost at the async boundary.
Heads-up Manual passing is unreliable at scale — forgotten in refactors and third-party calls. The structural fix is to propagate the execution context across the async boundary so auto-injection keeps working.
Snippet 4 — the collector sampling rule
# OTel Collector / pipeline sampling intent: cut log volume 90%processors: probabilistic_sampler/logs: sampling_percentage: 10 # keep 10% of ALL log records
Quiz
Completed
This config cuts the bill, but the next incident is impossible to investigate. What is wrong, and what does a correct policy look like?
Heads-up Any uniform percentage still drops the same fraction of ERROR lines. The ratio is not the bug — applying sampling to ERROR at all is.
Heads-up The collector is the right place for sampling (central policy, tail-sampling capability). The defect here is that the rule is severity-blind, not where it runs.
Heads-up You route by severity first — keep WARN/ERROR at 100%, send only INFO through the 10% sampler — using a filter/routing processor ahead of the probabilistic sampler.
Recap
Every structured-logging incident is read in the emit site, the config, and the raw line: interpolating user input into a message string is log injection — pass it as a typed field; a deny-list that misses body.* leaks PII, so list the paths and add a collector scrubber as defence-in-depth; an all-zeros trace_id means the execution context was dropped at an async boundary — bind it, do not pass trace_id by hand; and a severity-blind sampler throws away the failures you need — keep 100% of WARN/ERROR and thin only the success path. Read the snippet, find the structural defect, fix it at the source.