@kodobe/kdb-logger
v0.1.0
Published
Product-agnostic structured logger (pino) with OTEL trace correlation for kdb/SafePro services
Readme
@kodobe/kdb-logger
Product-agnostic structured logger for kdb / SafePro Node services. A thin
pino wrapper that emits JSON to stdout, correlates every
record with the active OpenTelemetry span (trace_id / span_id), and conforms
to the shared field contract in
kdb-monitoring/docs/semantic-conventions.md.
It replaces raw console.* logging and the otelConsoleBridge.js stopgap
(kdb-legacy-core#205).
Why
- Structured: real levels (
debug/info/warn/error/fatal) →severity_textthe SigNoz alerts match on; fields instead of stringified blobs. - Traceable:
trace_id/span_idinjected from the active span, so a log is clickable from its trace in SigNoz — the RCA requirement. - Agnostic: only generic
otp.*/merchant.idattributes — no product or brand names. Safe forkdb-legacy-core. - Safe: logging is best-effort and never throws into application code.
Install
yarn add @kodobe/kdb-logger@opentelemetry/api is an optional peer — present in instrumented services,
absent on legacy runtimes (e.g. file-service on Node 14.5), where the logger
still emits structured JSON but without trace ids.
Usage
const { createLogger, ATTRS, errorAttributes } = require("@kodobe/kdb-logger");
const logger = createLogger({ service: "user-service" }); // version/env from env vars
logger.info("user created", { [ATTRS.MERCHANT_ID]: merchantId });
logger.warn("retrying provider", { attempt: 2 });
logger.error("charge failed", err, { [ATTRS.TRANSACTION_ID]: txnId });
// Bind request-scoped context once; every child record carries it:
const reqLog = logger.child({ [ATTRS.MERCHANT_ID]: merchantId, [ATTRS.TRANSACTION_ID]: txnId });
reqLog.info("otp dispatched", { [ATTRS.CHANNEL]: "voice" });Record shape
{
"severity_text": "ERROR",
"time": "2026-06-26T07:51:55.829Z",
"service.name": "user-service",
"service.version": "1.3.0",
"deployment.environment": "staging",
"trace_id": "0af7651916cd43dd8448eb211c80319c",
"span_id": "b7ad6b7169203331",
"merchant.id": "m_123",
"error.message": "boom",
"error.type": "TypeError",
"exception.stacktrace": "TypeError: boom\n at ...",
"body": "charge failed"
}OTLP export wiring (consumer side)
kdb-logger produces the records; OTLP export is handled by
@opentelemetry/instrumentation-pino in the service's otel.js, which forwards
pino records to the OTEL log pipeline and reinforces trace injection:
const { PinoInstrumentation } = require("@opentelemetry/instrumentation-pino");
// ...inside the NodeSDK instrumentations list:
instrumentations: [
getNodeAutoInstrumentations({ "@opentelemetry/instrumentation-fs": { enabled: false } }),
new PinoInstrumentation(),
],Once a service is fully migrated to kdb-logger and no-console is enforced, its
otelConsoleBridge.js copy is removed.
API
| Export | Description |
|---|---|
| createLogger({ service, version, env, level, stream }) | Root logger. All fields fall back to OTEL_SERVICE_NAME/APP_NAME, npm_package_version, NODE_ENV, LOG_LEVEL. stream is for tests. |
| logger.{debug,info,warn,error,fatal}(msg, attrs?) | error/fatal accept (msg, err, attrs?) — the error is flattened to error.* / exception.stacktrace. |
| logger.child(bindings) | Returns a logger with bindings stamped on every record. |
| errorAttributes(err) | Flatten an error to fields. |
| cleanAttributes(obj) | Drop null/undefined/blank values. |
| ATTRS | Canonical attribute-name constants. |
Test
yarn test # jest — unit + real-API trace-correlation integration
yarn lint # eslint (no-console: error)