Skip to content

Commit a04e10b

Browse files
authored
feat(hono): Add base for Sentry Hono middleware (Cloudflare) (#18787)
Exporting a hono middleware for applications on Cloudflare with `@sentry/hono/cloudflare`. This is the base setup for continuing work on this SDK. It adds the export and some integration tests. Internal tracking issue: https://linear.app/getsentry/issue/FE-655/hono-add-cloudflare-support-1
1 parent e45312c commit a04e10b

File tree

27 files changed

+695
-13
lines changed

27 files changed

+695
-13
lines changed

dev-packages/cloudflare-integration-tests/expect.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ function dropUndefinedKeys<T extends Record<string, unknown>>(obj: T): T {
1919
return obj;
2020
}
2121

22-
function getSdk(): SdkInfo {
22+
function getSdk(sdk: 'cloudflare' | 'hono'): SdkInfo {
2323
return {
2424
integrations: expect.any(Array),
25-
name: 'sentry.javascript.cloudflare',
25+
name: `sentry.javascript.${sdk}`,
2626
packages: [
2727
{
28-
name: 'npm:@sentry/cloudflare',
28+
name: `npm:@sentry/${sdk}`,
2929
version: SDK_VERSION,
3030
},
3131
],
@@ -46,24 +46,27 @@ function defaultContexts(eventContexts: Contexts = {}): Contexts {
4646
});
4747
}
4848

49-
export function expectedEvent(event: Event): Event {
49+
export function expectedEvent(event: Event, { sdk }: { sdk: 'cloudflare' | 'hono' }): Event {
5050
return dropUndefinedKeys({
5151
event_id: UUID_MATCHER,
5252
timestamp: expect.any(Number),
5353
environment: 'production',
5454
platform: 'javascript',
55-
sdk: getSdk(),
55+
sdk: getSdk(sdk),
5656
...event,
5757
contexts: defaultContexts(event.contexts),
5858
});
5959
}
6060

61-
export function eventEnvelope(event: Event, includeSampleRand = false): Envelope {
61+
export function eventEnvelope(
62+
event: Event,
63+
{ includeSampleRand = false, sdk = 'cloudflare' }: { includeSampleRand?: boolean; sdk?: 'cloudflare' | 'hono' } = {},
64+
): Envelope {
6265
return [
6366
{
6467
event_id: UUID_MATCHER,
6568
sent_at: ISO_DATE_MATCHER,
66-
sdk: { name: 'sentry.javascript.cloudflare', version: SDK_VERSION },
69+
sdk: { name: `sentry.javascript.${sdk}`, version: SDK_VERSION },
6770
trace: {
6871
environment: event.environment || 'production',
6972
public_key: 'public',
@@ -74,6 +77,6 @@ export function eventEnvelope(event: Event, includeSampleRand = false): Envelope
7477
transaction: expect.any(String),
7578
},
7679
},
77-
[[{ type: 'event' }, expectedEvent(event)]],
80+
[[{ type: 'event' }, expectedEvent(event, { sdk })]],
7881
];
7982
}

dev-packages/cloudflare-integration-tests/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"dependencies": {
1616
"@langchain/langgraph": "^1.0.1",
1717
"@sentry/cloudflare": "10.38.0",
18+
"@sentry/hono": "10.38.0",
1819
"hono": "^4.11.7"
1920
},
2021
"devDependencies": {

dev-packages/cloudflare-integration-tests/suites/hono/basic/index.ts renamed to dev-packages/cloudflare-integration-tests/suites/hono-integration/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ app.get('/json', c => {
1616
});
1717

1818
app.get('/error', () => {
19-
throw new Error('Test error from Hono app');
19+
throw new Error('Test error from Hono app (Sentry Cloudflare SDK)');
2020
});
2121

2222
app.get('/hello/:name', c => {

dev-packages/cloudflare-integration-tests/suites/hono/basic/test.ts renamed to dev-packages/cloudflare-integration-tests/suites/hono-integration/test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect, it } from 'vitest';
2-
import { eventEnvelope } from '../../../expect';
3-
import { createRunner } from '../../../runner';
2+
import { eventEnvelope } from '../../expect';
3+
import { createRunner } from '../../runner';
44

55
it('Hono app captures errors', async ({ signal }) => {
66
const runner = createRunner(__dirname)
@@ -14,7 +14,7 @@ it('Hono app captures errors', async ({ signal }) => {
1414
values: [
1515
{
1616
type: 'Error',
17-
value: 'Test error from Hono app',
17+
value: 'Test error from Hono app (Sentry Cloudflare SDK)',
1818
stacktrace: {
1919
frames: expect.any(Array),
2020
},
@@ -28,7 +28,7 @@ it('Hono app captures errors', async ({ signal }) => {
2828
url: expect.any(String),
2929
},
3030
},
31-
true,
31+
{ includeSampleRand: true },
3232
),
3333
)
3434
// Second envelope: transaction event

dev-packages/cloudflare-integration-tests/suites/hono/basic/wrangler.jsonc renamed to dev-packages/cloudflare-integration-tests/suites/hono-integration/wrangler.jsonc

File renamed without changes.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { sentry } from '@sentry/hono/cloudflare';
2+
import { Hono } from 'hono';
3+
4+
interface Env {
5+
SENTRY_DSN: string;
6+
}
7+
8+
const app = new Hono<{ Bindings: Env }>();
9+
10+
app.use(
11+
'*',
12+
sentry(app, {
13+
dsn: process.env.SENTRY_DSN,
14+
tracesSampleRate: 1.0,
15+
debug: true,
16+
// fixme - check out what removing this integration changes
17+
// integrations: integrations => integrations.filter(integration => integration.name !== 'Hono'),
18+
}),
19+
);
20+
21+
app.get('/', c => {
22+
return c.text('Hello from Hono on Cloudflare!');
23+
});
24+
25+
app.get('/json', c => {
26+
return c.json({ message: 'Hello from Hono', framework: 'hono', platform: 'cloudflare' });
27+
});
28+
29+
app.get('/error', () => {
30+
throw new Error('Test error from Hono app');
31+
});
32+
33+
app.get('/hello/:name', c => {
34+
const name = c.req.param('name');
35+
return c.text(`Hello, ${name}!`);
36+
});
37+
38+
export default app;
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { expect, it } from 'vitest';
2+
import { eventEnvelope, SHORT_UUID_MATCHER, UUID_MATCHER } from '../../expect';
3+
import { createRunner } from '../../runner';
4+
5+
it('Hono app captures errors (Hono SDK)', async ({ signal }) => {
6+
const runner = createRunner(__dirname)
7+
.expect(
8+
eventEnvelope(
9+
{
10+
level: 'error',
11+
transaction: 'GET /error',
12+
exception: {
13+
values: [
14+
{
15+
type: 'Error',
16+
value: 'Test error from Hono app',
17+
stacktrace: {
18+
frames: expect.any(Array),
19+
},
20+
mechanism: { type: 'auto.faas.hono.error_handler', handled: false },
21+
},
22+
],
23+
},
24+
request: {
25+
headers: expect.any(Object),
26+
method: 'GET',
27+
url: expect.any(String),
28+
},
29+
},
30+
{ includeSampleRand: true, sdk: 'hono' },
31+
),
32+
)
33+
.expect(envelope => {
34+
const [, envelopeItems] = envelope;
35+
const [itemHeader, itemPayload] = envelopeItems[0];
36+
37+
expect(itemHeader.type).toBe('transaction');
38+
39+
expect(itemPayload).toMatchObject({
40+
type: 'transaction',
41+
platform: 'javascript',
42+
transaction: 'GET /error',
43+
contexts: {
44+
trace: {
45+
span_id: expect.any(String),
46+
trace_id: expect.any(String),
47+
op: 'http.server',
48+
status: 'internal_error',
49+
origin: 'auto.http.cloudflare',
50+
},
51+
},
52+
request: expect.objectContaining({
53+
method: 'GET',
54+
url: expect.stringContaining('/error'),
55+
}),
56+
});
57+
})
58+
59+
.unordered()
60+
.start(signal);
61+
62+
await runner.makeRequest('get', '/error', { expectError: true });
63+
await runner.completed();
64+
});
65+
66+
it('Hono app captures parametrized names', async ({ signal }) => {
67+
const runner = createRunner(__dirname)
68+
.expect(envelope => {
69+
const [, envelopeItems] = envelope;
70+
const [itemHeader, itemPayload] = envelopeItems[0];
71+
72+
expect(itemHeader.type).toBe('transaction');
73+
74+
expect(itemPayload).toMatchObject({
75+
type: 'transaction',
76+
platform: 'javascript',
77+
transaction: 'GET /hello/:name',
78+
contexts: {
79+
trace: {
80+
span_id: SHORT_UUID_MATCHER,
81+
trace_id: UUID_MATCHER,
82+
op: 'http.server',
83+
status: 'ok',
84+
origin: 'auto.http.cloudflare',
85+
},
86+
},
87+
request: expect.objectContaining({
88+
method: 'GET',
89+
url: expect.stringContaining('/hello/:name'),
90+
}),
91+
});
92+
})
93+
94+
.unordered()
95+
.start(signal);
96+
97+
await runner.makeRequest('get', '/hello/:name', { expectError: false });
98+
await runner.completed();
99+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "hono-sdk-worker",
3+
"compatibility_date": "2025-06-17",
4+
"main": "index.ts",
5+
"compatibility_flags": ["nodejs_compat"]
6+
}
7+

dev-packages/e2e-tests/verdaccio-config/config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ packages:
8686
unpublish: $all
8787
# proxy: npmjs # Don't proxy for E2E tests!
8888

89+
'@sentry/hono':
90+
access: $all
91+
publish: $all
92+
unpublish: $all
93+
# proxy: npmjs # Don't proxy for E2E tests!
94+
8995
'@sentry/nestjs':
9096
access: $all
9197
publish: $all

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
"packages/feedback",
6565
"packages/gatsby",
6666
"packages/google-cloud-serverless",
67+
"packages/hono",
6768
"packages/integration-shims",
6869
"packages/nestjs",
6970
"packages/nextjs",

0 commit comments

Comments
 (0)