Observability

Combined metrics + tracing tap surface — every framework hot path uses this one primitive to emit both a span and a counter bump in one call, with redact-aware metadata and breadcrumb integration into the audit chain.

tap(name, attrs, fn) wraps fn in a tracing span (via b.tracing.tap) and bumps a metrics counter named name (via b.metrics.tap) when the function settles, on both the success and failure branches. event(name, value, labels) is the fire-and-forget shape — fires the counter only, no span — and safeEvent wraps it in a try/catch so per-request hot paths can't crash the request that triggered them when the metrics registry has a misconfigured counter or label name.

timed(name, fn, labels) measures wall-clock duration of an operation and emits a counter event with outcome: "ok" / "fail" plus duration_ms in the labels — the standard pattern for per-call SLO tracking. SEMCONV carries the OTel semantic-convention attribute names (1.27+ stable namespace) so operators wiring the framework's tap into an OTel SDK don't maintain an aliasing table.

traceContext.parse / traceContext.build parse and emit the W3C traceparent header per RFC; traceContext.parseTracestate / traceContext.buildTracestate cover the tracestate companion header (32-entry / 512-char W3C cap). baggage.parse / baggage.build cover the W3C Baggage header for cross-service user context (tenant / region / experiment).

The drop-silent contract is intentional — observability runs in request hot paths where throwing on a misnamed metric would crash the request that triggered the emit. Bad input on event / safeEvent is dropped silently; bad input on tap throws at boot-time call sites where operators can fix typos before they corrupt the span tree AND the metrics route at the same time.

b.observability.setTap(handler) #

0.7.40

Install an external tap handler that receives every (name, value, labels) triple in addition to the framework's metrics module. Wired by b.otelExport.create() so an OTLP/HTTP exporter sees the same hot-path counters the framework emits internally. Pass null to remove the previously-installed handler.

b.observability.setTap(function (name, value, labels) {
  console.log("[obs]", name, value, labels);
});
b.observability.event("audit.record", 1,
  { action: "auth.login", outcome: "success" });
// → "[obs] audit.record 1 { action: 'auth.login', outcome: 'success' }"
b.observability.setTap(null);   // remove

b.observability.setRedactor(redactor) #

0.14.27

Override the redactor applied to every span / metric attribute VALUE before the OTLP exporter serializes it onto the wire. Telemetry is a first-class egress sink: an attribute holding a user email, bearer token, or secret would otherwise reach the collector in plaintext (CWE-532). Redaction is ON by default — the default redactor composes b.redact.redact and fires both field-name and value-shape rules; this setter only lets an operator swap in a stricter or domain-specific scrubber.

The redactor is redactor(value, key) and returns the value to export. It runs on the export hot path, so a throw is caught and the attribute is dropped (never exported raw) — a redactor that throws can only shrink the egress surface, never widen it. Pass null to restore the default b.redact.redact-backed redactor.

b.observability.setRedactor(function (value, key) {
  if (key === "enduser.id") return "[REDACTED]";
  return b.redact.redact(value, { parentKey: key });
});
b.observability.setRedactor(null);   // restore the default

b.observability.getRedactor() #

0.14.27

Return the redactor currently applied to span / metric attribute values on the OTLP egress path. The OTLP exporter calls this to scrub every attribute value before serialization; operators rarely need it directly. When no override has been installed it returns the default b.redact.redact-backed redactor.

var redactor = b.observability.getRedactor();
redactor("Bearer eyJabc.eyJdef.sig", "authorization");
// → "[REDACTED]"   (field-name rule on the "authorization" key)

b.observability.redactAttrs(attrs) #

0.15.4

Run every value of a telemetry attribute map through the active redactor and return a NEW { key: redactedValue } object. The OTLP exporters call this on span, span-event, metric, log-record, and resource attributes before serialization so no attribute value crosses the egress boundary unscrubbed (the HTTP-JSON and gRPC log sinks included) (CWE-532: insertion of sensitive information into an externally-shipped sink). A key whose redactor throws is DROPPED — failing toward dropping, never exporting the raw value; null / undefined values pass through for the type-encoder to handle.

b.observability.redactAttrs({ "http.method": "GET", authorization: "Bearer x" });
// → { "http.method": "GET", authorization: "[REDACTED]" }

b.observability.tap(name, attrs, fn) #

stable0.7.0

Wrap fn in a tracing span (via b.tracing.tap) and bump a metrics counter named name (via b.metrics.tap) when the function settles. The same attrs object becomes both span attributes and metric labels. Counter fires on both the success and failure paths so dashboards never miss a failure-rate increment. The two-arg form tap(name, fn) skips attributes. Throws on bad input — typos in name would silently corrupt both the span tree and the metrics route, so this is a config-time boundary.

var rows = await b.observability.tap("db.query",
  { table: "users" },
  async function (span) {
    span.setAttribute("db.statement", "SELECT id FROM users");
    return await db.queryAll("SELECT id FROM users");
  });
// span ended, framework_db_query_total bumped by 1

b.observability.event(name, value, labels) #

stable0.7.0

Fire-and-forget counter emit — same shape as b.metrics.tap but routed through observability so the operator's external tap (setTap) sees it too. Drop-silent on bad name by design: this runs in hot paths where throwing on a typo would crash the request that triggered the emit. Use tap instead when you also want a span around the emitting code.

b.observability.event("queue.enqueue", 1, { queueName: "email" });
b.observability.event("error.construct", 1, { class: "DatabaseError" });

b.observability.safeEvent(name, value, labels) #

0.7.40

Wraps event in a try/catch so per-request observability emits cannot crash the request that triggered them when the metrics registry has a misconfigured counter or label name. Replaces the per-file _emitEvent helper that several modules previously duplicated.

// Inside a request handler — even with a typo in label name,
// the request still completes.
b.observability.safeEvent("auth.attempt", 1, { outcome: "success" });

b.observability.safeEmit(sink, name, value, labels) #

0.15.13

The sink-aware sibling of safeEvent: emit a metric event to an explicitly-configured observability sink (a per-instance observability object) when one is supplied, otherwise fall back to the global registry — each path wrapped in a try/catch so a misconfigured counter never crashes the request that triggered it. Replaces the _emitObs + _safeGlobalObs helper pair that the auth brute-force modules (bot-challenge / lockout / session-device-binding) each duplicated to route a configured observability instance with a global fallback.

b.observability.safeEmit(opts.observability, "auth.lockout.hit", 1,
  { namespace: ns });

b.observability.makeCounterEmitter(sink) #

stable0.15.13

Bind a per-instance counter emitter. Returns (name, labels) that increments metric name by 1 (with labels) on the supplied observability sink, drop-silent on a sink throw and falling back to the global tap when sink is null. The shorthand every primitive that accepts an observability instance wrapped in a private _emitObs(name, labels) closure around safeEmit(obsInst, name, 1, labels) — build it once with the instance and call the returned emitter.

var b = require("blamejs");
var emit = b.observability.makeCounterEmitter(myObsInstance);
emit("auth.lockout.tripped", { actor: "alice" });

b.observability.namespaced(prefix, gateFlag?) #

stable0.15.13

Build a drop-silent metric emitter bound to one name prefix — the shape every primitive hand-rolled as a private _emitMetric(verb, n, labels) closure (try { observability().safeEvent("ns." + verb, n || 1, labels || {}); } catch {}). The returned function prefixes verb with prefix + ".", defaults the value to 1 and labels to {}, and routes through safeEvent so a misconfigured counter / label name cannot crash the caller. The metric sibling of b.audit.namespaced. Metrics emit unconditionally by default; pass gateFlag === false to disable a primitive's own metrics in lockstep with its audit (the few primitives that gate both behind one opts.audit).

var emitMetric = b.observability.namespaced("network.byte_quota");
emitMetric("exceeded", 1, { key: k });
// → safeEvent("network.byte_quota.exceeded", 1, { key: k })
emitMetric("reset");
// → safeEvent("network.byte_quota.reset", 1, {})

b.observability.timed(name, fn, labels) #

stable0.7.40

Measure wall-clock duration of a sync or async operation and emit a counter event with outcome: "ok" / "fail" plus duration_ms in the labels. Returns the wrapped function's return value verbatim; on throw, emits the failure event with error_type set to the error's name and re-throws. The name argument MUST be a stable string (not derived from input) to keep the metric cardinality bounded — operators dynamically scoping should put variable parts into labels.

var rows = await b.observability.timed("db.query",
  async function () {
    return await db.queryAll("SELECT id FROM users");
  },
  { [b.observability.SEMCONV.DB_OPERATION_NAME]: "select" });
// → emits db.query with { outcome: "ok", duration_ms: 12,
//   "db.operation.name": "select" }

Last updated 2026-08-08T16:39:49.652Z by seeder.