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/mixin-hot-shots hot-shots
bash
yarn add @loglayer/mixin-hot-shots hot-shots
bash
pnpm add @loglayer/mixin-hot-shots hot-shots

TypeScript Setup

To use this mixin with TypeScript, you must register the types by adding the mixin package to your tsconfig.json includes:

json
{
  "include": [
    "./node_modules/@loglayer/mixin-hot-shots"
  ]
}

This ensures TypeScript recognizes the mixin methods on your LogLayer instances.

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(). Since send() returns void, stats calls should be placed at the end of a method chain:

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();

// Chain LogLayer methods BEFORE stats
log
  .withContext({ userId: '123' })
  .withPrefix('API')
  .stats.increment('user.login').send(); // Chain ends here

// Stats methods are available on child loggers
const childLogger = log.child();
childLogger.stats.timing('operation.duration', 150).send();

// To continue logging after stats, start a new statement
log.stats.increment('login').send();
log.info('User logged in');

Testing with MockLogLayer

The hot-shots mixin is fully compatible with MockLogLayer for unit testing:

typescript
import { MockLogLayer } from 'loglayer';
import { useLogLayerMixin } from 'loglayer';
import { hotshotsMixin, MockStatsAPI } from '@loglayer/mixin-hot-shots';

// Register the mixin with a null client for testing
useLogLayerMixin(hotshotsMixin(null));

const mockLogger = new MockLogLayer();

// All stats methods are available on MockLogLayer
mockLogger.stats.increment('counter').send();
mockLogger.stats.gauge('gauge', 100).send();
mockLogger.stats.timing('timer', 500).send();

// The stats property will be a MockStatsAPI when using null client
// No actual metrics are sent, making it safe for unit tests

For more information on testing with MockLogLayer, see the Unit Testing documentation.

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();