Skip to content

Commit d16a889

Browse files
authored
fix(deno): support Deno.serve instrumentation on Deno 2.8 (#21155)
Deno 2.8 changed the `serve` to be a configurable property instead without a setter, so setting it directly crashes Deno apps. I have changed our serve instrumentation approach to use `Object.defineProperty` to override it, while wrapping everything in `try/catch` to avoid crashing user apps in the future. I also added a couple of optional deno latest variants that would help us catch this in the future. Fixes #21142
1 parent 1e0341d commit d16a889

4 files changed

Lines changed: 56 additions & 15 deletions

File tree

.github/workflows/build.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,7 @@ jobs:
999999
if: matrix.test-application == 'deno' || matrix.test-application == 'deno-streamed'
10001000
uses: denoland/setup-deno@v2.0.4
10011001
with:
1002-
deno-version: v2.1.5
1002+
deno-version: ${{ matrix.deno-version || 'v2.1.5' }}
10031003
- name: Restore caches
10041004
uses: ./.github/actions/restore-cache
10051005
with:
@@ -1118,6 +1118,11 @@ jobs:
11181118
uses: actions/setup-node@v6
11191119
with:
11201120
node-version-file: 'dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}/package.json'
1121+
- name: Set up Deno
1122+
if: matrix.test-application == 'deno' || matrix.test-application == 'deno-streamed'
1123+
uses: denoland/setup-deno@v2.0.4
1124+
with:
1125+
deno-version: ${{ matrix.deno-version || 'v2.1.5' }}
11211126
- name: Restore caches
11221127
uses: ./.github/actions/restore-cache
11231128
with:

dev-packages/e2e-tests/test-applications/deno-streamed/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,13 @@
2121
},
2222
"volta": {
2323
"extends": "../../package.json"
24+
},
25+
"sentryTest": {
26+
"optionalVariants": [
27+
{
28+
"deno-version": "latest",
29+
"label": "deno-streamed (latest)"
30+
}
31+
]
2432
}
2533
}

dev-packages/e2e-tests/test-applications/deno/package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,13 @@
2121
},
2222
"volta": {
2323
"extends": "../../package.json"
24+
},
25+
"sentryTest": {
26+
"optionalVariants": [
27+
{
28+
"deno-version": "latest",
29+
"label": "deno (latest)"
30+
}
31+
]
2432
}
2533
}

packages/deno/src/integrations/deno-serve.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { IntegrationFn } from '@sentry/core';
2-
import { defineIntegration } from '@sentry/core';
2+
import { debug, defineIntegration } from '@sentry/core';
33
import { setAsyncLocalStorageAsyncContextStrategy } from '../async';
44
import type { RequestHandlerWrapperOptions } from '../wrap-deno-request-handler';
55
import { wrapDenoRequestHandler } from '../wrap-deno-request-handler';
@@ -44,24 +44,44 @@ const applyHandlerWrap = <A extends Deno.Addr>(
4444
() => handler(request, info as Deno.ServeHandlerInfo<A>),
4545
)) as Deno.ServeHandler;
4646

47+
const instrumentedDenoServe = (serve: typeof Deno.serve): typeof Deno.serve =>
48+
new Proxy(serve, {
49+
apply(target, thisArg, args: ServeParams) {
50+
if (isSimpleHandler(args)) {
51+
args[0] = applyHandlerWrap(args[0]);
52+
} else if (isServeOptWithFunction(args)) {
53+
args[1] = applyHandlerWrap(args[1], args[0]);
54+
} else if (isServeInitOptions(args)) {
55+
args[0].handler = applyHandlerWrap(args[0].handler, args[0]);
56+
}
57+
// if none of those matched, it'll crash, most likely.
58+
return target.apply(thisArg, args);
59+
},
60+
});
61+
4762
const _denoServeIntegration = (() => {
4863
return {
4964
name: INTEGRATION_NAME,
5065
setupOnce() {
5166
setAsyncLocalStorageAsyncContextStrategy();
52-
Deno.serve = new Proxy(Deno.serve, {
53-
apply(target, thisArg, args: ServeParams) {
54-
if (isSimpleHandler(args)) {
55-
args[0] = applyHandlerWrap(args[0]);
56-
} else if (isServeOptWithFunction(args)) {
57-
args[1] = applyHandlerWrap(args[1], args[0]);
58-
} else if (isServeInitOptions(args)) {
59-
args[0].handler = applyHandlerWrap(args[0].handler, args[0]);
60-
}
61-
// if none of those matched, it'll crash, most likely.
62-
return target.apply(thisArg, args);
63-
},
64-
});
67+
68+
const originalServe = Deno.serve;
69+
const wrappedServe = instrumentedDenoServe(originalServe);
70+
71+
try {
72+
const descriptor = Object.getOwnPropertyDescriptor(Deno, 'serve');
73+
74+
Object.defineProperty(Deno, 'serve', {
75+
configurable: descriptor?.configurable ?? true,
76+
enumerable: descriptor?.enumerable ?? true,
77+
// writable: true avoids other instrumentations on older Deno versions
78+
// from crashing if they used to do assignment
79+
writable: true,
80+
value: wrappedServe,
81+
});
82+
} catch (error) {
83+
debug.warn('Could not instrument Deno.serve.', error);
84+
}
6585
},
6686
};
6787
}) satisfies IntegrationFn;

0 commit comments

Comments
 (0)