@@ -13,18 +13,23 @@ trace.setGlobalTracerProvider(fakeProvider as any);
1313
1414// Sentry.init() must call trace.disable() to clear the fake provider above
1515import * as Sentry from '@sentry/deno' ;
16+ import { generateText } from 'ai' ;
17+ import { MockLanguageModelV1 } from 'ai/test' ;
18+ import { z } from 'zod' ;
1619
1720Sentry . init ( {
1821 environment : 'qa' ,
1922 dsn : Deno . env . get ( 'E2E_TEST_DSN' ) ,
2023 debug : ! ! Deno . env . get ( 'DEBUG' ) ,
2124 tunnel : 'http://localhost:3031/' ,
2225 tracesSampleRate : 1 ,
26+ sendDefaultPii : true ,
27+ enableLogs : true ,
2328} ) ;
2429
2530const port = 3030 ;
2631
27- Deno . serve ( { port } , ( req : Request ) => {
32+ Deno . serve ( { port } , async ( req : Request ) => {
2833 const url = new URL ( req . url ) ;
2934
3035 if ( url . pathname === '/test-success' ) {
@@ -84,6 +89,219 @@ Deno.serve({ port }, (req: Request) => {
8489 } ) ;
8590 }
8691
92+ // Test breadcrumbs: add a breadcrumb then capture an error
93+ if ( url . pathname === '/test-breadcrumb' ) {
94+ Sentry . addBreadcrumb ( {
95+ message : 'test-breadcrumb' ,
96+ category : 'custom' ,
97+ level : 'info' ,
98+ } ) ;
99+ const exceptionId = Sentry . captureException ( new Error ( 'breadcrumb-test' ) ) ;
100+ return new Response ( JSON . stringify ( { exceptionId } ) , {
101+ headers : { 'Content-Type' : 'application/json' } ,
102+ } ) ;
103+ }
104+
105+ // Test context: set user, tag, extra then capture an error
106+ if ( url . pathname === '/test-context' ) {
107+ Sentry . setUser ( { id : '123' , email : 'test@sentry.io' } ) ;
108+ Sentry . setTag ( 'deno-runtime' , 'true' ) ;
109+ Sentry . setExtra ( 'detail' , { key : 'value' } ) ;
110+ const exceptionId = Sentry . captureException ( new Error ( 'context-test' ) ) ;
111+ return new Response ( JSON . stringify ( { exceptionId } ) , {
112+ headers : { 'Content-Type' : 'application/json' } ,
113+ } ) ;
114+ }
115+
116+ // Test scope isolation: tags inside withScope do not leak
117+ if ( url . pathname === '/test-scope-isolation' ) {
118+ let insideId : string | undefined ;
119+ let outsideId : string | undefined ;
120+
121+ Sentry . withScope ( scope => {
122+ scope . setTag ( 'isolated' , 'yes' ) ;
123+ insideId = Sentry . captureException ( new Error ( 'inside-scope' ) ) ;
124+ } ) ;
125+
126+ outsideId = Sentry . captureException ( new Error ( 'outside-scope' ) ) ;
127+
128+ return new Response ( JSON . stringify ( { insideId, outsideId } ) , {
129+ headers : { 'Content-Type' : 'application/json' } ,
130+ } ) ;
131+ }
132+
133+ // Test outbound fetch instrumentation
134+ if ( url . pathname === '/test-outgoing-fetch' ) {
135+ const response = await Sentry . startSpan ( { name : 'test-outgoing-fetch' } , async ( ) => {
136+ const res = await fetch ( 'http://localhost:3030/test-success' ) ;
137+ return res . json ( ) ;
138+ } ) ;
139+ return new Response ( JSON . stringify ( response ) , {
140+ headers : { 'Content-Type' : 'application/json' } ,
141+ } ) ;
142+ }
143+
144+ // Test AI: Vercel AI SDK generateText with mock model
145+ if ( url . pathname === '/test-ai' ) {
146+ const results = await Sentry . startSpan ( { op : 'function' , name : 'ai-test' } , async ( ) => {
147+ // First call - telemetry enabled by default
148+ const result1 = await generateText ( {
149+ model : new MockLanguageModelV1 ( {
150+ doGenerate : async ( ) => ( {
151+ rawCall : { rawPrompt : null , rawSettings : { } } ,
152+ finishReason : 'stop' ,
153+ usage : { promptTokens : 10 , completionTokens : 20 } ,
154+ text : 'First span here!' ,
155+ } ) ,
156+ } ) ,
157+ prompt : 'Where is the first span?' ,
158+ } ) ;
159+
160+ // Second call - explicitly enabled telemetry
161+ const result2 = await generateText ( {
162+ experimental_telemetry : { isEnabled : true } ,
163+ model : new MockLanguageModelV1 ( {
164+ doGenerate : async ( ) => ( {
165+ rawCall : { rawPrompt : null , rawSettings : { } } ,
166+ finishReason : 'stop' ,
167+ usage : { promptTokens : 10 , completionTokens : 20 } ,
168+ text : 'Second span here!' ,
169+ } ) ,
170+ } ) ,
171+ prompt : 'Where is the second span?' ,
172+ } ) ;
173+
174+ // Third call - with tool calls
175+ const result3 = await generateText ( {
176+ model : new MockLanguageModelV1 ( {
177+ doGenerate : async ( ) => ( {
178+ rawCall : { rawPrompt : null , rawSettings : { } } ,
179+ finishReason : 'tool-calls' ,
180+ usage : { promptTokens : 15 , completionTokens : 25 } ,
181+ text : 'Tool call completed!' ,
182+ toolCalls : [
183+ {
184+ toolCallType : 'function' ,
185+ toolCallId : 'call-1' ,
186+ toolName : 'getWeather' ,
187+ args : '{ "location": "San Francisco" }' ,
188+ } ,
189+ ] ,
190+ } ) ,
191+ } ) ,
192+ tools : {
193+ getWeather : {
194+ parameters : z . object ( { location : z . string ( ) } ) ,
195+ execute : async ( args : { location : string } ) => {
196+ return `Weather in ${ args . location } : Sunny, 72°F` ;
197+ } ,
198+ } ,
199+ } ,
200+ prompt : 'What is the weather in San Francisco?' ,
201+ } ) ;
202+
203+ // Fourth call - explicitly disabled telemetry, should not be captured
204+ const result4 = await generateText ( {
205+ experimental_telemetry : { isEnabled : false } ,
206+ model : new MockLanguageModelV1 ( {
207+ doGenerate : async ( ) => ( {
208+ rawCall : { rawPrompt : null , rawSettings : { } } ,
209+ finishReason : 'stop' ,
210+ usage : { promptTokens : 10 , completionTokens : 20 } ,
211+ text : 'Should not be captured!' ,
212+ } ) ,
213+ } ) ,
214+ prompt : 'Where is the disabled span?' ,
215+ } ) ;
216+
217+ return {
218+ result1 : result1 . text ,
219+ result2 : result2 . text ,
220+ result3 : result3 . text ,
221+ result4 : result4 . text ,
222+ } ;
223+ } ) ;
224+
225+ return new Response ( JSON . stringify ( results ) , {
226+ headers : { 'Content-Type' : 'application/json' } ,
227+ } ) ;
228+ }
229+
230+ // Test AI error: tool call that throws
231+ if ( url . pathname === '/test-ai-error' ) {
232+ try {
233+ await Sentry . startSpan ( { op : 'function' , name : 'ai-error-test' } , async ( ) => {
234+ await generateText ( {
235+ experimental_telemetry : { isEnabled : true } ,
236+ model : new MockLanguageModelV1 ( {
237+ doGenerate : async ( ) => ( {
238+ rawCall : { rawPrompt : null , rawSettings : { } } ,
239+ finishReason : 'tool-calls' ,
240+ usage : { promptTokens : 15 , completionTokens : 25 } ,
241+ text : 'Tool call completed!' ,
242+ toolCalls : [
243+ {
244+ toolCallType : 'function' ,
245+ toolCallId : 'call-1' ,
246+ toolName : 'getWeather' ,
247+ args : '{ "location": "San Francisco" }' ,
248+ } ,
249+ ] ,
250+ } ) ,
251+ } ) ,
252+ tools : {
253+ getWeather : {
254+ parameters : z . object ( { location : z . string ( ) } ) ,
255+ execute : async ( _args : { location : string } ) => {
256+ throw new Error ( 'Tool call failed' ) ;
257+ } ,
258+ } ,
259+ } ,
260+ prompt : 'What is the weather in San Francisco?' ,
261+ } ) ;
262+ } ) ;
263+ } catch ( e ) {
264+ Sentry . captureException ( e ) ;
265+ }
266+
267+ return new Response ( JSON . stringify ( { status : 'error-handled' } ) , {
268+ headers : { 'Content-Type' : 'application/json' } ,
269+ } ) ;
270+ }
271+
272+ // Test metrics: emit counter, distribution, and gauge
273+ if ( url . pathname === '/test-metrics' ) {
274+ Sentry . metrics . count ( 'test.deno.count' , 1 , {
275+ attributes : {
276+ endpoint : '/test-metrics' ,
277+ 'random.attribute' : 'Apples' ,
278+ } ,
279+ } ) ;
280+ Sentry . metrics . distribution ( 'test.deno.distribution' , 100 , {
281+ attributes : {
282+ endpoint : '/test-metrics' ,
283+ 'random.attribute' : 'Bananas' ,
284+ } ,
285+ } ) ;
286+ Sentry . metrics . gauge ( 'test.deno.gauge' , 200 , {
287+ attributes : {
288+ endpoint : '/test-metrics' ,
289+ 'random.attribute' : 'Cherries' ,
290+ } ,
291+ } ) ;
292+ return new Response ( JSON . stringify ( { status : 'ok' } ) , {
293+ headers : { 'Content-Type' : 'application/json' } ,
294+ } ) ;
295+ }
296+
297+ // Test logs: emit a debug log via Sentry.logger
298+ if ( url . pathname === '/test-log' ) {
299+ Sentry . logger . debug ( 'Accessed /test-log route' ) ;
300+ return new Response ( JSON . stringify ( { message : 'Log sent' } ) , {
301+ headers : { 'Content-Type' : 'application/json' } ,
302+ } ) ;
303+ }
304+
87305 return new Response ( 'Not found' , { status : 404 } ) ;
88306} ) ;
89307
0 commit comments