diff --git a/packages/next/telemetry/trace/report/index.ts b/packages/next/telemetry/trace/report/index.ts index 2f8130cab963e71..216b0e43002914d 100644 --- a/packages/next/telemetry/trace/report/index.ts +++ b/packages/next/telemetry/trace/report/index.ts @@ -1,6 +1,7 @@ import { TARGET, SpanId } from '../shared' import reportToConsole from './to-console' import reportToZipkin from './to-zipkin' +import reportToJaeger from './to-jaeger' import reportToTelemetry from './to-telemetry' type Reporter = ( @@ -28,6 +29,8 @@ if (target === TARGET.CONSOLE) { report = reportToConsole } else if (target === TARGET.ZIPKIN) { report = reportToZipkin +} else if (target === TARGET.JAEGER) { + report = reportToJaeger } else { report = reportToTelemetry } diff --git a/packages/next/telemetry/trace/report/to-jaeger.ts b/packages/next/telemetry/trace/report/to-jaeger.ts new file mode 100644 index 000000000000000..7d94ebff3104ad2 --- /dev/null +++ b/packages/next/telemetry/trace/report/to-jaeger.ts @@ -0,0 +1,90 @@ +import retry from 'next/dist/compiled/async-retry' +import { randomBytes } from 'crypto' +import fetch from 'node-fetch' +import * as Log from '../../../build/output/log' + +let traceId = process.env.TRACE_ID +let batch: ReturnType | undefined + +const localEndpoint = { + serviceName: 'nextjs', + ipv4: '127.0.0.1', + port: 9411, +} +// Jaeger supports Zipkin's reporting API +const zipkinUrl = `http://${localEndpoint.ipv4}:${localEndpoint.port}` +const jaegerWebUiUrl = `http://${localEndpoint.ipv4}:16686` +const zipkinAPI = `${zipkinUrl}/api/v2/spans` + +type Event = { + traceId: string + parentId?: string + name: string + id: string + timestamp: number + duration: number + localEndpoint: typeof localEndpoint + tags?: Object +} + +// Batch events as zipkin allows for multiple events to be sent in one go +function batcher(reportEvents: (evts: Event[]) => void) { + const events: Event[] = [] + let timeout: ReturnType | undefined + return (event: Event) => { + events.push(event) + // setTimeout is used instead of setInterval to ensure events sending does not block exiting the program + if (!timeout) { + timeout = setTimeout(() => { + reportEvents(events.slice()) + events.length = 0 + timeout = undefined + }, 1500) + } + } +} + +const reportToLocalHost = ( + name: string, + duration: number, + timestamp: number, + id: string, + parentId?: string, + attrs?: Object +) => { + if (!traceId) { + traceId = process.env.TRACE_ID = randomBytes(8).toString('hex') + Log.info( + `Jaeger trace will be available on ${jaegerWebUiUrl}/trace/${traceId}` + ) + } + + if (!batch) { + batch = batcher((events) => { + // Ensure ECONNRESET error is retried 3 times before erroring out + retry( + () => + // Send events to zipkin + fetch(zipkinAPI, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(events), + }), + { minTimeout: 500, retries: 3, factor: 1 } + ).catch(console.log) + }) + } + + batch({ + traceId, + parentId, + name, + id, + timestamp, + duration, + localEndpoint, + tags: attrs, + }) +} + +export default reportToLocalHost diff --git a/packages/next/telemetry/trace/shared.ts b/packages/next/telemetry/trace/shared.ts index 08fc14a3170eb94..74be1e29198fc76 100644 --- a/packages/next/telemetry/trace/shared.ts +++ b/packages/next/telemetry/trace/shared.ts @@ -3,6 +3,7 @@ export enum TARGET { CONSOLE = 'CONSOLE', ZIPKIN = 'ZIPKIN', + JAEGER = 'JAEGER', TELEMETRY = 'TELEMETRY', }