Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion dev-packages/cloudflare-integration-tests/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function expectedEvent(event: Event): Event {
});
}

export function eventEnvelope(event: Event): Envelope {
export function eventEnvelope(event: Event, includeSampleRand = false): Envelope {
return [
{
event_id: UUID_MATCHER,
Expand All @@ -69,6 +69,7 @@ export function eventEnvelope(event: Event): Envelope {
public_key: 'public',
trace_id: UUID_MATCHER,
sample_rate: expect.any(String),
...(includeSampleRand && { sample_rand: expect.stringMatching(/^[01](\.\d+)?$/) }),
sampled: expect.any(String),
transaction: expect.any(String),
},
Expand Down
3 changes: 2 additions & 1 deletion dev-packages/cloudflare-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
},
"dependencies": {
"@langchain/langgraph": "^1.0.1",
"@sentry/cloudflare": "10.29.0"
"@sentry/cloudflare": "10.29.0",
"hono": "^4.0.0"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20250922.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as Sentry from '@sentry/cloudflare';
import { Hono } from 'hono';

interface Env {
SENTRY_DSN: string;
}

const app = new Hono<{ Bindings: Env }>();

app.get('/', c => {
return c.text('Hello from Hono on Cloudflare!');
});

app.get('/json', c => {
return c.json({ message: 'Hello from Hono', framework: 'hono', platform: 'cloudflare' });
});

app.get('/error', () => {
throw new Error('Test error from Hono app');
});

app.get('/hello/:name', c => {
const name = c.req.param('name');
return c.text(`Hello, ${name}!`);
});

export default Sentry.withSentry(
(env: Env) => ({
dsn: env.SENTRY_DSN,
tracesSampleRate: 1.0,
}),
app,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { expect, it } from 'vitest';
import { eventEnvelope } from '../../../expect';
import { createRunner } from '../../../runner';

it('Hono app captures errors', async ({ signal }) => {
const runner = createRunner(__dirname)
// First envelope: error event from Hono error handler
.expect(
eventEnvelope(
{
level: 'error',
transaction: 'GET /error',
exception: {
values: [
{
type: 'Error',
value: 'Test error from Hono app',
stacktrace: {
frames: expect.any(Array),
},
mechanism: { type: 'auto.faas.hono.error_handler', handled: false },
},
],
},
request: {
headers: expect.any(Object),
method: 'GET',
url: expect.any(String),
},
},
true,
),
)
// Second envelope: transaction event
.expect(envelope => {
const transactionEvent = envelope[1]?.[0]?.[1];
expect(transactionEvent).toEqual(
expect.objectContaining({
type: 'transaction',
transaction: 'GET /error',
contexts: expect.objectContaining({
trace: expect.objectContaining({
op: 'http.server',
status: 'internal_error',
}),
}),
}),
);
})
.start(signal);
await runner.makeRequest('get', '/error', { expectError: true });
await runner.completed();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "hono-basic-worker",
"compatibility_date": "2025-06-17",
"main": "index.ts",
"compatibility_flags": ["nodejs_compat"]
}

4 changes: 2 additions & 2 deletions packages/cloudflare/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ export function withSentry<
) {
handler.errorHandler = new Proxy(handler.errorHandler, {
apply(target, thisArg, args) {
const [err] = args;
const [err, context] = args;

getHonoIntegration()?.handleHonoException(err);
getHonoIntegration()?.handleHonoException(err, context);

return Reflect.apply(target, thisArg, args);
},
Expand Down
33 changes: 31 additions & 2 deletions packages/cloudflare/src/integrations/hono.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import type { IntegrationFn } from '@sentry/core';
import { captureException, debug, defineIntegration, getClient } from '@sentry/core';
import {
captureException,
debug,
defineIntegration,
getActiveSpan,
getClient,
getIsolationScope,
getRootSpan,
updateSpanName,
} from '@sentry/core';
import { DEBUG_BUILD } from '../debug-build';

const INTEGRATION_NAME = 'Hono';
Expand All @@ -8,6 +17,15 @@ interface HonoError extends Error {
status?: number;
}

interface HonoContext {
req: {
method: string;
url: string;
headers: Record<string, string>;
path?: string;
};
}

export interface Options {
/**
* Callback method deciding whether error should be captured and sent to Sentry
Expand All @@ -28,10 +46,13 @@ function isHonoError(err: unknown): err is HonoError {
return typeof err === 'object' && err !== null && 'status' in (err as Record<string, unknown>);
}

// Vendored from https://github.com/honojs/hono/blob/d3abeb1f801aaa1b334285c73da5f5f022dbcadb/src/helper/route/index.ts#L58-L59
const routePath = (c: HonoContext): string => c.req?.path ?? '';

const _honoIntegration = ((options: Partial<Options> = {}) => {
return {
name: INTEGRATION_NAME,
handleHonoException(err: HonoError): void {
handleHonoException(err: HonoError, context: HonoContext): void {
const shouldHandleError = options.shouldHandleError || defaultShouldHandleError;

if (!isHonoError(err)) {
Expand All @@ -40,6 +61,14 @@ const _honoIntegration = ((options: Partial<Options> = {}) => {
}

if (shouldHandleError(err)) {
const activeSpan = getActiveSpan();
if (activeSpan) {
activeSpan.updateName(`${context.req.method} ${routePath(context)}`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l: "${context.req.method} ${routePath(context)}" is used 3 times. We could use a const at the beginning

updateSpanName(getRootSpan(activeSpan), `${context.req.method} ${routePath(context)}`);
}

getIsolationScope().setTransactionName(`${context.req.method} ${routePath(context)}`);

This comment was marked as outdated.


captureException(err, { mechanism: { handled: false, type: 'auto.faas.hono.error_handler' } });
} else {
DEBUG_BUILD && debug.log('[Hono] Not capturing exception because `shouldHandleError` returned `false`.', err);
Expand Down