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

@nage-api/observability

v1.0.0-beta.4

Published

Logging, tracing, metrics and health for @nage-api

Readme

@nage-api/observability

Logging, tracing, metrics and health (PLAN.md §18, §21, §25 P1).

Ports with no-op defaults. The framework's own instrumentation — a span per request, a counter per response — costs nothing when nobody is collecting, which is the normal case in development and in tests. An application that wants OpenTelemetry or a hosted metrics backend binds an adapter, and no caller changes.

| Concern | Port | Default | Enabled by | | ------- | ------------- | ------------------- | ------------------------------- | | Logging | LoggerPort | JsonLogger (core) | always | | Tracing | TracerPort | NoopTracer | observability.tracing.enabled | | Metrics | MetricsPort | NoopMetrics | observability.metrics.enabled | | Health | — | HealthRegistry | always |

Health

/health/live never touches a dependency. If a database outage made liveness fail, the orchestrator would restart every pod and turn a degraded system into an outage.

/health/ready does check, concurrently, each probe under its own timeout — because a dependency that hangs must be reported down in bounded time rather than holding the probe open until the orchestrator gives up.

It also answers 503 draining the moment a shutdown signal arrives, before any probe runs: dependency state was never the only reason to stop taking traffic, and a draining instance is about to close the connection it is being offered. The flag comes from NAGE_LIFECYCLE, published by NageCoreModule and flipped by installShutdown; /health/live ignores it deliberately, because failing liveness during a drain gets the process killed before it can finish.

import { Module } from '@nestjs/common';
import { NageObservabilityModule, healthCheck } from '@nage-api/observability';

// Whatever the application already holds: a Sequelize instance, an ioredis
// client. A probe is any function that resolves when the dependency answers.
declare const repository: { ping(): Promise<unknown> };
declare const redis: { ping(): Promise<unknown> };

@Module({
  imports: [
    NageObservabilityModule.forRoot({
      checks: [
        healthCheck('database', () => repository.ping()),
        healthCheck('cache', () => redis.ping(), { critical: false }),
      ],
    }),
  ],
})
export class AppModule {}

critical: false degrades the report instead of failing it. Pulling a node out of the load balancer because Redis blinked makes the outage worse. Readiness answers 503 only when something critical is down.

Failure detail is withheld by default: a probe endpoint is unauthenticated, and a connection string in an error message is a disclosure. exposeHealthDetail: true opts in.

Metrics

An in-process registry with Prometheus text exposition, served from /metrics when metrics are enabled. Counters, gauges and histograms; nothing else.

The thing it takes seriously is cardinality. A label whose value comes from a request path or a user id creates a new time series per value, and a metrics endpoint that grows without bound eventually takes the scraper down with it. So the HTTP interceptor labels by route template (/orders/:id, never /orders/1), and the registry caps series per metric name and exposes nage_metrics_dropped_total rather than growing silently or dropping silently.

Built-in series: nage_http_requests_total{method,route,status}, nage_http_request_duration_ms{method,route}, nage_http_requests_in_flight. In-flight is decremented on the error path too, so a failing endpoint does not show a permanent backlog.

Tracing

NAGE_TRACER is what application code injects; it resolves to whatever the deployment bound, or to NoopTracer, and the call site does not change.

import { Inject, Injectable } from '@nestjs/common';
import { NAGE_TRACER, type TracerPort } from '@nage-api/observability';

interface Order {
  readonly id: string;
  readonly total: number;
}

declare const gateway: { charge(order: Order): Promise<{ reference: string }> };

@Injectable()
export class PaymentService {
  constructor(@Inject(NAGE_TRACER) private readonly tracer: TracerPort) {}

  async charge(order: Order): Promise<{ reference: string }> {
    return this.tracer.trace(
      'charge-card',
      async (span) => {
        span.setAttribute('order.id', order.id);
        return gateway.charge(order);
      },
      { kind: 'client' },
    );
  }
}

The span ends in a finally, so a thrown error still closes it — an unclosed span reads as an operation that never returned. RecordingTracer is the same interface backed by memory, so a test can assert on spans without a collector.

The span per request comes from the same HTTP interceptor that records the metrics, so that interceptor is registered as soon as either metrics or tracing is enabled. A deployment that turns tracing on and leaves metrics off still gets its request spans.

Two details: the correlation id from the request context is attached automatically, which is what ties a span to the log lines and the response envelope for the same request; and string attributes go through the same redaction that guards log lines, because attributes reach a collector the team may not control.

Not yet implemented

  • The OpenTelemetry adapter itself. TracerPort is the seam; the exporter, sampler and propagation headers are not written.
  • pino as a LoggerPort implementation (§8 names it); JsonLogger in @nage-api/core covers structured output today.
  • Sentry. ObservabilityConfig.sentry is declared and unread.
  • Metric-name collision detection: registering the same name as two different types produces two series rather than an error.
  • Binding your own metrics sink while metrics.enabled is true still mounts /metrics from the built-in registry, which then renders empty: the framework's series go to the bound sink, and only that sink has them.