All files index.ts

100% Statements 19/19
100% Branches 1/1
100% Functions 10/10
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99        1x 1x         1x           3x 3x                                                                           8x     7x 7x 2x         3x       3x 4x         1x 1x                 1x 1x         1x 1x         1x  
/**
 * Start a high resolution timer
 */
export function startTimer() {
  let start = process.hrtime();
  return {
    /**
     * Reset the timer
     */
    reset() {
      start = process.hrtime();
    },
    /**
     * Get duration in seconds
     */
    getDuration() {
      const elapsed = process.hrtime(start);
      return (elapsed[0] * 1e9 + elapsed[1]) / 1e9;
    },
  };
}
 
export interface LoggerAdapter {
  captureError(error: Error | null, attributes?: Record<string, any>): void;
  write(chunk: Record<string, any>): boolean;
}
 
export type MetricAttributes = Record<string, string | number | boolean>;
 
/**
 * Common interface for metric tracking backends (Prometheus, Sentry, etc.).
 * Inspired by Node.js diagnostics channels — adapters subscribe to a shared
 * metrics channel and receive all published measurements.
 */
export interface MetricsAdapter {
  /** Increment a monotonic counter */
  increment(name: string, value?: number, attributes?: MetricAttributes): void;
  /** Record the current value of a gauge */
  gauge(name: string, value: number, attributes?: MetricAttributes): void;
  /** Record a sample in a distribution/histogram */
  distribution(
    name: string,
    value: number,
    attributes?: MetricAttributes,
  ): void;
  /** Record a timing in milliseconds */
  timing(name: string, value: number, attributes?: MetricAttributes): void;
}
 
/**
 * A publish/subscribe metrics channel. Adapters register via `subscribe` and
 * receive all subsequent measurements. Unsubscribe by calling the returned
 * dispose function — the same pattern as `diagnostics_channel.subscribe`.
 */
export class Metrics implements MetricsAdapter {
  readonly #adapters = new Set<MetricsAdapter>();
 
  subscribe(adapter: MetricsAdapter): () => void {
    this.#adapters.add(adapter);
    return () => {
      this.#adapters.delete(adapter);
    };
  }
 
  hasSubscribers(): boolean {
    return this.#adapters.size > 0;
  }
 
  increment(name: string, value = 1, attributes?: MetricAttributes): void {
    for (const adapter of this.#adapters) {
      adapter.increment(name, value, attributes);
    }
  }
 
  gauge(name: string, value: number, attributes?: MetricAttributes): void {
    for (const adapter of this.#adapters) {
      adapter.gauge(name, value, attributes);
    }
  }
 
  distribution(
    name: string,
    value: number,
    attributes?: MetricAttributes,
  ): void {
    for (const adapter of this.#adapters) {
      adapter.distribution(name, value, attributes);
    }
  }
 
  timing(name: string, value: number, attributes?: MetricAttributes): void {
    for (const adapter of this.#adapters) {
      adapter.timing(name, value, attributes);
    }
  }
}
 
export const metrics = new Metrics();