Skip to content

Instantly share code, notes, and snippets.

@JonCanning
Created March 24, 2025 13:30
Show Gist options
  • Save JonCanning/286f0c83711d6b58dedca524c5bb8c30 to your computer and use it in GitHub Desktop.
Save JonCanning/286f0c83711d6b58dedca524c5bb8c30 to your computer and use it in GitHub Desktop.
An OpenTelemetry abstraction for typescript
import {
type AttributeValue,
type Context,
context,
type Counter,
type Meter,
propagation,
type Span,
SpanStatusCode,
trace,
} from '@opentelemetry/api'
import { AsyncLocalStorage } from 'async_hooks'
import { W3CTraceContextPropagator } from '@opentelemetry/core'
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc'
import { resourceFromAttributes } from '@opentelemetry/resources'
import {
ConsoleMetricExporter,
MeterProvider,
MetricReader,
PeriodicExportingMetricReader,
} from '@opentelemetry/sdk-metrics'
import {
BasicTracerProvider,
BatchSpanProcessor,
ConsoleSpanExporter,
NodeTracerProvider,
SimpleSpanProcessor,
type SpanProcessor,
} from '@opentelemetry/sdk-trace-node'
import * as os from 'os'
let tracer = new BasicTracerProvider().getTracer('noop')
const initTracing = (serviceName: string, version: string, environment: string, ...spanProcessors: SpanProcessor[]) => {
const resourceAttributes = {
['deployment.environment.name']: environment,
['service.name']: serviceName,
['service.version']: version,
['host.name']: os.hostname(),
}
const resource = resourceFromAttributes(resourceAttributes)
const provider = new NodeTracerProvider({
resource,
spanProcessors,
})
tracer = provider.getTracer(serviceName)
propagation.setGlobalPropagator(new W3CTraceContextPropagator())
}
const grpcSpanProcessor = () => {
const exporter = new OTLPTraceExporter()
return new BatchSpanProcessor(exporter) as SpanProcessor
}
const consoleSpanProcessor = () => {
const exporter = new ConsoleSpanExporter()
return new SimpleSpanProcessor(exporter) as SpanProcessor
}
const asyncLocalStorage = new AsyncLocalStorage<Context>()
const startSpan = (name: string, attributes: Record<string, AttributeValue> = {}) => {
const ctx = asyncLocalStorage.getStore() || context.active()
const span = tracer.startSpan(name, { attributes }, ctx)
asyncLocalStorage.enterWith(trace.setSpan(ctx, span))
return span
}
const runInSpan = <T>(name: string, attributes: Record<string, AttributeValue> = {}, fn: (span: Span) => T) => {
const span = startSpan(name, attributes)
try {
return fn(span)
} finally {
span.end()
}
}
const extract = (carrier: Record<string, string>) =>
asyncLocalStorage.enterWith(propagation.extract(context.active(), carrier))
const inject = (carrier: Record<string, string> = {}) => {
const ctx = asyncLocalStorage.getStore() || context.active()
propagation.inject(ctx, carrier)
return carrier
}
const initCounters = (meter: Meter) => {
const requests = meter.createCounter('requests')
counters = { requests }
}
let counters: Record<string, Counter>
;(() => initCounters(new MeterProvider().getMeter('noop')))()
const initMetrics = (serviceName: string, version: string, environment: string, ...readers: MetricReader[]) => {
const resourceAttributes = {
['deployment.environment.name']: environment,
['service.name']: serviceName,
['service.version']: version,
['host.name']: os.hostname(),
}
const resource = resourceFromAttributes(resourceAttributes)
const meterProvider = new MeterProvider({
resource,
readers,
})
const meter = meterProvider.getMeter(serviceName)
initCounters(meter)
}
const grpcMetricReader = () => {
const exporter = new OTLPMetricExporter()
const reader = new PeriodicExportingMetricReader({ exporter })
return reader as MetricReader
}
const consoleMetricReader = () => {
const exporter = new ConsoleMetricExporter()
const reader = new PeriodicExportingMetricReader({ exporter, exportIntervalMillis: 1000 })
return reader as MetricReader
}
export {
consoleMetricReader,
consoleSpanProcessor,
counters,
extract,
grpcMetricReader,
grpcSpanProcessor,
initMetrics,
initTracing,
inject,
startSpan,
runInSpan,
SpanStatusCode,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment