Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(deno): Add OpenTelemetry support and vercelAI integration #17445

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
sergical merged 13 commits into develop from feat/deno-opentelemetry-vercelai
Sep 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
4269b46
feat(deno): Add OpenTelemetry support and vercelAI integration
sergical Aug 22, 2025
1427b8b
lint
sergical Aug 22, 2025
814a1e3
better tests
sergical Aug 22, 2025
446f901
Merge branch 'develop' into feat/deno-opentelemetry-vercelai
sergical Aug 25, 2025
170d300
fixes for 🤖
sergical Aug 25, 2025
3b38271
Merge branch 'develop' into feat/deno-opentelemetry-vercelai
sergical Aug 27, 2025
a84b0d9
Merge branch 'develop' into feat/deno-opentelemetry-vercelai
sergical Aug 29, 2025
f98559f
Merge branch 'develop' into feat/deno-opentelemetry-vercelai
sergical Sep 4, 2025
f93232a
Merge branch 'develop' into feat/deno-opentelemetry-vercelai
sergical Sep 8, 2025
70f92af
feat(deno): Add OTeL compatability tests, move vercelai to /tracing
sergical Sep 9, 2025
bb5ff2d
Merge branch 'develop' into feat/deno-opentelemetry-vercelai
sergical Sep 9, 2025
0636943
fix(deno) formatting
sergical Sep 9, 2025
304d17d
Merge branch 'develop' into feat/deno-opentelemetry-vercelai
sergical Sep 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/deno/package.json
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"/build"
],
"dependencies": {
"@opentelemetry/api": "^1.9.0",
"@sentry/core": "10.11.0"
},
"scripts": {
Expand All @@ -42,7 +43,7 @@
"lint:es-compatibility": "es-check es2022 ./build/esm/*.js --module",
"install:deno": "node ./scripts/install-deno.mjs",
"test": "run-s install:deno deno-types test:unit",
"test:unit": "deno test --allow-read --allow-run --no-check",
"test:unit": "deno test --allow-read --allow-run --allow-env --no-check",
"test:unit:update": "deno test --allow-read --allow-write --allow-run -- --update",
"yalc:publish": "yalc publish --push --sig"
},
Expand Down
1 change: 1 addition & 0 deletions packages/deno/src/index.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,4 @@ export { normalizePathsIntegration } from './integrations/normalizepaths';
export { contextLinesIntegration } from './integrations/contextlines';
export { denoCronIntegration } from './integrations/deno-cron';
export { breadcrumbsIntegration } from './integrations/breadcrumbs';
export { vercelAIIntegration } from './integrations/tracing/vercelai';
45 changes: 45 additions & 0 deletions packages/deno/src/integrations/tracing/vercelai.ts
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* This is a copy of the Vercel AI integration from the cloudflare SDK.
*/

import type { IntegrationFn } from '@sentry/core';
import { addVercelAiProcessors, defineIntegration } from '@sentry/core';

const INTEGRATION_NAME = 'VercelAI';

const _vercelAIIntegration = (() => {
return {
name: INTEGRATION_NAME,
setup(client) {
addVercelAiProcessors(client);
},
};
}) satisfies IntegrationFn;

/**
* Adds Sentry tracing instrumentation for the [ai](https://www.npmjs.com/package/ai) library.
* This integration is not enabled by default, you need to manually add it.
*
* For more information, see the [`ai` documentation](https://sdk.vercel.ai/docs/ai-sdk-core/telemetry).
*
* You need to enable collecting spans for a specific call by setting
* `experimental_telemetry.isEnabled` to `true` in the first argument of the function call.
*
* ```javascript
* const result = await generateText({
* model: openai('gpt-4-turbo'),
* experimental_telemetry: { isEnabled: true },
* });
* ```
*
* If you want to collect inputs and outputs for a specific call, you must specifically opt-in to each
* function call by setting `experimental_telemetry.recordInputs` and `experimental_telemetry.recordOutputs`
* to `true`.
*
* ```javascript
* const result = await generateText({
* model: openai('gpt-4-turbo'),
* experimental_telemetry: { isEnabled: true, recordInputs: true, recordOutputs: true },
* });
*/
export const vercelAIIntegration = defineIntegration(_vercelAIIntegration);
110 changes: 110 additions & 0 deletions packages/deno/src/opentelemetry/tracer.ts
View file Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import type { Context, Span, SpanOptions, Tracer, TracerProvider } from '@opentelemetry/api';
import { SpanKind, trace } from '@opentelemetry/api';
import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
startInactiveSpan,
startSpanManual,
} from '@sentry/core';

/**
* Set up a mock OTEL tracer to allow inter-op with OpenTelemetry emitted spans.
* This is not perfect but handles easy/common use cases.
*/
export function setupOpenTelemetryTracer(): void {
trace.setGlobalTracerProvider(new SentryDenoTraceProvider());
}

class SentryDenoTraceProvider implements TracerProvider {
private readonly _tracers: Map<string, Tracer> = new Map();

public getTracer(name: string, version?: string, options?: { schemaUrl?: string }): Tracer {
const key = `${name}@${version || ''}:${options?.schemaUrl || ''}`;
if (!this._tracers.has(key)) {
this._tracers.set(key, new SentryDenoTracer());
}

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this._tracers.get(key)!;
}
}

class SentryDenoTracer implements Tracer {
public startSpan(name: string, options?: SpanOptions): Span {
// Map OpenTelemetry SpanKind to Sentry operation
const op = this._mapSpanKindToOp(options?.kind);

return startInactiveSpan({
name,
...options,
attributes: {
...options?.attributes,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
'sentry.deno_tracer': true,
},
});
}

/**
* NOTE: This does not handle `context` being passed in. It will always put spans on the current scope.
*/
public startActiveSpan<F extends (span: Span) => unknown>(name: string, fn: F): ReturnType<F>;
public startActiveSpan<F extends (span: Span) => unknown>(name: string, options: SpanOptions, fn: F): ReturnType<F>;
public startActiveSpan<F extends (span: Span) => unknown>(
name: string,
options: SpanOptions,
context: Context,
fn: F,
): ReturnType<F>;
public startActiveSpan<F extends (span: Span) => unknown>(
name: string,
options: unknown,
context?: unknown,
fn?: F,
): ReturnType<F> {
const opts = (typeof options === 'object' && options !== null ? options : {}) as SpanOptions;

// Map OpenTelemetry SpanKind to Sentry operation
const op = this._mapSpanKindToOp(opts.kind);

const spanOpts = {
name,
...opts,
attributes: {
...opts.attributes,
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual',
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: op,
'sentry.deno_tracer': true,
},
};

const callback = (
typeof options === 'function'
? options
: typeof context === 'function'
? context
: typeof fn === 'function'
? fn
: () => {}
) as F;

// In OTEL the semantic matches `startSpanManual` because spans are not auto-ended
return startSpanManual(spanOpts, callback) as ReturnType<F>;
}

private _mapSpanKindToOp(kind?: SpanKind): string {
switch (kind) {
case SpanKind.CLIENT:
return 'http.client';
case SpanKind.SERVER:
return 'http.server';
case SpanKind.PRODUCER:
return 'message.produce';
case SpanKind.CONSUMER:
return 'message.consume';
default:
return 'otel.span';
}
}
}
11 changes: 10 additions & 1 deletion packages/deno/src/sdk.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { denoContextIntegration } from './integrations/context';
import { contextLinesIntegration } from './integrations/contextlines';
import { globalHandlersIntegration } from './integrations/globalhandlers';
import { normalizePathsIntegration } from './integrations/normalizepaths';
import { setupOpenTelemetryTracer } from './opentelemetry/tracer';
import { makeFetchTransport } from './transports';
import type { DenoOptions } from './types';

Expand Down Expand Up @@ -97,5 +98,13 @@ export function init(options: DenoOptions = {}): Client {
transport: options.transport || makeFetchTransport,
};

return initAndBind(DenoClient, clientOptions);
const client = initAndBind(DenoClient, clientOptions);

// Set up OpenTelemetry compatibility to capture spans from libraries using @opentelemetry/api
// Note: This is separate from Deno's native OTEL support and doesn't capture auto-instrumented spans
if (!options.skipOpenTelemetrySetup) {
setupOpenTelemetryTracer();
}

return client;
}
13 changes: 13 additions & 0 deletions packages/deno/src/types.ts
View file Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ export interface BaseDenoOptions {
/** Sets an optional server name (device name) */
serverName?: string;

/**
* The Deno SDK is not OpenTelemetry native, however, we set up some OpenTelemetry compatibility
* via a custom trace provider.
* This ensures that any spans emitted via `@opentelemetry/api` will be captured by Sentry.
* HOWEVER, big caveat: This does not handle custom context handling, it will always work off the current scope.
* This should be good enough for many, but not all integrations.
*
* If you want to opt-out of setting up the OpenTelemetry compatibility tracer, set this to `true`.
*
* @default false
*/
skipOpenTelemetrySetup?: boolean;

/** Callback that is executed when a fatal global error occurs. */
onFatalError?(this: void, error: Error): void;
}
Expand Down
Loading

AltStyle によって変換されたページ (->オリジナル) /