Sociologix
← Latest in AI

Automation · · 6 min read

Pino: redact sensitive fields in AI workflow logs

Keep useful job status in structured logs, test nested-field redaction and expose the limits of free-text messages and inconsistent field names.

By Sociologix Editorial

Official GitHub repository preview for pinojs/pino with its tree logo and JSON logger description.
GitHub’s actual preview of the official Pino repository, retrieved October 21, 2026. Repository counters are a dated publisher snapshot, not test results.Image source ↗

Record what happened without copying the whole conversation

When an AI worker stalls, a job ID, status and duration can help locate the problem. Logging the complete prompt or request object can also copy contact details and authorization headers into another system. Start by deciding which operational fields your team actually needs.

This AI-assisted editorial guide tests Pino’s path-based redaction with seven synthetic cases. Four check sensitive fields, one checks ordinary status data and two deliberately reveal gaps. The local test writes JSON into memory; it contacts no log collector, AI provider or customer system. It demonstrates configured field handling, not automatic detection of every sensitive value.

1. Reproduce the checked package version

The example uses the official pinojs/pino package, published as pino under the MIT license. Version 10.4.0 was checked and executed with Node.js 24.19.0 on October 21, 2026. Use a separate scratch directory to keep these fixtures out of an application’s dependency tree. [1]

Run the first three PowerShell commands, save both JavaScript blocks below in order as redact-demo.mjs, then run the final command. Installation needs internet access. The test data contains only invented placeholders, and the example requires no service account or credentials.

mkdir ai-log-demo
cd ai-log-demo
npm install --ignore-scripts "pino@10.4.0"
# Save both JavaScript blocks below as redact-demo.mjs first.
node redact-demo.mjs

2. Define a structured logging contract

Pino accepts redaction paths for nested fields and array entries, plus a replacement value through censor. Our policy covers prompt, contact.email, request.headers.authorization and every email inside the leads array. Configure these paths in application code rather than taking them from a request. [2]

The custom destination below captures each emitted JSON line so assertions can examine the output. Pino’s destination API supports a stream-like object with write. base:null and timestamp:false simplify this fixture; they are not a recommendation to remove useful production metadata. [3]

For a real worker, prefer constructing a small log object containing approved fields. Redaction is another check when known sensitive fields enter that object. It should not become a reason to dump every document, prompt, response or HTTP request.

import assert from "node:assert/strict";
import pino from "pino";

const lines = [];
const logger = pino({
  base: null,
  timestamp: false, // Test output only; retain timestamps in production.
  redact: {
    paths: ["prompt", "contact.email", "request.headers.authorization", "leads[*].email"],
    censor: "[REDACTED]",
  },
}, { write(line) { lines.push(JSON.parse(line)); } });

3. Assert what reaches the destination

Append this block to the same file. The first cases check the replacement marker in a top-level prompt, nested email, nested authorization field and two array entries. The operational case confirms the job ID, status and duration survive unchanged.

The final cases are negative controls. One places an invented email address inside the message string; the other uses Email with a capital E. Their assertions expect those values to remain, making the gaps visible rather than treating them as a successful redaction policy.

// All values below are synthetic; no log collector or AI service is connected.
function check(name, fields, message, verify) {
  logger.info(fields, message);
  verify(lines.at(-1));
  console.log(name + ": passed");
}
check("prompt", { prompt: "demo prompt text" }, "job started",
  line => assert.equal(line.prompt, "[REDACTED]"));
check("contact email", { contact: { email: "demo@example.invalid" } }, "lead received",
  line => assert.equal(line.contact.email, "[REDACTED]"));
check("authorization", { request: { headers: { authorization: "demo-placeholder" } } }, "lookup",
  line => assert.equal(line.request.headers.authorization, "[REDACTED]"));
check("array emails", { leads: [{ email: "one@example.invalid" }, { email: "two@example.invalid" }] }, "batch",
  line => assert.deepEqual(line.leads.map(lead => lead.email), ["[REDACTED]", "[REDACTED]"]));
check("operational fields", { jobId: "demo-job-1", status: "completed", durationMs: 42 }, "job completed",
  line => assert.deepEqual([line.jobId, line.status, line.durationMs], ["demo-job-1", "completed", 42]));

// Negative controls: configured paths do not scan arbitrary strings or other keys.
check("message string remains", {}, "email=demo@example.invalid",
  line => assert.equal(line.msg, "email=demo@example.invalid"));
check("different key case remains", { contact: { Email: "demo@example.invalid" } }, "case check",
  line => assert.equal(line.contact.Email, "demo@example.invalid"));
assert.equal(lines.length, 7);

4. Interpret the seven observed results

All seven cases passed locally. Configured prompt, email and authorization fields received the replacement marker, including both array emails. The operational fields stayed intact. The message string and capitalized Email field retained the synthetic address exactly as the negative controls expected.

Pino documents case-sensitive path matching. These tests also show why a path list is not a general scanner for content inside arbitrary strings. Keep message text fixed and put changing operational values in consistently named fields. If another component uses a different structure, give that structure its own policy and fixtures. [2]

An exception message can contain input text too. Before logging an error object, decide which code or failure category is useful and review what its serializer emits. Do not assume the four paths in this small example cover error messages, stacks, child bindings or every integration’s request format.

5. Review the complete logging path

Run similar assertions against the actual logger your worker uses, including child loggers, serializers and transports. Pino’s API allows child loggers to customize configuration, so review those options rather than assuming every logger instance inherits an identical policy. [3]

Check the output before enabling a remote destination. A downstream formatter, direct console call or separate SDK logger can create another route for content to leave the worker. Use synthetic markers to test those paths; avoid copying production prompts into a debugging fixture.

Finally, define access and retention for the destination separately. Redacting selected fields does not remove data already stored elsewhere, authorize a wider audience or set a deletion schedule. Pair the logging contract with the smallest operational record that lets your team support the workflow, then rerun the checks whenever field names or logging configuration change.

Sources & further reading

  1. Pino 10.4.0 package metadata and license
  2. Pino documentation: redaction paths, censor and matching rules
  3. Pino API: destinations, metadata and child logger configuration

Make AI operations observable with less copied data

Sociologix can help define AI workflow events, diagnostic fields, failure handling and logging policies around your business processes. Bring one worker and the information your support team needs to troubleshoot it.

Talk to Sociologix