Skip to content

Commit b3aa87e

Browse files
authored
Add custom telemetry exporter support (#1602)
* WIP langsmith & AI SDK integration * Add changeset * Add exporter support to deployed tasks * Better support for external exporters and group exporters and instrumentations under the telemetry config property * Missing changes
1 parent 14ce599 commit b3aa87e

File tree

12 files changed

+266
-12
lines changed

12 files changed

+266
-12
lines changed

.changeset/rich-trainers-glow.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"trigger.dev": patch
3+
"@trigger.dev/core": patch
4+
---
5+
6+
Add otel exporter support

packages/cli-v3/src/config.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,10 @@ function adaptResolveEnvVarsToSyncEnvVarsExtension(
317317
function getInstrumentedPackageNames(config: ResolvedConfig): Array<string> {
318318
const packageNames = [];
319319

320-
if (config.instrumentations) {
321-
for (const instrumentation of config.instrumentations) {
320+
if (config.instrumentations ?? config.telemetry?.instrumentations) {
321+
for (const instrumentation of config.telemetry?.instrumentations ??
322+
config.instrumentations ??
323+
[]) {
322324
const moduleDefinitions = (
323325
instrumentation as any
324326
).getModuleDefinitions?.() as Array<InstrumentationModuleDefinition>;

packages/cli-v3/src/entryPoints/deploy-run-worker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,8 @@ async function bootstrap() {
148148

149149
const tracingSDK = new TracingSDK({
150150
url: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
151-
instrumentations: config.instrumentations ?? [],
151+
instrumentations: config.telemetry?.instrumentations ?? config.instrumentations ?? [],
152+
exporters: config.telemetry?.exporters ?? [],
152153
diagLogLevel: (env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
153154
forceFlushTimeoutMillis: 30_000,
154155
});

packages/cli-v3/src/entryPoints/dev-run-worker.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ async function bootstrap() {
128128

129129
const tracingSDK = new TracingSDK({
130130
url: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318",
131-
instrumentations: config.instrumentations ?? [],
131+
instrumentations: config.telemetry?.instrumentations ?? config.instrumentations ?? [],
132+
exporters: config.telemetry?.exporters ?? [],
132133
diagLogLevel: (env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none",
133134
forceFlushTimeoutMillis: 30_000,
134135
});

packages/core/src/v3/config.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Instrumentation } from "@opentelemetry/instrumentation";
2+
import type { SpanExporter } from "@opentelemetry/sdk-trace-base";
23
import type { BuildExtension } from "./build/extensions.js";
34
import type { MachinePresetName } from "./schemas/common.js";
45
import type { LogLevel } from "./logger/taskLogger.js";
@@ -53,9 +54,27 @@ export type TriggerConfig = {
5354
* Instrumentations to use for OpenTelemetry. This is useful if you want to add custom instrumentations to your tasks.
5455
*
5556
* @see https://trigger.dev/docs/config/config-file#instrumentations
57+
*
58+
* @deprecated Use the `telemetry.instrumentations` option instead.
5659
*/
5760
instrumentations?: Array<Instrumentation>;
5861

62+
telemetry?: {
63+
/**
64+
* Instrumentations to use for OpenTelemetry. This is useful if you want to add custom instrumentations to your tasks.
65+
*
66+
* @see https://trigger.dev/docs/config/config-file#instrumentations
67+
*/
68+
instrumentations?: Array<Instrumentation>;
69+
70+
/**
71+
* Exporters to use for OpenTelemetry. This is useful if you want to add custom exporters to your tasks.
72+
*
73+
* @see https://trigger.dev/docs/config/config-file#exporters
74+
*/
75+
exporters?: Array<SpanExporter>;
76+
};
77+
5978
/**
6079
* Specify a custom path to your tsconfig file. This is useful if you have a custom tsconfig file that you want to use.
6180
*/

packages/core/src/v3/otel/tracingSDK.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {
2121
BatchSpanProcessor,
2222
NodeTracerProvider,
23+
ReadableSpan,
2324
SimpleSpanProcessor,
2425
SpanExporter,
2526
} from "@opentelemetry/sdk-trace-node";
@@ -85,6 +86,7 @@ export type TracingSDKConfig = {
8586
forceFlushTimeoutMillis?: number;
8687
resource?: IResource;
8788
instrumentations?: Instrumentation[];
89+
exporters?: SpanExporter[];
8890
diagLogLevel?: TracingDiagnosticLogLevel;
8991
};
9092

@@ -111,6 +113,8 @@ export class TracingSDK {
111113
.merge(
112114
new Resource({
113115
[SemanticResourceAttributes.CLOUD_PROVIDER]: "trigger.dev",
116+
[SemanticResourceAttributes.SERVICE_NAME]:
117+
getEnvVar("OTEL_SERVICE_NAME") ?? "trigger.dev",
114118
[SemanticInternalAttributes.TRIGGER]: true,
115119
[SemanticInternalAttributes.CLI_VERSION]: VERSION,
116120
})
@@ -153,6 +157,25 @@ export class TracingSDK {
153157
)
154158
);
155159

160+
const externalTraceId = crypto.randomUUID();
161+
162+
for (const exporter of config.exporters ?? []) {
163+
traceProvider.addSpanProcessor(
164+
getEnvVar("OTEL_BATCH_PROCESSING_ENABLED") === "1"
165+
? new BatchSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId), {
166+
maxExportBatchSize: parseInt(getEnvVar("OTEL_SPAN_MAX_EXPORT_BATCH_SIZE") ?? "64"),
167+
scheduledDelayMillis: parseInt(
168+
getEnvVar("OTEL_SPAN_SCHEDULED_DELAY_MILLIS") ?? "200"
169+
),
170+
exportTimeoutMillis: parseInt(
171+
getEnvVar("OTEL_SPAN_EXPORT_TIMEOUT_MILLIS") ?? "30000"
172+
),
173+
maxQueueSize: parseInt(getEnvVar("OTEL_SPAN_MAX_QUEUE_SIZE") ?? "512"),
174+
})
175+
: new SimpleSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId))
176+
);
177+
}
178+
156179
traceProvider.register();
157180

158181
registerInstrumentations({
@@ -236,3 +259,49 @@ function setLogLevel(level: TracingDiagnosticLogLevel) {
236259

237260
diag.setLogger(new DiagConsoleLogger(), diagLogLevel);
238261
}
262+
263+
class ExternalSpanExporterWrapper {
264+
constructor(
265+
private underlyingExporter: SpanExporter,
266+
private externalTraceId: string
267+
) {}
268+
269+
private transformSpan(span: ReadableSpan): ReadableSpan | undefined {
270+
if (span.attributes[SemanticInternalAttributes.SPAN_PARTIAL]) {
271+
// Skip partial spans
272+
return;
273+
}
274+
275+
const spanContext = span.spanContext();
276+
277+
return {
278+
...span,
279+
spanContext: () => ({ ...spanContext, traceId: this.externalTraceId }),
280+
parentSpanId: span.attributes[SemanticInternalAttributes.SPAN_ATTEMPT]
281+
? undefined
282+
: span.parentSpanId,
283+
};
284+
}
285+
286+
export(spans: any[], resultCallback: (result: any) => void): void {
287+
try {
288+
const modifiedSpans = spans.map(this.transformSpan.bind(this));
289+
this.underlyingExporter.export(
290+
modifiedSpans.filter(Boolean) as ReadableSpan[],
291+
resultCallback
292+
);
293+
} catch (e) {
294+
console.error(e);
295+
}
296+
}
297+
298+
shutdown(): Promise<void> {
299+
return this.underlyingExporter.shutdown();
300+
}
301+
302+
forceFlush?(): Promise<void> {
303+
return this.underlyingExporter.forceFlush
304+
? this.underlyingExporter.forceFlush()
305+
: Promise.resolve();
306+
}
307+
}

packages/core/src/v3/semanticInternalAttributes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,5 @@ export const SemanticInternalAttributes = {
5151
RATE_LIMIT_LIMIT: "response.rateLimit.limit",
5252
RATE_LIMIT_REMAINING: "response.rateLimit.remaining",
5353
RATE_LIMIT_RESET: "response.rateLimit.reset",
54+
SPAN_ATTEMPT: "$span.attempt",
5455
};

packages/core/src/v3/workers/taskExecutor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ export class TaskExecutor {
231231
kind: SpanKind.CONSUMER,
232232
attributes: {
233233
[SemanticInternalAttributes.STYLE_ICON]: "attempt",
234+
[SemanticInternalAttributes.SPAN_ATTEMPT]: true,
234235
},
235236
},
236237
this._tracer.extractContext(traceContext),

0 commit comments

Comments
 (0)