npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zudojs/observability

v1.0.0

Published

Structured logging, metrics, tracing, context propagation, and exporters for Zudojs applications.

Readme

@zudojs/observability

Structured logging, metrics, tracing, context propagation, and telemetry exporters for Zudojs applications.

Installation

npm install @zudojs/observability

Quick Start

createObservability is the entry point. It wires the logger, the metrics registry, the tracer and the propagation manager into one object, and owns their shutdown.

import {
  createObservability,
  createProbabilitySampler,
  LogLevel,
} from "@zudojs/observability";

const obs = createObservability({
  serviceName: "checkout-api",
  serviceVersion: "1.4.0",
  environment: "production",
  logLevel: LogLevel.INFO,

  // Opt in to redaction — see "Redaction" below.
  redaction: {},

  // Sample 10% of traces. The decision is derived from the trace ID, so a
  // trace is never sampled in half across services.
  sampler: createProbabilitySampler(0.1),

  // Console exporters are the development default; turn them off in
  // production and supply real ones.
  useConsoleExporters: false,
});

obs.logger.info("server started", { port: 3000 });
obs.metrics.counter("http.requests.total", { route: "/checkout" }).increment();

const span = obs.tracer.startSpan("handle-request");
span.setAttribute("http.method", "POST");
span.end();

await obs.shutdown(); // drains every buffer, then closes the exporters

Logging

Records are structured, level-filtered, and stamped with the ambient trace.

obs.logger.info("order placed", { orderId: 42 });

// An Error goes in the third parameter. Putting it in the context would
// serialize to {} — Error's fields are non-enumerable.
obs.logger.error("charge failed", { orderId: 42 }, error);

const child = obs.logger.child("payments", { provider: "stripe" });
child.setLevel(LogLevel.DEBUG); // adjustable at runtime
await obs.logger.flush();

Every record carries traceId and spanId when a propagation context is active, so logs and traces line up without threading IDs by hand.

Redaction

Redaction is off unless you configure it, and on once you do. The logger applies it to every record before any transport sees it, and the tracer applies it to every span attribute and span event attribute before any processor or exporter sees it — a Bearer token attached to a span is redacted the same way one written to a log field is.

const obs = createObservability({
  serviceName: "api",
  redaction: {
    // Defaults cover passwords, tokens, cookies, keys and card numbers.
    fields: ["password", "token", "ssn"],
    patterns: [/^x-.*-secret$/i],
    // "contains" (the default) matches on word boundaries: "userPassword"
    // and "x-api-key" match, "shippingAddress" and "authorId" do not.
    matchMode: "contains",
    replacement: "[REDACTED]",
  },
});

obs.logger.info("login", {
  username: "ada",
  password: "hunter2", // → "[REDACTED]"
  headers: [{ authorization: "…" }], // arrays are traversed too
});

Traversal handles the shapes secrets actually arrive in: arrays, nested objects, and cyclic graphs (a request object in a log context becomes [CIRCULAR] rather than a stack overflow). Class instances — Error, Date, Map — are left intact instead of being flattened to {}.

redactObject, redactValue and createStructureRedactor are exported for use outside the logger.

Metrics

Counters, gauges and histograms, keyed by name and labels.

obs.metrics.counter("jobs.processed", { queue: "email" }).increment();
obs.metrics.gauge("queue.depth", { queue: "email" }).setValue(17);

const latency = obs.metrics.histogram("http.duration", { route: "/checkout" });
latency.record(87);

const value = latency.getValue();
value.p95; // interpolated from the bucket boundaries
value.mean;

Histograms keep cumulative buckets, so p50/p90/p95/p99 are real answers rather than something you have to reconstruct from count and sum. Pass your own boundaries when the unit is not milliseconds.

Values that cannot be aggregated — NaN, Infinity, a negative counter increment — throw a MetricValueError rather than being silently dropped.

Cardinality. The registry caps the number of distinct series (default 10,000). A label carrying a user ID is the usual way to blow past that, and the cap turns an unbounded memory leak into a reported error. Lower it — or set the histogram boundaries used registry-wide — through metrics:

const obs = createObservability({
  serviceName: "api",
  metrics: { maxSeries: 2_000 },
  onError: (error, source) => report(error, source), // cardinality is reported here
});

One metric name may only ever be one type: registering counter("latency") and then histogram("latency") throws, because a document carrying the same name as two types is rejected wholesale by OTLP and Prometheus.

Metrics are exported by a PeriodicMetricReader, which the facade starts for you when a metricExporter is configured. metricExportIntervalMs: 0 disables the periodic export while still collecting a final snapshot on flush() and shutdown():

const obs = createObservability({
  serviceName: "api",
  metricExporter: myExporter,
  metricExportIntervalMs: 30_000,
});

Tracing

const span = obs.tracer.startSpan("db.query", { kind: SpanKind.CLIENT });
try {
  span.setAttribute("db.statement", sql);
  return await run(sql);
} catch (error) {
  span.recordError(error as Error);
  throw error;
} finally {
  span.end();
}

Spans are capped: 128 attributes, 128 events, 4096-character values by default, with the overflow counted in droppedAttributes / droppedEvents. Durations use a monotonic clock, so a clock adjustment cannot produce a negative one. setStatus(status, message) keeps the message — it is exported alongside the status.

Stack traces recorded by recordError reach the backend unredacted; set captureStackTraces: false to omit them.

Sampling

import {
  createAlwaysOnSampler,
  createParentBasedSampler,
  createProbabilitySampler,
} from "@zudojs/observability";

createObservability({
  serviceName: "api",
  sampler: createParentBasedSampler({ root: createProbabilitySampler(0.05) }),
});

The decision is stamped into SpanContext.traceFlags and inherited by every child, which is what keeps a sampled trace whole across services.

Context propagation

import { createPropagationContext } from "@zudojs/observability";

await obs.propagation.run(createPropagationContext({ requestId }), async () => {
  obs.logger.info("handling"); // carries traceId, spanId
  await handle();
});

obs.propagation.current(); // undefined outside a run() scope

current() returns undefined when there is no active context, so "no trace" stays distinguishable from a real one.

Exporters

Console exporters ship for development. They serialize defensively — a circular attribute or a BigInt cannot make an exporter throw inside the logging path — and emit one line per record by default, which is what log shippers parse.

import {
  createConsoleSpanExporter,
  createBatchSpanProcessor,
  createSimpleSpanProcessor,
} from "@zudojs/observability";

createObservability({
  serviceName: "api",
  processors: [
    createBatchSpanProcessor({
      exporter: createConsoleSpanExporter({ pretty: true }),
      batchSize: 512,
      maxQueueSize: 2048,
      onError: (error, source) => console.error(source, error),
    }),
  ],
});

Both the span and log processors have a hard queue cap: past it, records are dropped and counted rather than growing the process until it dies. Export failures go to onError instead of vanishing.

Exporters for OpenTelemetry, Prometheus or Datadog belong in separate packages; implement SpanExporter, LogExporter or MetricExporter.

Scopes

resource() creates a view that differs only in its resource attributes, sharing the parent's logger, registry, processors and exporters:

const podScoped = obs.resource({ "k8s.pod": process.env.POD_NAME });

Shut down the root; a scope does not own the pipeline. flush() works from either, because the buffers it drains are the shared ones.

Disabling telemetry

import { createNoopObservability } from "@zudojs/observability";

const obs = enabled ? createObservability(config) : createNoopObservability();

Errors

ObservabilityError and its subclasses — ExporterError, ObservabilityConfigError, MetricValueError — cover the cases where this package throws. Transport failures never throw; they are reported through onError.

Use Cases

  • Distributed tracing
  • Performance monitoring
  • Error tracking
  • SLA and SLO monitoring