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
8 changes: 7 additions & 1 deletion dev-packages/bun-integration-tests/expect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ export function expectedEvent(event: Event, { sdk }: { sdk: 'bun' | 'hono' }): E

export function eventEnvelope(
event: Event,
{ includeSampleRand = false, sdk = 'bun' }: { includeSampleRand?: boolean; sdk?: 'bun' | 'hono' } = {},
{
includeSampleRand = false,
includeTransaction = true,
sdk = 'bun',
}: { includeSampleRand?: boolean; includeTransaction?: boolean; sdk?: 'bun' | 'hono' } = {},
): Envelope {
return [
{
Expand All @@ -79,11 +83,13 @@ export function eventEnvelope(
environment: event.environment || 'production',
public_key: 'public',
trace_id: UUID_MATCHER,

sample_rate: expect.any(String),
sampled: expect.any(String),
// release is auto-detected from GitHub CI env vars, so only expect it if we know it will be there
...(process.env.GITHUB_SHA ? { release: expect.any(String) } : {}),
...(includeSampleRand && { sample_rand: expect.stringMatching(/^[01](\.\d+)?$/) }),
...(includeTransaction && { transaction: expect.any(String) }),
},
},
[[{ type: 'event' }, expectedEvent(event, { sdk })]],
Expand Down
2 changes: 1 addition & 1 deletion dev-packages/bun-integration-tests/suites/basic/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ it('captures an error thrown in Bun.serve fetch handler', async ({ signal }) =>
url: expect.stringContaining('/error'),
}),
},
{ includeSampleRand: true },
{ includeSampleRand: true, includeTransaction: false },
),
)
.ignore('transaction')
Expand Down
31 changes: 31 additions & 0 deletions dev-packages/bun-integration-tests/suites/hono-sdk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { sentry } from '@sentry/hono/bun';
import { Hono } from 'hono';

const app = new Hono();

app.use(
sentry(app, {
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
}),
);

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

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

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

const server = Bun.serve({
port: 0,
fetch: app.fetch,
});

process.send?.(JSON.stringify({ event: 'READY', port: server.port }));
131 changes: 131 additions & 0 deletions dev-packages/bun-integration-tests/suites/hono-sdk/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { expect, it } from 'vitest';
import { eventEnvelope, SHORT_UUID_MATCHER, UUID_MATCHER } from '../../expect';
import { createRunner } from '../../runner';

it('Hono app captures parametrized errors (Hono SDK on Bun)', async ({ signal }) => {
const runner = createRunner(__dirname)
.expect(envelope => {
const [, envelopeItems] = envelope;
const [itemHeader, itemPayload] = envelopeItems[0];

expect(itemHeader.type).toBe('transaction');

expect(itemPayload).toMatchObject({
type: 'transaction',
platform: 'node',
transaction: 'GET /error/:param',
transaction_info: {
source: 'route',
},
contexts: {
trace: {
span_id: expect.any(String),
trace_id: expect.any(String),
op: 'http.server',
status: 'internal_error',
origin: 'auto.http.bun.serve',
},
response: {
status_code: 500,
},
},
request: expect.objectContaining({
method: 'GET',
url: expect.stringContaining('/error/param-123'),
}),
breadcrumbs: [
{
timestamp: expect.any(Number),
category: 'console',
level: 'error',
message: 'Error: Test error from Hono app',
data: expect.objectContaining({
logger: 'console',
arguments: [{ message: 'Test error from Hono app', name: 'Error', stack: expect.any(String) }],
}),
},
],
});
})

.expect(
eventEnvelope(
{
level: 'error',
transaction: 'GET /error/:param',
exception: {
values: [
{
type: 'Error',
value: 'Test error from Hono app',
stacktrace: {
frames: expect.any(Array),
},
mechanism: { type: 'auto.http.hono.context_error', handled: false },
},
],
},
request: {
cookies: {},
headers: expect.any(Object),
method: 'GET',
url: expect.stringContaining('/error/param-123'),
},
breadcrumbs: [
{
timestamp: expect.any(Number),
category: 'console',
level: 'error',
message: 'Error: Test error from Hono app',
data: expect.objectContaining({
logger: 'console',
arguments: [{ message: 'Test error from Hono app', name: 'Error', stack: expect.any(String) }],
}),
},
],
},
{ sdk: 'hono', includeSampleRand: true, includeTransaction: true },
),
)
.unordered()
.start(signal);

await runner.makeRequest('get', '/error/param-123', { expectError: true });
await runner.completed();
});

it('Hono app captures parametrized route names on Bun', async ({ signal }) => {
const runner = createRunner(__dirname)
.expect(envelope => {
const [, envelopeItems] = envelope;
const [itemHeader, itemPayload] = envelopeItems[0];

expect(itemHeader.type).toBe('transaction');

expect(itemPayload).toMatchObject({
type: 'transaction',
platform: 'node',
transaction: 'GET /hello/:name',
transaction_info: {
source: 'route',
},
contexts: {
trace: {
span_id: SHORT_UUID_MATCHER,
trace_id: UUID_MATCHER,
op: 'http.server',
status: 'ok',
origin: 'auto.http.bun.serve',
},
},
request: expect.objectContaining({
method: 'GET',
url: expect.stringContaining('/hello/world'),
}),
});
})
.start(signal);

await runner.makeRequest('get', '/hello/world');
await runner.completed();
});
14 changes: 14 additions & 0 deletions packages/hono/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@
"types": "./build/types/index.node.d.ts",
"default": "./build/cjs/index.node.js"
}
},
"./bun": {
"import": {
"types": "./build/types/index.bun.d.ts",
"default": "./build/esm/index.bun.js"
},
"require": {
"types": "./build/types/index.bun.d.ts",
"default": "./build/cjs/index.bun.js"
}
}
},
"typesVersions": {
Expand All @@ -58,6 +68,9 @@
],
"build/types/index.node.d.ts": [
"build/types-ts3.8/index.node.d.ts"
],
"build/types/index.bun.d.ts": [
"build/types-ts3.8/index.bun.d.ts"
]
}
},
Expand All @@ -66,6 +79,7 @@
},
"dependencies": {
"@opentelemetry/api": "^1.9.1",
"@sentry/bun": "10.49.0",
"@sentry/cloudflare": "10.49.0",
"@sentry/core": "10.49.0",
"@sentry/node": "10.49.0"
Expand Down
2 changes: 1 addition & 1 deletion packages/hono/rollup.npm.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';

const baseConfig = makeBaseNPMConfig({
entrypoints: ['src/index.ts', 'src/index.cloudflare.ts', 'src/index.node.ts'],
entrypoints: ['src/index.ts', 'src/index.cloudflare.ts', 'src/index.node.ts', 'src/index.bun.ts'],
packageSpecificConfig: {
output: {
preserveModulesRoot: 'src',
Expand Down
28 changes: 28 additions & 0 deletions packages/hono/src/bun/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { type BaseTransportOptions, debug, type Options } from '@sentry/core';
import { init } from './sdk';
import type { Hono, MiddlewareHandler } from 'hono';
import { patchAppUse } from '../shared/patchAppUse';
import { requestHandler, responseHandler } from '../shared/middlewareHandlers';

export interface HonoBunOptions extends Options<BaseTransportOptions> {}

/**
* Sentry middleware for Hono running in a Bun runtime environment.
*/
export const sentry = (app: Hono, options: HonoBunOptions | undefined = {}): MiddlewareHandler => {
const isDebug = options.debug;

isDebug && debug.log('Initialized Sentry Hono middleware (Bun)');

init(options);

patchAppUse(app);

return async (context, next) => {
requestHandler(context);

await next(); // Handler runs in between Request above ⤴ and Response below ⤵

responseHandler(context);
};
};
34 changes: 34 additions & 0 deletions packages/hono/src/bun/sdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Client, Integration } from '@sentry/core';
import { applySdkMetadata, getIntegrationsToSetup } from '@sentry/core';
import { init as initBun } from '@sentry/bun';
import type { HonoBunOptions } from './middleware';
import { filterHonoIntegration } from '../shared/filterHonoIntegration';

/**
* Initializes Sentry for Hono running in a Bun runtime environment.
*
* In general, it is recommended to initialize Sentry via the `sentry()` middleware, as it sets up everything by default and calls `init` internally.
*
* When manually calling `init`, add the `honoIntegration` to the `integrations` array to set up the Hono integration.
*/
export function init(options: HonoBunOptions): Client | undefined {
applySdkMetadata(options, 'hono', ['hono', 'bun']);

const { integrations: userIntegrations } = options;

// Remove Hono from the SDK defaults to prevent double instrumentation: @sentry/bun
const filteredOptions: HonoBunOptions = {
...options,
integrations: Array.isArray(userIntegrations)
? (defaults: Integration[]) =>
getIntegrationsToSetup({
defaultIntegrations: defaults.filter(filterHonoIntegration),
integrations: userIntegrations, // user's explicit Hono integration is preserved
})
: typeof userIntegrations === 'function'
? (defaults: Integration[]) => userIntegrations(defaults.filter(filterHonoIntegration))
: (defaults: Integration[]) => defaults.filter(filterHonoIntegration),
};

return initBun(filteredOptions);
}
Comment thread
cursor[bot] marked this conversation as resolved.
5 changes: 5 additions & 0 deletions packages/hono/src/index.bun.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { sentry } from './bun/middleware';

export * from '@sentry/bun';

export { init } from './bun/sdk';
Loading
Loading