Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion packages/deno/src/wrap-deno-request-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
captureBodyFromWinterCGRequest,
captureException,
continueTrace,
debug,
getClient,
getHttpSpanDetailsFromUrlObject,
hasSpanStreamingEnabled,
Expand Down Expand Up @@ -48,7 +49,12 @@ export const wrapDenoRequestHandler = <Addr extends Deno.Addr = Deno.Addr>(

const client = getClient();
if (!client) {
throw new Error('could not get Deno client. Did you run Sentry.init?');
// `denoServeIntegration` patches `Deno.serve` from `Client.init()`, which a
// directly-constructed client also runs — that path never calls
// `setCurrentClient`, so the patch can be live with no client bound. Keep
// requests flowing to the user's handler, uninstrumented.
debug.warn('Cannot instrument Deno.serve request. No client defined.');
return handler();
}
isolationScope.setClient(client);

Expand Down
67 changes: 67 additions & 0 deletions packages/deno/test/deno-serve-no-client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// <reference lib="deno.ns" />

/**
* Lives in its own file because it wipes the global carrier; Deno gives each test
* file a fresh module graph, so the wipe stays contained here.
*/

import { getMainCarrier } from '@sentry/core';
import { assertEquals } from 'https://deno.land/std@0.212.0/assert/assert_equals.ts';
import { assertNotEquals } from 'https://deno.land/std@0.212.0/assert/assert_not_equals.ts';
import { init } from '../build/esm/index.js';

function resetGlobals(): void {
getMainCarrier().__SENTRY__ = undefined;
}

// Captured before any init() so the patch assertion below compares against the
// genuinely unpatched function; the patch installs once per module graph.
const unpatchedServe = Deno.serve;

function initPatchedServeWithoutClient(): void {
// Install the Deno.serve patch, then unbind the client. The patch is installed by
// `Client.init()`, which a directly-constructed client also runs without ever
// calling `setCurrentClient` — so a live patch with no bound client is a real state.
resetGlobals();
init({ dsn: 'https://username@domain/123' });
assertNotEquals(Deno.serve, unpatchedServe, 'Deno.serve was not patched; test would pass vacuously');
resetGlobals();
}

Deno.test('Deno.serve keeps serving when no client is bound', async () => {
initPatchedServeWithoutClient();

const abortController = new AbortController();
let onListen: ((_: unknown) => void) | undefined = undefined;
const p = new Promise(resolve => (onListen = resolve));
const server = Deno.serve({ port: 0, signal: abortController.signal, onListen }, () => {
return new Response('Hello World');
});
await p;

const response = await fetch(`http://localhost:${server.addr.port}/test`);
assertEquals(response.status, 200);
assertEquals(await response.text(), 'Hello World');

abortController.abort();
await server.finished;
});

Deno.test('Deno.serve propagates handler errors as 500 when no client is bound', async () => {
initPatchedServeWithoutClient();

const abortController = new AbortController();
let onListen: ((_: unknown) => void) | undefined = undefined;
const p = new Promise(resolve => (onListen = resolve));
const server = Deno.serve({ port: 0, signal: abortController.signal, onListen }, () => {
throw new Error('handler blew up');
});
await p;

const response = await fetch(`http://localhost:${server.addr.port}/boom`);
assertEquals(response.status, 500);
await response.body?.cancel();

abortController.abort();
await server.finished;
});