Skip to content

Commit 50fae00

Browse files
authored
feat: Add @netlify/otel affordances (#363)
* Allow consumer to specify instrumentations and span processors * Add convenience withActiveSpan function so that consumers don't have to repeat themselves * Remove default instrumentations in favour of allowing users to pass them in * Remove extra packages
1 parent 74855b8 commit 50fae00

File tree

4 files changed

+151
-5
lines changed

4 files changed

+151
-5
lines changed

package-lock.json

Lines changed: 88 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/otel/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"dependencies": {
7373
"@opentelemetry/api": "1.9.0",
7474
"@opentelemetry/core": "1.30.1",
75+
"@opentelemetry/instrumentation": "^0.203.0",
7576
"@opentelemetry/otlp-transformer": "0.57.2",
7677
"@opentelemetry/resources": "1.30.1",
7778
"@opentelemetry/sdk-trace-node": "1.30.1"

packages/otel/src/bootstrap/main.ts

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1+
import { type SpanProcessor } from '@opentelemetry/sdk-trace-node'
2+
import type { Instrumentation } from '@opentelemetry/instrumentation'
13
import { GET_TRACER, SHUTDOWN_TRACERS } from '../constants.js'
24

3-
export const createTracerProvider = async (options: {
5+
export interface TracerProviderOptions {
6+
/**
7+
* The request headers (checked for presence of the `x-nf-enable-tracing` header)
8+
*/
49
headers: Headers
510
serviceName: string
611
serviceVersion: string
712
deploymentEnvironment: string
813
siteUrl: string
914
siteId: string
1015
siteName: string
11-
}) => {
16+
instrumentations?: (Instrumentation | Promise<Instrumentation>)[]
17+
extraSpanProcessors?: (SpanProcessor | Promise<SpanProcessor>)[]
18+
}
19+
20+
export const createTracerProvider = async (options: TracerProviderOptions) => {
1221
if (!options.headers.has('x-nf-enable-tracing')) {
1322
return
1423
}
@@ -17,7 +26,9 @@ export const createTracerProvider = async (options: {
1726
const runtimeVersion = nodeVersion.slice(1)
1827

1928
const { Resource } = await import('@opentelemetry/resources')
20-
const { NodeTracerProvider, BatchSpanProcessor } = await import('@opentelemetry/sdk-trace-node')
29+
const { NodeTracerProvider, SimpleSpanProcessor } = await import('@opentelemetry/sdk-trace-node')
30+
31+
const { registerInstrumentations } = await import('@opentelemetry/instrumentation')
2132

2233
const { NetlifySpanExporter } = await import('./netlify_span_exporter.js')
2334

@@ -34,11 +45,21 @@ export const createTracerProvider = async (options: {
3445

3546
const nodeTracerProvider = new NodeTracerProvider({
3647
resource,
37-
spanProcessors: [new BatchSpanProcessor(new NetlifySpanExporter())],
48+
spanProcessors: [
49+
new SimpleSpanProcessor(new NetlifySpanExporter()),
50+
...(await Promise.all(options.extraSpanProcessors ?? [])),
51+
],
3852
})
3953

4054
nodeTracerProvider.register()
4155

56+
const instrumentations = await Promise.all(options.instrumentations ?? [])
57+
58+
registerInstrumentations({
59+
instrumentations,
60+
tracerProvider: nodeTracerProvider,
61+
})
62+
4263
const { trace } = await import('@opentelemetry/api')
4364
const { SugaredTracer } = await import('@opentelemetry/api/experimental')
4465
const { default: pkg } = await import('../../package.json', { with: { type: 'json' } })

packages/otel/src/main.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { type SugaredTracer } from '@opentelemetry/api/experimental'
1+
import { type SugaredSpanOptions, type SugaredTracer } from '@opentelemetry/api/experimental'
22
import { GET_TRACER, SHUTDOWN_TRACERS } from './constants.js'
3+
import type { Context, Span } from '@opentelemetry/api'
34

45
type GlobalThisExtended = typeof globalThis & {
56
[GET_TRACER]?: (name?: string, version?: string) => SugaredTracer | undefined
@@ -13,3 +14,38 @@ export const getTracer = (name?: string, version?: string): SugaredTracer | unde
1314
export const shutdownTracers = async (): Promise<void> => {
1415
return (globalThis as GlobalThisExtended)[SHUTDOWN_TRACERS]?.()
1516
}
17+
18+
export function withActiveSpan<F extends (span?: Span) => ReturnType<F>>(
19+
tracer: SugaredTracer | undefined,
20+
name: string,
21+
fn: F,
22+
): ReturnType<F>
23+
export function withActiveSpan<F extends (span?: Span) => ReturnType<F>>(
24+
tracer: SugaredTracer | undefined,
25+
name: string,
26+
options: SugaredSpanOptions,
27+
fn: F,
28+
): ReturnType<F>
29+
export function withActiveSpan<F extends (span?: Span) => ReturnType<F>>(
30+
tracer: SugaredTracer | undefined,
31+
name: string,
32+
options: SugaredSpanOptions,
33+
context: Context,
34+
fn: F,
35+
): ReturnType<F>
36+
export function withActiveSpan<F extends (span?: Span) => ReturnType<F>>(
37+
tracer: SugaredTracer | undefined,
38+
name: string,
39+
optionsOrFn: SugaredSpanOptions | F,
40+
contextOrFn?: Context | F,
41+
fn?: F,
42+
): ReturnType<F> {
43+
const func = typeof contextOrFn === 'function' ? contextOrFn : typeof optionsOrFn === 'function' ? optionsOrFn : fn
44+
if (!func) {
45+
throw new Error('function to execute with active span is missing')
46+
}
47+
if (!tracer) {
48+
return func()
49+
}
50+
return tracer.withActiveSpan(name, optionsOrFn as SugaredSpanOptions, contextOrFn as Context, func)
51+
}

0 commit comments

Comments
 (0)