diff --git a/.changeset/rich-trainers-glow.md b/.changeset/rich-trainers-glow.md new file mode 100644 index 0000000000..d9faa804dc --- /dev/null +++ b/.changeset/rich-trainers-glow.md @@ -0,0 +1,6 @@ +--- +"trigger.dev": patch +"@trigger.dev/core": patch +--- + +Add otel exporter support diff --git a/packages/cli-v3/src/config.ts b/packages/cli-v3/src/config.ts index 3283c15eb6..928169abc7 100644 --- a/packages/cli-v3/src/config.ts +++ b/packages/cli-v3/src/config.ts @@ -317,8 +317,10 @@ function adaptResolveEnvVarsToSyncEnvVarsExtension( function getInstrumentedPackageNames(config: ResolvedConfig): Array { const packageNames = []; - if (config.instrumentations) { - for (const instrumentation of config.instrumentations) { + if (config.instrumentations ?? config.telemetry?.instrumentations) { + for (const instrumentation of config.telemetry?.instrumentations ?? + config.instrumentations ?? + []) { const moduleDefinitions = ( instrumentation as any ).getModuleDefinitions?.() as Array; diff --git a/packages/cli-v3/src/entryPoints/deploy-run-worker.ts b/packages/cli-v3/src/entryPoints/deploy-run-worker.ts index a4bea9e393..2c6154a65a 100644 --- a/packages/cli-v3/src/entryPoints/deploy-run-worker.ts +++ b/packages/cli-v3/src/entryPoints/deploy-run-worker.ts @@ -148,7 +148,8 @@ async function bootstrap() { const tracingSDK = new TracingSDK({ url: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318", - instrumentations: config.instrumentations ?? [], + instrumentations: config.telemetry?.instrumentations ?? config.instrumentations ?? [], + exporters: config.telemetry?.exporters ?? [], diagLogLevel: (env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none", forceFlushTimeoutMillis: 30_000, }); diff --git a/packages/cli-v3/src/entryPoints/dev-run-worker.ts b/packages/cli-v3/src/entryPoints/dev-run-worker.ts index 8784b1dfe5..e299dd1ebf 100644 --- a/packages/cli-v3/src/entryPoints/dev-run-worker.ts +++ b/packages/cli-v3/src/entryPoints/dev-run-worker.ts @@ -128,7 +128,8 @@ async function bootstrap() { const tracingSDK = new TracingSDK({ url: env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "http://0.0.0.0:4318", - instrumentations: config.instrumentations ?? [], + instrumentations: config.telemetry?.instrumentations ?? config.instrumentations ?? [], + exporters: config.telemetry?.exporters ?? [], diagLogLevel: (env.OTEL_LOG_LEVEL as TracingDiagnosticLogLevel) ?? "none", forceFlushTimeoutMillis: 30_000, }); diff --git a/packages/core/src/v3/config.ts b/packages/core/src/v3/config.ts index d565d62614..a57eba1266 100644 --- a/packages/core/src/v3/config.ts +++ b/packages/core/src/v3/config.ts @@ -1,4 +1,5 @@ import type { Instrumentation } from "@opentelemetry/instrumentation"; +import type { SpanExporter } from "@opentelemetry/sdk-trace-base"; import type { BuildExtension } from "./build/extensions.js"; import type { MachinePresetName } from "./schemas/common.js"; import type { LogLevel } from "./logger/taskLogger.js"; @@ -53,9 +54,27 @@ export type TriggerConfig = { * Instrumentations to use for OpenTelemetry. This is useful if you want to add custom instrumentations to your tasks. * * @see https://trigger.dev/docs/config/config-file#instrumentations + * + * @deprecated Use the `telemetry.instrumentations` option instead. */ instrumentations?: Array; + telemetry?: { + /** + * Instrumentations to use for OpenTelemetry. This is useful if you want to add custom instrumentations to your tasks. + * + * @see https://trigger.dev/docs/config/config-file#instrumentations + */ + instrumentations?: Array; + + /** + * Exporters to use for OpenTelemetry. This is useful if you want to add custom exporters to your tasks. + * + * @see https://trigger.dev/docs/config/config-file#exporters + */ + exporters?: Array; + }; + /** * Specify a custom path to your tsconfig file. This is useful if you have a custom tsconfig file that you want to use. */ diff --git a/packages/core/src/v3/otel/tracingSDK.ts b/packages/core/src/v3/otel/tracingSDK.ts index 929b0c8011..be7ab34012 100644 --- a/packages/core/src/v3/otel/tracingSDK.ts +++ b/packages/core/src/v3/otel/tracingSDK.ts @@ -20,6 +20,7 @@ import { import { BatchSpanProcessor, NodeTracerProvider, + ReadableSpan, SimpleSpanProcessor, SpanExporter, } from "@opentelemetry/sdk-trace-node"; @@ -85,6 +86,7 @@ export type TracingSDKConfig = { forceFlushTimeoutMillis?: number; resource?: IResource; instrumentations?: Instrumentation[]; + exporters?: SpanExporter[]; diagLogLevel?: TracingDiagnosticLogLevel; }; @@ -111,6 +113,8 @@ export class TracingSDK { .merge( new Resource({ [SemanticResourceAttributes.CLOUD_PROVIDER]: "trigger.dev", + [SemanticResourceAttributes.SERVICE_NAME]: + getEnvVar("OTEL_SERVICE_NAME") ?? "trigger.dev", [SemanticInternalAttributes.TRIGGER]: true, [SemanticInternalAttributes.CLI_VERSION]: VERSION, }) @@ -153,6 +157,25 @@ export class TracingSDK { ) ); + const externalTraceId = crypto.randomUUID(); + + for (const exporter of config.exporters ?? []) { + traceProvider.addSpanProcessor( + getEnvVar("OTEL_BATCH_PROCESSING_ENABLED") === "1" + ? new BatchSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId), { + maxExportBatchSize: parseInt(getEnvVar("OTEL_SPAN_MAX_EXPORT_BATCH_SIZE") ?? "64"), + scheduledDelayMillis: parseInt( + getEnvVar("OTEL_SPAN_SCHEDULED_DELAY_MILLIS") ?? "200" + ), + exportTimeoutMillis: parseInt( + getEnvVar("OTEL_SPAN_EXPORT_TIMEOUT_MILLIS") ?? "30000" + ), + maxQueueSize: parseInt(getEnvVar("OTEL_SPAN_MAX_QUEUE_SIZE") ?? "512"), + }) + : new SimpleSpanProcessor(new ExternalSpanExporterWrapper(exporter, externalTraceId)) + ); + } + traceProvider.register(); registerInstrumentations({ @@ -236,3 +259,49 @@ function setLogLevel(level: TracingDiagnosticLogLevel) { diag.setLogger(new DiagConsoleLogger(), diagLogLevel); } + +class ExternalSpanExporterWrapper { + constructor( + private underlyingExporter: SpanExporter, + private externalTraceId: string + ) {} + + private transformSpan(span: ReadableSpan): ReadableSpan | undefined { + if (span.attributes[SemanticInternalAttributes.SPAN_PARTIAL]) { + // Skip partial spans + return; + } + + const spanContext = span.spanContext(); + + return { + ...span, + spanContext: () => ({ ...spanContext, traceId: this.externalTraceId }), + parentSpanId: span.attributes[SemanticInternalAttributes.SPAN_ATTEMPT] + ? undefined + : span.parentSpanId, + }; + } + + export(spans: any[], resultCallback: (result: any) => void): void { + try { + const modifiedSpans = spans.map(this.transformSpan.bind(this)); + this.underlyingExporter.export( + modifiedSpans.filter(Boolean) as ReadableSpan[], + resultCallback + ); + } catch (e) { + console.error(e); + } + } + + shutdown(): Promise { + return this.underlyingExporter.shutdown(); + } + + forceFlush?(): Promise { + return this.underlyingExporter.forceFlush + ? this.underlyingExporter.forceFlush() + : Promise.resolve(); + } +} diff --git a/packages/core/src/v3/semanticInternalAttributes.ts b/packages/core/src/v3/semanticInternalAttributes.ts index 98b14f1aa3..74b09426e1 100644 --- a/packages/core/src/v3/semanticInternalAttributes.ts +++ b/packages/core/src/v3/semanticInternalAttributes.ts @@ -51,4 +51,5 @@ export const SemanticInternalAttributes = { RATE_LIMIT_LIMIT: "response.rateLimit.limit", RATE_LIMIT_REMAINING: "response.rateLimit.remaining", RATE_LIMIT_RESET: "response.rateLimit.reset", + SPAN_ATTEMPT: "$span.attempt", }; diff --git a/packages/core/src/v3/workers/taskExecutor.ts b/packages/core/src/v3/workers/taskExecutor.ts index 06f965c35c..894145878a 100644 --- a/packages/core/src/v3/workers/taskExecutor.ts +++ b/packages/core/src/v3/workers/taskExecutor.ts @@ -231,6 +231,7 @@ export class TaskExecutor { kind: SpanKind.CONSUMER, attributes: { [SemanticInternalAttributes.STYLE_ICON]: "attempt", + [SemanticInternalAttributes.SPAN_ATTEMPT]: true, }, }, this._tracer.extractContext(traceContext), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20f797707c..60a0ccd0f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1628,6 +1628,9 @@ importers: '@fast-csv/parse': specifier: ^5.0.2 version: 5.0.2 + '@opentelemetry/exporter-trace-otlp-http': + specifier: ^0.57.0 + version: 0.57.0(@opentelemetry/api@1.9.0) '@radix-ui/react-dialog': specifier: ^1.0.3 version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.3.1)(react-dom@18.2.0)(react@18.3.1) @@ -1667,12 +1670,15 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + langsmith: + specifier: ^0.2.15 + version: 0.2.15(openai@4.68.4) lucide-react: specifier: ^0.451.0 version: 0.451.0(react@18.3.1) next: specifier: 14.2.15 - version: 14.2.15(@playwright/test@1.37.0)(react-dom@18.2.0)(react@18.3.1) + version: 14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.37.0)(react-dom@18.2.0)(react@18.3.1) openai: specifier: ^4.68.4 version: 4.68.4(zod@3.23.8) @@ -8210,6 +8216,13 @@ packages: dependencies: '@opentelemetry/api': 1.9.0 + /@opentelemetry/api-logs@0.57.0: + resolution: {integrity: sha512-l1aJ30CXeauVYaI+btiynHpw341LthkMTv3omi1VJDX14werY2Wmv9n1yudMsq9HuY0m8PvXEVX4d8zxEb+WRg==} + engines: {node: '>=14'} + dependencies: + '@opentelemetry/api': 1.9.0 + dev: false + /@opentelemetry/api@1.4.1: resolution: {integrity: sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==} engines: {node: '>=8.0.0'} @@ -8289,6 +8302,16 @@ packages: '@opentelemetry/semantic-conventions': 1.25.1 dev: false + /@opentelemetry/core@1.30.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-Q/3u/K73KUjTCnFUP97ZY+pBjQ1kPEgjOfXj/bJl8zW7GbXdkw6cwuyZk6ZTXkVgCBsYRYUzx4fvYK1jxdb9MA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.28.0 + dev: false + /@opentelemetry/exporter-logs-otlp-http@0.49.1(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-3QoBnIGCmEkujynUP0mK155QtOM0MSf9FNrEw7u9ieCFsoMiyatg2hPp+alEDONJ8N8wGEK+wP2q3icgXBiggw==} engines: {node: '>=14'} @@ -8375,6 +8398,20 @@ packages: '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) dev: false + /@opentelemetry/exporter-trace-otlp-http@0.57.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-BJl35PSkwoMlGEOrzjCG1ih6zqZoAZJIR4xyqSKC2BqPtwuRjID0vWBaEdP9xrxxJTEIEQw+gEY/0pUgicX0ew==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.0(@opentelemetry/api@1.9.0) + dev: false + /@opentelemetry/exporter-trace-otlp-proto@0.49.1(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-n8ON/c9pdMyYAfSFWKkgsPwjYoxnki+6Olzo+klKfW7KqLWoyEkryNkbcMIYnGGNXwdkMIrjoaP0VxXB26Oxcg==} engines: {node: '>=14'} @@ -8654,6 +8691,17 @@ packages: '@opentelemetry/otlp-transformer': 0.52.1(@opentelemetry/api@1.9.0) dev: false + /@opentelemetry/otlp-exporter-base@0.57.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-QQl4Ngm3D6H8SDO0EM642ncTxjRsf/HDq7+IWIA0eaEK/NTsJeQ3iYJiZj3F4jkALnvyeM1kkwd+DHtqxTBx9Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.57.0(@opentelemetry/api@1.9.0) + dev: false + /@opentelemetry/otlp-grpc-exporter-base@0.49.1(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-DNDNUWmOqtKTFJAyOyHHKotVox0NQ/09ETX8fUOeEtyNVHoGekAVtBbvIA3AtK+JflP7LC0PTjlLfruPM3Wy6w==} engines: {node: '>=14'} @@ -8723,6 +8771,22 @@ packages: protobufjs: 7.3.2 dev: false + /@opentelemetry/otlp-transformer@0.57.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-yHX7sdwkdAmSa6Jbi3caSLDWy0PCHS1pKQeKz8AIWSyQqL7IojHKgdk9A+7eRd98Z1n9YTdwWSWLnObvIqhEhQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.57.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.57.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.0(@opentelemetry/api@1.9.0) + protobufjs: 7.3.2 + dev: false + /@opentelemetry/propagator-b3@1.22.0(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-qBItJm9ygg/jCB5rmivyGz1qmKZPsL/sX715JqPMFgq++Idm0x+N9sLQvWFHFt2+ZINnCSojw7FVBgFW6izcXA==} engines: {node: '>=14'} @@ -8827,6 +8891,17 @@ packages: '@opentelemetry/semantic-conventions': 1.25.1 dev: false + /@opentelemetry/resources@1.30.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-5mGMjL0Uld/99t7/pcd7CuVtJbkARckLVuiOX84nO8RtLtIz0/J6EOHM2TGvPZ6F4K+XjUq13gMx14w80SVCQg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + dev: false + /@opentelemetry/sdk-logs@0.49.1(@opentelemetry/api-logs@0.49.1)(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-gCzYWsJE0h+3cuh3/cK+9UwlVFyHvj3PReIOCDOmdeXOp90ZjKRoDOJBc3mvk1LL6wyl1RWIivR8Rg9OToyesw==} engines: {node: '>=14'} @@ -8865,6 +8940,18 @@ packages: '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) dev: false + /@opentelemetry/sdk-logs@0.57.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-6Kbxdu/QE9LWH7+WSLmYo3DjAq+c55TiCLXiXu6b/2m2muy5SyOG2m0MrGqetyRpfYSSbIqHmJoqNVTN3+2a9g==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.57.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + dev: false + /@opentelemetry/sdk-metrics@1.22.0(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-k6iIx6H3TZ+BVMr2z8M16ri2OxWaljg5h8ihGJxi/KQWcjign6FEaEzuigXt5bK9wVEhqAcWLCfarSftaNWkkg==} engines: {node: '>=14'} @@ -8889,6 +8976,17 @@ packages: lodash.merge: 4.6.2 dev: false + /@opentelemetry/sdk-metrics@1.30.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-5kcj6APyRMvv6dEIP5plz2qfJAD4OMipBRT11u/pa1a68rHKI2Ln+iXVkAGKgx8o7CXbD7FdPypTUY88ZQgP4Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + dev: false + /@opentelemetry/sdk-node@0.49.1(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-feBIT85ndiSHXsQ2gfGpXC/sNeX4GCHLksC4A9s/bfpUbbgbCSl0RvzZlmEpCHarNrkZMwFRi4H0xFfgvJEjrg==} engines: {node: '>=14'} @@ -8985,6 +9083,18 @@ packages: '@opentelemetry/semantic-conventions': 1.25.1 dev: false + /@opentelemetry/sdk-trace-base@1.30.0(@opentelemetry/api@1.9.0): + resolution: {integrity: sha512-RKQDaDIkV7PwizmHw+rE/FgfB2a6MBx+AEVVlAHXRG1YYxLiBpPX2KhmoB99R5vA4b72iJrjle68NDWnbrE9Dg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + dev: false + /@opentelemetry/sdk-trace-node@1.22.0(@opentelemetry/api@1.4.1): resolution: {integrity: sha512-gTGquNz7ue8uMeiWPwp3CU321OstQ84r7PCDtOaCicjbJxzvO8RZMlEC4geOipTeiF88kss5n6w+//A0MhP1lQ==} engines: {node: '>=14'} @@ -9067,6 +9177,11 @@ packages: engines: {node: '>=14'} dev: false + /@opentelemetry/semantic-conventions@1.28.0: + resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} + engines: {node: '>=14'} + dev: false + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -16938,6 +17053,10 @@ packages: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true + /@types/uuid@10.0.0: + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + dev: false + /@types/uuid@9.0.0: resolution: {integrity: sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==} dev: true @@ -17214,7 +17333,7 @@ packages: dependencies: '@uploadthing/shared': 7.0.3 file-selector: 0.6.0 - next: 14.2.15(@playwright/test@1.37.0)(react-dom@18.2.0)(react@18.3.1) + next: 14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.37.0)(react-dom@18.2.0)(react@18.3.1) react: 18.3.1 uploadthing: 7.1.0(next@14.2.15)(tailwindcss@3.4.1) dev: false @@ -24007,6 +24126,23 @@ packages: engines: {node: '>=14.0.0'} dev: false + /langsmith@0.2.15(openai@4.68.4): + resolution: {integrity: sha512-homtJU41iitqIZVuuLW7iarCzD4f39KcfP9RTBWav9jifhrsDa1Ez89Ejr+4qi72iuBu8Y5xykchsGVgiEZ93w==} + peerDependencies: + openai: '*' + peerDependenciesMeta: + openai: + optional: true + dependencies: + '@types/uuid': 10.0.0 + commander: 10.0.1 + openai: 4.68.4(zod@3.23.8) + p-queue: 6.6.2 + p-retry: 4.6.2 + semver: 7.6.3 + uuid: 10.0.0 + dev: false + /language-subtag-registry@0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: true @@ -25369,7 +25505,7 @@ packages: - babel-plugin-macros dev: false - /next@14.2.15(@playwright/test@1.37.0)(react-dom@18.2.0)(react@18.3.1): + /next@14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.37.0)(react-dom@18.2.0)(react@18.3.1): resolution: {integrity: sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==} engines: {node: '>=18.17.0'} hasBin: true @@ -25388,6 +25524,7 @@ packages: optional: true dependencies: '@next/env': 14.2.15 + '@opentelemetry/api': 1.9.0 '@playwright/test': 1.37.0 '@swc/helpers': 0.5.5 busboy: 1.6.0 @@ -31242,7 +31379,7 @@ packages: '@uploadthing/mime-types': 0.3.0 '@uploadthing/shared': 7.0.3 effect: 3.7.2 - next: 14.2.15(@playwright/test@1.37.0)(react-dom@18.2.0)(react@18.3.1) + next: 14.2.15(@opentelemetry/api@1.9.0)(@playwright/test@1.37.0)(react-dom@18.2.0)(react@18.3.1) tailwindcss: 3.4.1(ts-node@10.9.1) dev: false @@ -31387,6 +31524,11 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + /uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + dev: false + /uuid@3.4.0: resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. diff --git a/references/nextjs-realtime/package.json b/references/nextjs-realtime/package.json index 9a00ea0b51..342897d66d 100644 --- a/references/nextjs-realtime/package.json +++ b/references/nextjs-realtime/package.json @@ -14,6 +14,7 @@ "@ai-sdk/openai": "^1.0.1", "@fal-ai/serverless-client": "^0.15.0", "@fast-csv/parse": "^5.0.2", + "@opentelemetry/exporter-trace-otlp-http": "^0.57.0", "@radix-ui/react-dialog": "^1.0.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-progress": "^1.1.1", @@ -27,6 +28,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "langsmith": "^0.2.15", "lucide-react": "^0.451.0", "next": "14.2.15", "openai": "^4.68.4", diff --git a/references/nextjs-realtime/src/trigger/ai.ts b/references/nextjs-realtime/src/trigger/ai.ts index 26d4c63a9f..e11524e823 100644 --- a/references/nextjs-realtime/src/trigger/ai.ts +++ b/references/nextjs-realtime/src/trigger/ai.ts @@ -4,6 +4,7 @@ import { streamText, type TextStreamPart } from "ai"; import { setTimeout } from "node:timers/promises"; import { z } from "zod"; import OpenAI from "openai"; +import { AISDKExporter } from "langsmith/vercel"; const openaiSDK = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, @@ -98,16 +99,21 @@ export const openaiStreaming = schemaTask({ run: async ({ model, prompt }) => { logger.info("Running OpenAI model", { model, prompt }); + const telemetrySettings = AISDKExporter.getSettings(); + + logger.info("Telemetry settings", { telemetrySettings }); + const result = streamText({ model: openai(model), prompt, - tools: { - getWeather: weatherTask.tool, - }, - maxSteps: 10, + experimental_telemetry: telemetrySettings, }); const stream = await metadata.stream("openai", result.fullStream); + + for await (const chunk of stream) { + logger.log("Received chunk", { chunk }); + } }, }); diff --git a/references/nextjs-realtime/trigger.config.ts b/references/nextjs-realtime/trigger.config.ts index 96a51a6b94..bed71e2eff 100644 --- a/references/nextjs-realtime/trigger.config.ts +++ b/references/nextjs-realtime/trigger.config.ts @@ -1,9 +1,13 @@ import { defineConfig } from "@trigger.dev/sdk/v3"; import { rscExtension } from "@trigger.dev/rsc"; +import { AISDKExporter } from "langsmith/vercel"; export default defineConfig({ project: "proj_bzhdaqhlymtuhlrcgbqy", dirs: ["./src/trigger"], + telemetry: { + exporters: [new AISDKExporter()], + }, build: { extensions: [rscExtension({ reactDomEnvironment: "worker" })], },