Logging and Privacy
HeronJS logging uses strict redaction by default. Arbitrary messages, error text/stacks/causes, request data and personal information, credentials, and unknown dictionary keys are removed before any output sink sees them. JSON is the default output format; optional pretty output receives the same sanitized records.
Reviewed events and operational data
import {configureLogging, defineLogEvent, Logger} from '@heronjs/common';
const component = defineLogEvent('service.storage');
const queryCompleted = defineLogEvent('database.query.completed');
configureLogging({level: 'info'});
const logger = new Logger(component);
logger.info(queryCompleted, {durationMs: 12, count: 3, success: true});
logger.error('Account update failed', new Error('Sensitive account detail'));
// The second record's message and exception details are [REDACTED].Event labels are reviewed literals in source code. Never construct them from request values or personal information, error messages, tenant identifiers, subjects, URLs, or other runtime data. Dynamic string arguments are rejected by the factory’s TypeScript contract, and forged event objects cannot bypass runtime validation. Passing an ordinary string to a Logger method is supported, but the entire string is redacted. Logger target strings/class instances do not expose their names or caller paths; use a reviewed event to name the component.
The data allowlist retains:
| Field | Accepted value |
|---|---|
statusCode | Integer HTTP status from 100 through 599 |
durationMs, count, attempt, retryCount, bytes | Finite, nonnegative operational numbers |
success, retryable | Booleans |
code | A recognized framework error code |
Nested operational fields are supported under object, data, context, details, error, cause, and errors. Unknown fields and arbitrary string values are redacted. Input is never mutated; getters, toJSON, and inspection hooks are not executed. Cycles, opaque objects, revoked proxies, and excessive nesting are handled conservatively.
Use allowlisted fields only for their documented operational purpose. Do not encode personal information, credentials, or request payloads as operational values. Log timestamps describe emission time.
Sinks and additional redaction
configureLogging({stdio: true}) directs default output to stderr so protocol stdout stays clean. Existing Logger instances follow new configuration on their next write. format: 'pretty' selects pretty output; destination accepts Pino’s DestinationStream for an explicitly configured sink.
Per-logger redact paths are additive. For example, {redact: ['data.count']} removes the already-sanitized count from that logger. Empty paths or options on another logger cannot disable the mandatory allowlist. There is no raw-message or raw-error opt-out.
Logger no longer automatically reports errors to Sentry. Explicit capture(error, extras?) sanitizes the report and clears ambient user/request context, tags, breadcrumbs, attachments, and exception stacks before the SDK transport. Framework code must not create another raw-error reporting path. Application-defined SDK hooks and downstream destinations remain application responsibilities.
Worker stdout/stderr are drained without relaying their contents; the parent emits a fixed redaction notice once per worker that writes output. The standalone thread error fallback uses a fixed message. EBS onLog receives stream.started, stream.item, stream.batch, and stream.ended with redacted data. Transport payloads are unchanged. The subject argument is no longer accepted by GenericMessagingStreamer; construct it with (transport, defaultSignal?).
Scope and responsibilities
Framework redaction applies to the logging and telemetry paths described above. It does not validate application-specific logging paths or external destinations. Configure access controls and retention policies for those destinations, and review custom SDK hooks before sending operational data.
Based on 4.0.0 release source (4.0.0).