Skip to content

Commit 8bec42e

Browse files
author
Luca Forstner
authored
ref: Deprecate non-callback based continueTrace (#10301)
1 parent 9fcbb84 commit 8bec42e

File tree

13 files changed

+392
-464
lines changed

13 files changed

+392
-464
lines changed

MIGRATION.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,32 @@ be removed. Instead, use the new performance APIs:
197197

198198
You can [read more about the new performance APIs here](./docs/v8-new-performance-apis.md).
199199

200+
## Deprecate variations of `Sentry.continueTrace()`
201+
202+
The version of `Sentry.continueTrace()` which does not take a callback argument will be removed in favor of the version
203+
that does. Additionally, the callback argument will not receive an argument with the next major version.
204+
205+
Use `Sentry.continueTrace()` as follows:
206+
207+
```ts
208+
app.get('/your-route', req => {
209+
Sentry.withIsolationScope(isolationScope => {
210+
Sentry.continueTrace(
211+
{
212+
sentryTrace: req.headers.get('sentry-trace'),
213+
baggage: req.headers.get('baggage'),
214+
},
215+
() => {
216+
// All events recorded in this callback will be associated with the incoming trace. For example:
217+
Sentry.startSpan({ name: '/my-route' }, async () => {
218+
await doExpensiveWork();
219+
});
220+
},
221+
);
222+
});
223+
});
224+
```
225+
200226
## Deprecate `Sentry.lastEventId()` and `hub.lastEventId()`
201227

202228
`Sentry.lastEventId()` sometimes causes race conditions, so we are deprecating it in favour of the `beforeSend`

packages/astro/src/server/middleware.ts

Lines changed: 83 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -94,97 +94,95 @@ async function instrumentRequest(
9494

9595
const { method, headers } = ctx.request;
9696

97-
const traceCtx = continueTrace({
98-
sentryTrace: headers.get('sentry-trace') || undefined,
99-
baggage: headers.get('baggage'),
100-
});
97+
return continueTrace(
98+
{
99+
sentryTrace: headers.get('sentry-trace') || undefined,
100+
baggage: headers.get('baggage'),
101+
},
102+
async () => {
103+
const allHeaders: Record<string, string> = {};
101104

102-
const allHeaders: Record<string, string> = {};
105+
if (options.trackHeaders) {
106+
headers.forEach((value, key) => {
107+
allHeaders[key] = value;
108+
});
109+
}
103110

104-
if (options.trackHeaders) {
105-
headers.forEach((value, key) => {
106-
allHeaders[key] = value;
107-
});
108-
}
111+
if (options.trackClientIp) {
112+
getCurrentScope().setUser({ ip_address: ctx.clientAddress });
113+
}
109114

110-
if (options.trackClientIp) {
111-
getCurrentScope().setUser({ ip_address: ctx.clientAddress });
112-
}
115+
try {
116+
const interpolatedRoute = interpolateRouteFromUrlAndParams(ctx.url.pathname, ctx.params);
117+
const source = interpolatedRoute ? 'route' : 'url';
118+
// storing res in a variable instead of directly returning is necessary to
119+
// invoke the catch block if next() throws
120+
const res = await startSpan(
121+
{
122+
attributes: {
123+
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.astro',
124+
},
125+
name: `${method} ${interpolatedRoute || ctx.url.pathname}`,
126+
op: 'http.server',
127+
status: 'ok',
128+
data: {
129+
method,
130+
url: stripUrlQueryAndFragment(ctx.url.href),
131+
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
132+
...(ctx.url.search && { 'http.query': ctx.url.search }),
133+
...(ctx.url.hash && { 'http.fragment': ctx.url.hash }),
134+
...(options.trackHeaders && { headers: allHeaders }),
135+
},
136+
},
137+
async span => {
138+
const originalResponse = await next();
113139

114-
try {
115-
const interpolatedRoute = interpolateRouteFromUrlAndParams(ctx.url.pathname, ctx.params);
116-
const source = interpolatedRoute ? 'route' : 'url';
117-
// storing res in a variable instead of directly returning is necessary to
118-
// invoke the catch block if next() throws
119-
const res = await startSpan(
120-
{
121-
...traceCtx,
122-
attributes: {
123-
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.astro',
124-
},
125-
name: `${method} ${interpolatedRoute || ctx.url.pathname}`,
126-
op: 'http.server',
127-
status: 'ok',
128-
metadata: {
129-
// eslint-disable-next-line deprecation/deprecation
130-
...traceCtx?.metadata,
131-
},
132-
data: {
133-
method,
134-
url: stripUrlQueryAndFragment(ctx.url.href),
135-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: source,
136-
...(ctx.url.search && { 'http.query': ctx.url.search }),
137-
...(ctx.url.hash && { 'http.fragment': ctx.url.hash }),
138-
...(options.trackHeaders && { headers: allHeaders }),
139-
},
140-
},
141-
async span => {
142-
const originalResponse = await next();
143-
144-
if (span && originalResponse.status) {
145-
setHttpStatus(span, originalResponse.status);
146-
}
147-
148-
const scope = getCurrentScope();
149-
const client = getClient();
150-
const contentType = originalResponse.headers.get('content-type');
151-
152-
const isPageloadRequest = contentType && contentType.startsWith('text/html');
153-
if (!isPageloadRequest || !client) {
154-
return originalResponse;
155-
}
156-
157-
// Type case necessary b/c the body's ReadableStream type doesn't include
158-
// the async iterator that is actually available in Node
159-
// We later on use the async iterator to read the body chunks
160-
// see https://github.com/microsoft/TypeScript/issues/39051
161-
const originalBody = originalResponse.body as NodeJS.ReadableStream | null;
162-
if (!originalBody) {
163-
return originalResponse;
164-
}
165-
166-
const decoder = new TextDecoder();
167-
168-
const newResponseStream = new ReadableStream({
169-
start: async controller => {
170-
for await (const chunk of originalBody) {
171-
const html = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
172-
const modifiedHtml = addMetaTagToHead(html, scope, client, span);
173-
controller.enqueue(new TextEncoder().encode(modifiedHtml));
140+
if (span && originalResponse.status) {
141+
setHttpStatus(span, originalResponse.status);
174142
}
175-
controller.close();
176-
},
177-
});
178143

179-
return new Response(newResponseStream, originalResponse);
180-
},
181-
);
182-
return res;
183-
} catch (e) {
184-
sendErrorToSentry(e);
185-
throw e;
186-
}
187-
// TODO: flush if serverless (first extract function)
144+
const scope = getCurrentScope();
145+
const client = getClient();
146+
const contentType = originalResponse.headers.get('content-type');
147+
148+
const isPageloadRequest = contentType && contentType.startsWith('text/html');
149+
if (!isPageloadRequest || !client) {
150+
return originalResponse;
151+
}
152+
153+
// Type case necessary b/c the body's ReadableStream type doesn't include
154+
// the async iterator that is actually available in Node
155+
// We later on use the async iterator to read the body chunks
156+
// see https://github.com/microsoft/TypeScript/issues/39051
157+
const originalBody = originalResponse.body as NodeJS.ReadableStream | null;
158+
if (!originalBody) {
159+
return originalResponse;
160+
}
161+
162+
const decoder = new TextDecoder();
163+
164+
const newResponseStream = new ReadableStream({
165+
start: async controller => {
166+
for await (const chunk of originalBody) {
167+
const html = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
168+
const modifiedHtml = addMetaTagToHead(html, scope, client, span);
169+
controller.enqueue(new TextEncoder().encode(modifiedHtml));
170+
}
171+
controller.close();
172+
},
173+
});
174+
175+
return new Response(newResponseStream, originalResponse);
176+
},
177+
);
178+
return res;
179+
} catch (e) {
180+
sendErrorToSentry(e);
181+
throw e;
182+
}
183+
// TODO: flush if serverless (first extract function)
184+
},
185+
);
188186
}
189187

190188
/**

packages/astro/test/server/middleware.test.ts

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ describe('sentryMiddleware', () => {
6666
url: 'https://mydomain.io/users/123/details',
6767
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
6868
},
69-
metadata: {},
7069
name: 'GET /users/[id]/details',
7170
op: 'http.server',
7271
status: 'ok',
@@ -104,7 +103,6 @@ describe('sentryMiddleware', () => {
104103
url: 'http://localhost:1234/a%xx',
105104
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
106105
},
107-
metadata: {},
108106
name: 'GET a%xx',
109107
op: 'http.server',
110108
status: 'ok',
@@ -144,43 +142,6 @@ describe('sentryMiddleware', () => {
144142
});
145143
});
146144

147-
it('attaches tracing headers', async () => {
148-
const middleware = handleRequest();
149-
const ctx = {
150-
request: {
151-
method: 'GET',
152-
url: '/users',
153-
headers: new Headers({
154-
'sentry-trace': '12345678901234567890123456789012-1234567890123456-1',
155-
baggage: 'sentry-release=1.0.0',
156-
}),
157-
},
158-
params: {},
159-
url: new URL('https://myDomain.io/users/'),
160-
};
161-
const next = vi.fn(() => nextResult);
162-
163-
// @ts-expect-error, a partial ctx object is fine here
164-
await middleware(ctx, next);
165-
166-
expect(startSpanSpy).toHaveBeenCalledWith(
167-
expect.objectContaining({
168-
data: expect.objectContaining({
169-
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
170-
}),
171-
metadata: {
172-
dynamicSamplingContext: {
173-
release: '1.0.0',
174-
},
175-
},
176-
parentSampled: true,
177-
parentSpanId: '1234567890123456',
178-
traceId: '12345678901234567890123456789012',
179-
}),
180-
expect.any(Function), // the `next` function
181-
);
182-
});
183-
184145
it('attaches client IP and request headers if options are set', async () => {
185146
const middleware = handleRequest({ trackClientIp: true, trackHeaders: true });
186147
const ctx = {

packages/core/src/tracing/trace.ts

Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { Span, SpanTimeInput, StartSpanOptions, TransactionContext } from '@sentry/types';
22

3-
import type { propagationContextFromHeaders } from '@sentry/utils';
43
import { dropUndefinedKeys, logger, tracingContextFromHeaders } from '@sentry/utils';
54

65
import { DEBUG_BUILD } from '../debug-build';
@@ -225,31 +224,58 @@ export function getActiveSpan(): Span | undefined {
225224
return getCurrentScope().getSpan();
226225
}
227226

228-
export function continueTrace({
229-
sentryTrace,
230-
baggage,
231-
}: {
232-
sentryTrace: Parameters<typeof propagationContextFromHeaders>[0];
233-
baggage: Parameters<typeof propagationContextFromHeaders>[1];
234-
}): Partial<TransactionContext>;
235-
export function continueTrace<V>(
236-
{
227+
interface ContinueTrace {
228+
/**
229+
* Continue a trace from `sentry-trace` and `baggage` values.
230+
* These values can be obtained from incoming request headers,
231+
* or in the browser from `<meta name="sentry-trace">` and `<meta name="baggage">` HTML tags.
232+
*
233+
* @deprecated Use the version of this function taking a callback as second parameter instead:
234+
*
235+
* ```
236+
* Sentry.continueTrace(sentryTrace: '...', baggage: '...' }, () => {
237+
* // ...
238+
* })
239+
* ```
240+
*
241+
*/
242+
({
237243
sentryTrace,
238244
baggage,
239245
}: {
240-
sentryTrace: Parameters<typeof propagationContextFromHeaders>[0];
241-
baggage: Parameters<typeof propagationContextFromHeaders>[1];
242-
},
243-
callback: (transactionContext: Partial<TransactionContext>) => V,
244-
): V;
245-
/**
246-
* Continue a trace from `sentry-trace` and `baggage` values.
247-
* These values can be obtained from incoming request headers,
248-
* or in the browser from `<meta name="sentry-trace">` and `<meta name="baggage">` HTML tags.
249-
*
250-
* The callback receives a transactionContext that may be used for `startTransaction` or `startSpan`.
251-
*/
252-
export function continueTrace<V>(
246+
// eslint-disable-next-line deprecation/deprecation
247+
sentryTrace: Parameters<typeof tracingContextFromHeaders>[0];
248+
// eslint-disable-next-line deprecation/deprecation
249+
baggage: Parameters<typeof tracingContextFromHeaders>[1];
250+
}): Partial<TransactionContext>;
251+
252+
/**
253+
* Continue a trace from `sentry-trace` and `baggage` values.
254+
* These values can be obtained from incoming request headers, or in the browser from `<meta name="sentry-trace">`
255+
* and `<meta name="baggage">` HTML tags.
256+
*
257+
* Spans started with `startSpan`, `startSpanManual` and `startInactiveSpan`, within the callback will automatically
258+
* be attached to the incoming trace.
259+
*
260+
* Deprecation notice: In the next major version of the SDK the provided callback will not receive a transaction
261+
* context argument.
262+
*/
263+
<V>(
264+
{
265+
sentryTrace,
266+
baggage,
267+
}: {
268+
// eslint-disable-next-line deprecation/deprecation
269+
sentryTrace: Parameters<typeof tracingContextFromHeaders>[0];
270+
// eslint-disable-next-line deprecation/deprecation
271+
baggage: Parameters<typeof tracingContextFromHeaders>[1];
272+
},
273+
// TODO(v8): Remove parameter from this callback.
274+
callback: (transactionContext: Partial<TransactionContext>) => V,
275+
): V;
276+
}
277+
278+
export const continueTrace: ContinueTrace = <V>(
253279
{
254280
sentryTrace,
255281
baggage,
@@ -260,12 +286,14 @@ export function continueTrace<V>(
260286
baggage: Parameters<typeof tracingContextFromHeaders>[1];
261287
},
262288
callback?: (transactionContext: Partial<TransactionContext>) => V,
263-
): V | Partial<TransactionContext> {
289+
): V | Partial<TransactionContext> => {
264290
// TODO(v8): Change this function so it doesn't do anything besides setting the propagation context on the current scope:
265291
/*
266-
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
267-
getCurrentScope().setPropagationContext(propagationContext);
268-
return;
292+
return withScope((scope) => {
293+
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
294+
scope.setPropagationContext(propagationContext);
295+
return callback();
296+
})
269297
*/
270298

271299
const currentScope = getCurrentScope();
@@ -293,8 +321,10 @@ export function continueTrace<V>(
293321
return transactionContext;
294322
}
295323

296-
return callback(transactionContext);
297-
}
324+
return runWithAsyncContext(() => {
325+
return callback(transactionContext);
326+
});
327+
};
298328

299329
function createChildSpanOrTransaction(
300330
hub: Hub,

0 commit comments

Comments
 (0)