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

@tryghost/metrics

v3.7.1

Published

Readme

Metrics

Install

npm install @tryghost/metrics --save

or

pnpm add @tryghost/metrics

Purpose

Ghost metrics facade for collecting and emitting operational metrics across services.

Usage

The default export is a pre-configured GhostMetrics instance, built from the loggingrc file at the process root if one exists. The GhostMetrics class is attached to it for creating additional instances.

const metrics = require('@tryghost/metrics');

await metrics.metric('memory-usage', process.memoryUsage().heapUsed);

const custom = new metrics.GhostMetrics({
    domain: 'my-service',
    metrics: { transports: ['stdout'] },
});

Elasticsearch client implementation

elasticsearch.implementation picks how the Elasticsearch transport talks to the cluster. legacy (the default) uses the official @elastic/elasticsearch client; fetch uses Node's built-in fetch. The fetch implementation will become the only one in the next major version.

elasticsearch.proxy only works with legacy. With fetch, it's ignored with a warning: set HTTP_PROXY or HTTPS_PROXY to match the Elasticsearch URL's scheme (and NO_PROXY as needed), and enable them with NODE_USE_ENV_PROXY=1 or --use-env-proxy. This needs Node 22.21.0+ on the 22.x line, or Node 24+, and applies to every request the process makes.

Sampling

High-volume metrics can be sampled so only a proportion of them are shipped. sampleRate is a number between 0 and 1, and defaults to 1 (ship everything). Configure a default rate, per-metric rates, or both:

const metrics = require('@tryghost/metrics');

const sampled = new metrics.GhostMetrics({
    metrics: {
        transports: ['elasticsearch'],
        sampleRate: 0.5,
        sampleRates: {
            'request-duration': 0.01,
        },
    },
});

A rate can also be overridden per call, which takes precedence over both:

await sampled.metric('request-duration', duration, { sampleRate: 0.001 });

Sampled metrics carry the rate they survived, so consumers can scale counts back up by dividing by it. The Elasticsearch shipper writes it as a sampleRate field, and the stdout shipper appends it to the log line. Metrics shipped at a rate of 1 are not tagged, so an absent sampleRate means the metric was not sampled.

An invalid configured rate throws when the instance is constructed. An invalid per-call rate is ignored in favour of the configured rate, so a bad call site cannot break the code path it is measuring.

Batching

Sampling reduces how many metrics are shipped; batching reduces how many requests they take. With batching on, the Elasticsearch transport buffers documents and ships them with the bulk API, so a hot code path costs one request per batch instead of one per metric. It is off by default:

const metrics = require('@tryghost/metrics');

const batched = new metrics.GhostMetrics({
    metrics: {
        transports: ['elasticsearch'],
        batch: true,
    },
});

batch: true uses the defaults; pass an object to override them:

| Option | Default | Meaning | | --------------- | ----------- | --------------------------------------------------------------- | | enabled | true | Set to false to turn batching off without dropping the config | | size | 100 | Buffered documents that trigger a bulk request | | maxWaitMs | 5000 | How long a document may sit in the buffer before it is shipped | | maxBufferSize | size * 10 | Hard cap on buffered documents; further metrics are dropped |

const batched = new metrics.GhostMetrics({
    metrics: {
        transports: ['elasticsearch'],
        batch: {
            size: 500,
            maxWaitMs: 2000,
        },
    },
});

Documents for different metric names can share a batch, because each bulk operation carries its own index. Sampling still applies first, so only metrics that survive their sample rate are buffered.

Batching changes two things worth knowing about:

  • metric() resolves once the metric is buffered, not once it has been shipped. It was already fire-and-forget for most callers, but a caller that awaited delivery no longer gets it.
  • Buffered metrics are lost if the process exits without draining them. Call flush() from your shutdown handler; it is a no-op when batching is off, so it is always safe to call.
process.on('SIGTERM', async () => {
    await batched.flush();
    process.exit(0);
});

The buffer is bounded: once it holds maxBufferSize documents, further metrics are dropped rather than growing the heap while Elasticsearch is slow or down. Only one bulk request is ever in flight, and a failed batch is dropped without rejecting, so a metric call can never break the code path it is measuring.

Invalid batch config throws when the instance is constructed.

The stdout transport is unaffected - it writes locally, so there is nothing to batch.

Types

Types ship with the package. GhostMetrics is exported as both a value (the class) and a type (an instance of it), alongside the options and shipper types:

import metrics, { GhostMetrics } from '@tryghost/metrics';
import type {
    GhostMetrics as GhostMetricsInstance,
    GhostMetricsOptions,
    MetricsOptions,
    MetricOptions,
    BatchOptions,
    ElasticsearchOptions,
    MetricShipper,
} from '@tryghost/metrics';

const options: GhostMetricsOptions = { domain: 'my-service' };
const custom: GhostMetricsInstance = new GhostMetrics(options);

function ship(instance: GhostMetricsInstance, name: string, value: unknown) {
    return instance.metric(name, value);
}

In a CommonJS file the whole surface is reachable through a single import:

import metrics = require('@tryghost/metrics');

const custom: metrics.GhostMetrics = new metrics.GhostMetrics({ domain: 'my-service' });
const options: metrics.GhostMetricsOptions = {};

Develop

This is a mono repository, managed with Nx.

Follow the instructions for the top-level repo.

  1. git clone this repo & cd into it as usual
  2. Run pnpm install to install top-level dependencies.

Run

  • pnpm dev

Test

  • pnpm lint runs oxlint
  • pnpm test runs lint and tests

Copyright & License

Copyright (c) 2013-2026 Ghost Foundation - Released under the MIT license.