Skip to content

Hot-Shots (StatsD) Mixin Server

NPM Version

Source

Adds StatsD metrics functionality to the LogLayer logging library using hot-shots. The mixin provides a fluent builder API for sending metrics to StatsD, DogStatsD, and Telegraf through a stats property on LogLayer instances.

Installation

This mixin requires the hot-shots library to be installed in your project.

bash
npm install loglayer @loglayer/mixin-hot-shots hot-shots
bash
yarn add loglayer @loglayer/mixin-hot-shots hot-shots
bash
pnpm add loglayer @loglayer/mixin-hot-shots hot-shots

Usage

First, create and configure your StatsD client, then register the mixin before creating any LogLayer instances:

typescript
import { LogLayer, useLogLayerMixin, ConsoleTransport } from 'loglayer';
import { StatsD } from 'hot-shots';
import { hotshotsMixin } from '@loglayer/mixin-hot-shots';

// Create and configure your StatsD client
const statsd = new StatsD({
  host: 'localhost',
  port: 8125
});

// Register the mixin (must be called before creating LogLayer instances)
useLogLayerMixin(hotshotsMixin(statsd));

// Create LogLayer instance
const log = new LogLayer({
  transport: new ConsoleTransport({
    logger: console
  })
});

// Use StatsD methods through the stats property
log.stats.increment('request.count').send();
log.stats.decrement('request.count').send();
log.stats.timing('request.duration', 150).send();
log.stats.gauge('active.connections', 42).send();

Builder Pattern

All stats methods use a fluent builder pattern. You can chain configuration methods before calling send():

typescript
// Increment with value, tags, and sample rate
log.stats.increment('request.count')
  .withValue(5)
  .withTags(['env:production', 'service:api'])
  .withSampleRate(0.5)
  .withCallback((error, bytes) => {
    if (error) {
      log.withError(error).error('Error sending metric');
    } else {
      log.info(`Sent ${bytes} bytes`);
    }
  })
  .send();

// Timing with tags
log.stats.timing('request.duration', 150)
  .withTags({ env: 'production', method: 'GET' })
  .send();

// Gauge with sample rate
log.stats.gauge('active.connections', 42)
  .withSampleRate(1.0)
  .send();

TypeScript Type Usage

When using TypeScript interfaces (as recommended in the TypeScript tips page), mixin methods won't be recognized on the ILogLayer interface. Since it's difficult to extend ILogLayer directly (TypeScript doesn't allow extending interfaces from external modules in a way that captures mixin methods), you can create a composite type using an intersection type (&) that combines ILogLayer with the mixin interface:

typescript
import type { ILogLayer } from 'loglayer';
import type { IHotShotsMixin } from '@loglayer/mixin-hot-shots';

export type ILogLayerWithMixins = ILogLayer & IHotShotsMixin<ILogLayer>;

// Create your instance of LogLayer
const log: ILogLayerWithMixins = new LogLayer();

// Replace your usage of ILogLayer with ILogLayerWithMixins instead
function getLogger(): ILogLayerWithMixins {
  return log;
}

Troubleshooting

If TypeScript types still do not work after following the TypeScript Type Usage instructions, you can manually add the module declaration to a type declaration file. Create a type declaration file (e.g., loglayer.d.ts or add it to an existing declaration file) and include the following:

typescript
import type { IHotShotsMixin } from '@loglayer/mixin-hot-shots';

declare module "loglayer" {
  interface LogLayer extends IHotShotsMixin<LogLayer> {}
  interface MockLogLayer extends IHotShotsMixin<MockLogLayer> {}
}

This will ensure TypeScript recognizes the mixin methods on your LogLayer instances.

Migration from v2 to v3

In v3, the mixin API has been refactored to use a builder pattern with a stats property instead of direct methods on LogLayer.

API Changes

Before (v2):

typescript
// Mixin registration
useLogLayerMixin(hotshotsMixin(statsd));

// Usage
log.statsIncrement('request.count').info('Request received');
log.statsDecrement('request.count');
log.statsTiming('request.duration', 150).info('Request processed');
log.statsGauge('active.connections', 42).info('Connection established');

After (v3):

typescript
// Mixin registration (same API, but now uses stats property)
useLogLayerMixin(hotshotsMixin(statsd));

// Usage (now uses stats property with builder pattern)
log.stats.increment('request.count').send();
log.stats.decrement('request.count').send();
log.stats.timing('request.duration', 150).send();
log.stats.gauge('active.connections', 42).send();

Migration Steps

  1. Update method calls: Replace all stats* methods with the stats property (mixin registration stays the same: hotshotsMixin(statsd)):

    • log.statsIncrement(...)log.stats.increment(...).send()
    • log.statsDecrement(...)log.stats.decrement(...).send()
    • log.statsTiming(...)log.stats.timing(...).send()
    • log.statsGauge(...)log.stats.gauge(...).send()
    • And so on for all stats methods
  2. Remove LogLayer chaining: The stats methods no longer return LogLayer instances. If you were chaining logging methods after stats calls, you'll need to separate them:

    typescript
    // Before
    log.statsIncrement('request.count').info('Request received');
    
    // After
    log.stats.increment('request.count').send();
    log.info('Request received');
  3. Update method parameters: Some methods now use the builder pattern for optional parameters:

    typescript
    // Before
    log.statsIncrement('counter', 5, 0.5, ['tag:value']);
    
    // After
    log.stats.increment('counter')
      .withValue(5)
      .withSampleRate(0.5)
      .withTags(['tag:value'])
      .send();

Configuration

The hotshotsMixin function requires a configured StatsD client instance from the hot-shots library.

Required Parameters

NameTypeDescription
clientStatsDA configured StatsD client instance from the hot-shots library

For detailed information about configuring the StatsD client, see the hot-shots documentation.

Available Methods

All stats methods are accessed through the stats property on LogLayer instances. Each method returns a builder that supports chaining configuration methods before calling send() or create(). For detailed usage information, parameters, and examples, refer to the hot-shots documentation.

Accessing the StatsD Client

MethodReturnsDescription
getClient()StatsDReturns the underlying hot-shots StatsD client instance that was configured when the mixin was registered. Useful for accessing advanced features that aren't directly exposed through the mixin API.

Counters

MethodReturnsDescription
stats.increment(stat)IIncrementDecrementBuilderIncrements a counter stat. Use withValue() to specify the increment amount (defaults to 1)
stats.decrement(stat)IIncrementDecrementBuilderDecrements a counter stat. Use withValue() to specify the decrement amount (defaults to 1)

Builder methods: withValue(value), withTags(tags), withSampleRate(rate), withCallback(callback), send()

Timings

MethodReturnsDescription
stats.timing(stat, value)IStatsBuilderSends a timing command. The value can be milliseconds (number) or a Date object
stats.timer(func, stat)ITimerBuilderWraps a synchronous function to automatically time its execution. Returns a builder that supports chaining with withTags(), withSampleRate(), and withCallback(). Call create() to get the wrapped function
stats.asyncTimer(func, stat)IAsyncTimerBuilderWraps an async function to automatically time its execution. Returns a builder that supports chaining with withTags(), withSampleRate(), and withCallback(). Call create() to get the wrapped function
stats.asyncDistTimer(func, stat)IAsyncDistTimerBuilderWraps an async function to automatically time its execution as a distribution metric (DogStatsD only). Returns a builder that supports chaining with withTags(), withSampleRate(), and withCallback(). Call create() to get the wrapped function

Builder methods for timing: withTags(tags), withSampleRate(rate), withCallback(callback), send()

Builder methods for timer, asyncTimer, and asyncDistTimer: withTags(tags), withSampleRate(rate), withCallback(callback), create()

typescript
// Wrap a synchronous function to automatically time it
const processData = (data: string) => {
  // Your synchronous logic
  return data.toUpperCase();
};

const timedProcess = log.stats.timer(processData, 'data.process.duration').create();
const result = timedProcess('hello'); // Automatically records timing

// Wrap an async function to automatically time it
const fetchData = async (url: string) => {
  const response = await fetch(url);
  return response.json();
};

// Simple usage
const timedFetch = log.stats.asyncTimer(fetchData, 'api.fetch.duration').create();
const data = await timedFetch('https://api.example.com/data');

// With tags and sample rate
const timedFetchWithOptions = log.stats
  .asyncTimer(fetchData, 'api.fetch.duration')
  .withTags(['env:production', 'service:api'])
  .withSampleRate(0.5)
  .create();

const data2 = await timedFetchWithOptions('https://api.example.com/data');

Histograms and Distributions

MethodReturnsDescription
stats.histogram(stat, value)IStatsBuilderRecords a value in a histogram. Histograms track the statistical distribution of values (DogStatsD/Telegraf only)
stats.distribution(stat, value)IStatsBuilderTracks the statistical distribution of a set of values (DogStatsD v6). Similar to histogram but optimized for distributions

Builder methods: withTags(tags), withSampleRate(rate), withCallback(callback), send()

Gauges

MethodReturnsDescription
stats.gauge(stat, value)IStatsBuilderSets or changes a gauge stat to the specified value
stats.gaugeDelta(stat, delta)IStatsBuilderChanges a gauge stat by a specified amount rather than setting it to a value

Builder methods: withTags(tags), withSampleRate(rate), withCallback(callback), send()

Sets

MethodReturnsDescription
stats.set(stat, value)IStatsBuilderCounts unique occurrences of a stat. Records how many unique elements were tracked
stats.unique(stat, value)IStatsBuilderAlias for set. Counts unique occurrences of a stat

Builder methods: withTags(tags), withSampleRate(rate), withCallback(callback), send()

Service Checks (DogStatsD only)

MethodReturnsDescription
stats.check(name, status)ICheckBuilderSends a service check status. Status values: 0 = OK, 1 = WARNING, 2 = CRITICAL, 3 = UNKNOWN

Builder methods: withOptions(options), withTags(tags), withCallback(callback), send()

Note: withSampleRate() is not supported for service checks and is a no-op.

Events (DogStatsD only)

MethodReturnsDescription
stats.event(title)IEventBuilderSends an event. Use withText() to set the event description

Builder methods: withText(text), withTags(tags), withCallback(callback), send()

Note: withSampleRate() is not supported for events and is a no-op.

Builder Methods

All stats methods return a builder that supports the following configuration methods:

Common Builder Methods

MethodTypeDescription
withTags(tags)StatsTagsAdd tags to the metric. Can be an array of strings (['env:prod', 'service:api']) or an object ({ env: 'prod', service: 'api' })
withSampleRate(rate)numberSet the sample rate for the metric (0.0 to 1.0). Not supported for events and service checks
withCallback(callback)StatsCallbackAdd a callback function to be called after sending the metric. Receives (error?: Error, bytes?: number)
send()voidSend the metric with the configured options

Increment/Decrement Builder Methods

MethodTypeDescription
withValue(value)numberSet the increment/decrement value (defaults to 1 if not specified)

Event Builder Methods

MethodTypeDescription
withText(text)stringSet the event text/description

Check Builder Methods

MethodTypeDescription
withOptions(options)CheckOptionsSet service check options (hostname, timestamp, message, etc.)

Examples

Basic Usage

typescript
import { LogLayer, useLogLayerMixin, ConsoleTransport } from 'loglayer';
import { StatsD } from 'hot-shots';
import { hotshotsMixin } from '@loglayer/mixin-hot-shots';

const statsd = new StatsD({ host: 'localhost', port: 8125 });
useLogLayerMixin(hotshotsMixin(statsd));

const log = new LogLayer({
  transport: new ConsoleTransport({ logger: console })
});

// Simple increment
log.stats.increment('page.views').send();

// Increment by specific value
log.stats.increment('items.sold').withValue(10).send();

// Timing with tags
log.stats.timing('request.duration', 150)
  .withTags(['method:GET', 'status:200'])
  .send();

// Gauge with object tags
log.stats.gauge('active.users', 42)
  .withTags({ env: 'production', region: 'us-east-1' })
  .send();

Advanced Usage

typescript
// Increment with all options
log.stats.increment('api.requests')
  .withValue(1)
  .withTags(['env:production', 'service:api'])
  .withSampleRate(0.1) // Sample 10% of requests
  .withCallback((error, bytes) => {
    if (error) {
      log.error('Failed to send metric').withError(error).send();
    }
  })
  .send();

// Service check (DogStatsD)
import { StatsD } from 'hot-shots';
const statsd = new StatsD({ host: 'localhost', port: 8125 });
const CHECKS = statsd.CHECKS;

log.stats.check('database.health', CHECKS.OK)
  .withOptions({
    hostname: 'db-server-1',
    message: 'Database is healthy'
  })
  .withTags(['service:database', 'env:production'])
  .send();

// Event (DogStatsD)
log.stats.event('Deployment completed')
  .withText('Version 1.2.3 deployed to production')
  .withTags(['env:production', 'version:1.2.3'])
  .send();

Multiple Stats

All methods support arrays of stat names:

typescript
// Increment multiple counters at once
log.stats.increment(['requests.total', 'requests.api']).send();

// Set multiple gauges
log.stats.gauge(['cpu.usage', 'memory.usage'], 75).send();