All files / src/types/client OTLPClient.ts

100% Statements 41/41
87.5% Branches 7/8
100% Functions 11/11
100% Lines 41/41

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 100 101 102 103 104 1052x   2x   2x 2x               2x       17x 17x 17x     17x   17x         17x       17x       3x 3x 1x   3x 1x   2x         2x 2x           5x 5x 2x   5x 4x   5x       1x       5x 5x 2x   5x 4x         5x       17x 1x     16x       16x   16x 8x 8x       16x      
import { Resource } from '@opentelemetry/resources';
import { Meter, UpDownCounter, Histogram, ObservableGauge, Attributes, ObservableResult } from '@opentelemetry/api';
import { MeterProvider, MetricReader, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
 
import { MetricClient } from './MetricClient';
import { ForcedTemporalityExporter } from './ForcedTemporalityExporter';
 
type GaugeWithValue = {
  gauge: ObservableGauge<Attributes>;
  value: number;
  tags: Record<string, string>;
};
 
export class OTLPClient extends MetricClient {
  protected meterProvider: MeterProvider;
  protected metricReader: MetricReader;
  private meter: Meter;
  private createdGauges: Record<string, GaugeWithValue> = {};
  private createdCounters: Record<string, UpDownCounter<Attributes>> = {};
  private createdHistograms: Record<string, Histogram<Attributes>> = {};
 
  constructor(hostname: string, portNumber: number, isEnabled = true, mock = false) {
    super(isEnabled);
 
    this.metricReader = new PeriodicExportingMetricReader({
      exporter: new ForcedTemporalityExporter({ url: `http://${hostname}:${portNumber}` }),
      exportIntervalMillis: 10000,
    });
 
    this.meterProvider = new MeterProvider({
      resource: new Resource({ hostname, mock }),
      readers: [this.metricReader],
    });
    this.meter = this.meterProvider.getMeter('app');
  }
 
  gaugeImpl(metricName: string, value: number, attributes: Record<string, string>): void {
    const tags = this.mapAttributesToTags(attributes);
    const isMetricCreated = Object.keys(this.createdGauges).find(
      (createdGaugeNames) => createdGaugeNames === metricName,
    );
    if (isMetricCreated) {
      this.createdGauges[metricName].value = value;
    } else {
      this.createdGauges[metricName] = {
        gauge: this.meter.createObservableGauge(metricName, { unit: '', valueType: 0 }),
        value,
        tags,
      };
      this.createdGauges[metricName].gauge.addCallback((r: ObservableResult<Attributes>): void => {
        r.observe(this.createdGauges[metricName].value, this.createdGauges[metricName].tags);
      });
    }
  }
 
  counterImpl(metricName: string, value: number, attributes: Record<string, string>): void {
    const tags = this.mapAttributesToTags(attributes);
    const isMetricCreated = Object.keys(this.createdCounters).find(
      (createdCounterNames) => createdCounterNames === metricName,
    );
    if (!isMetricCreated) {
      this.createdCounters[metricName] = this.meter.createUpDownCounter(metricName, { unit: '', valueType: 0 });
    }
    this.createdCounters[metricName].add(value, tags);
  }
 
  timeImpl(metricName: string, value: number, buckets: number[], attributes: Record<string, string>): void {
    this.recordHistogram(metricName, value, buckets, attributes);
  }
 
  histogramImpl(metricName: string, value: number, buckets: number[], attributes: Record<string, string>): void {
    const tags = this.mapAttributesToTags(attributes);
    const isMetricCreated = Object.keys(this.createdHistograms).find(
      (createdHistogramNames) => createdHistogramNames === metricName,
    );
    if (!isMetricCreated) {
      this.createdHistograms[metricName] = this.meter.createHistogram(metricName, {
        unit: '',
        advice: { explicitBucketBoundaries: buckets },
      });
    }
    this.createdHistograms[metricName].record(value, tags);
  }
 
  protected mapAttributesToTags(attributes: Record<string, string> | undefined): Record<string, string> {
    if (attributes === undefined) {
      return {};
    }
 
    return this.sanitizeAttributes(attributes);
  }
 
  private sanitizeAttributes(attributes: Record<string, string>): Record<string, string> {
    const sanitizedAttributes: Record<string, string> = {};
 
    for (const attributeName in attributes) {
      if (attributes[attributeName]) {
        sanitizedAttributes[attributeName] = attributes[attributeName].replace(/[#,:]/g, '_');
      }
    }
 
    return sanitizedAttributes;
  }
}