Skip to content

Commit 95df562

Browse files
authored
feat(hono): Filter noisy transactions (favicon etc) (#21365)
Similar to #21145
1 parent 92eb5d2 commit 95df562

7 files changed

Lines changed: 105 additions & 1 deletion

File tree

packages/hono/src/bun/sdk.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { applySdkMetadata, consoleSandbox, getClient } from '@sentry/core';
33
import { init as initBun } from '@sentry/bun';
44
import type { HonoBunOptions } from './middleware';
55
import { buildFilteredIntegrations } from '../shared/buildFilteredIntegrations';
6+
import { LOW_QUALITY_TRANSACTION_PATTERNS } from '../shared/lowQualityTransactionPatterns';
67

78
/**
89
* Initializes Sentry for Hono running in a Bun runtime environment.
@@ -23,9 +24,10 @@ export function init(options: HonoBunOptions): Client | undefined {
2324

2425
applySdkMetadata(options, 'hono', ['hono', 'bun']);
2526

26-
// Remove Hono from the SDK defaults to prevent double instrumentation: @sentry/bun
2727
const filteredOptions: HonoBunOptions = {
2828
...options,
29+
ignoreSpans: [...(options.ignoreSpans || []), ...LOW_QUALITY_TRANSACTION_PATTERNS],
30+
// Remove Hono from the SDK defaults to prevent double instrumentation: @sentry/bun
2931
integrations: buildFilteredIntegrations(options.integrations, false),
3032
};
3133

packages/hono/src/cloudflare/middleware.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { withSentry } from '@sentry/cloudflare';
22
import { applySdkMetadata, type BaseTransportOptions, debug, type Options } from '@sentry/core';
33
import type { Env, Hono, MiddlewareHandler } from 'hono';
44
import { buildFilteredIntegrations } from '../shared/buildFilteredIntegrations';
5+
import { LOW_QUALITY_TRANSACTION_PATTERNS } from '../shared/lowQualityTransactionPatterns';
56
import { requestHandler, responseHandler } from '../shared/middlewareHandlers';
67
import { applyPatches } from '../shared/applyPatches';
78
import type { SentryHonoMiddlewareOptions } from '../shared/types';
@@ -25,6 +26,7 @@ export function sentry<E extends Env>(
2526

2627
return {
2728
...honoOptions,
29+
ignoreSpans: [...(honoOptions.ignoreSpans || []), ...LOW_QUALITY_TRANSACTION_PATTERNS],
2830
// Always filter out the Hono integration from defaults and user integrations.
2931
// The Hono integration is already set up by withSentry, so adding it again would cause capturing too early (in Cloudflare SDK) and non-parametrized URLs.
3032
integrations: buildFilteredIntegrations(honoOptions.integrations, true),

packages/hono/src/node/sdk.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { applySdkMetadata, debug, getClient } from '@sentry/core';
33
import { init as initNode } from '@sentry/node';
44
import type { HonoNodeOptions } from './middleware';
55
import { buildFilteredIntegrations } from '../shared/buildFilteredIntegrations';
6+
import { LOW_QUALITY_TRANSACTION_PATTERNS } from '../shared/lowQualityTransactionPatterns';
67

78
/**
89
* Initializes Sentry for Hono running in a Node runtime environment.
@@ -20,6 +21,7 @@ export function init(options: HonoNodeOptions): Client | undefined {
2021

2122
const filteredOptions: HonoNodeOptions = {
2223
...options,
24+
ignoreSpans: [...(options.ignoreSpans || []), ...LOW_QUALITY_TRANSACTION_PATTERNS],
2325
integrations: buildFilteredIntegrations(options.integrations, false),
2426
};
2527

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Patterns for low-quality transactions that should be filtered out.
3+
* Covers e.g. browser noise (favicon) and Vite dev-server artifacts (`@hono/vite-dev-server`).
4+
*/
5+
export const LOW_QUALITY_TRANSACTION_PATTERNS: (string | RegExp)[] = [
6+
/\/node_modules\//,
7+
/\/favicon\.ico/,
8+
/\/@id\//,
9+
/\/@fs\//,
10+
/\/@vite\//,
11+
];

packages/hono/test/bun/middleware.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Hono } from 'hono';
44
import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
55
import { sentry } from '../../src/bun/middleware';
66
import { init } from '../../src/bun/sdk';
7+
import { LOW_QUALITY_TRANSACTION_PATTERNS } from '../../src/shared/lowQualityTransactionPatterns';
78

89
vi.mock('@sentry/bun', () => ({
910
init: vi.fn(),
@@ -205,4 +206,35 @@ describe('Hono Bun Middleware', () => {
205206
expect(initBunMock).toHaveBeenCalledTimes(1);
206207
});
207208
});
209+
210+
describe('ignoreSpans (low-quality transaction filtering)', () => {
211+
it('adds default low-quality transaction patterns to ignoreSpans', () => {
212+
const app = new Hono();
213+
sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' });
214+
215+
const callArgs = (initBunMock as Mock).mock.calls[0]?.[0];
216+
expect(callArgs.ignoreSpans).toEqual(expect.arrayContaining(LOW_QUALITY_TRANSACTION_PATTERNS));
217+
});
218+
219+
it('preserves user-supplied ignoreSpans and appends defaults', () => {
220+
const app = new Hono();
221+
const userPattern = /^GET \/health$/;
222+
sentry(app, {
223+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
224+
ignoreSpans: [userPattern],
225+
});
226+
227+
const callArgs = (initBunMock as Mock).mock.calls[0]?.[0];
228+
expect(callArgs.ignoreSpans[0]).toBe(userPattern);
229+
expect(callArgs.ignoreSpans).toEqual(expect.arrayContaining(LOW_QUALITY_TRANSACTION_PATTERNS));
230+
});
231+
232+
it('handles undefined ignoreSpans gracefully', () => {
233+
const app = new Hono();
234+
sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' });
235+
236+
const callArgs = (initBunMock as Mock).mock.calls[0]?.[0];
237+
expect(callArgs.ignoreSpans).toHaveLength(LOW_QUALITY_TRANSACTION_PATTERNS.length);
238+
});
239+
});
208240
});

packages/hono/test/cloudflare/middleware.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { SDK_VERSION } from '@sentry/core';
44
import { Hono } from 'hono';
55
import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
66
import { sentry } from '../../src/cloudflare/middleware';
7+
import { LOW_QUALITY_TRANSACTION_PATTERNS } from '../../src/shared/lowQualityTransactionPatterns';
78

89
vi.mock('@sentry/cloudflare', { spy: true });
910
vi.mock('@sentry/core', async () => {
@@ -117,6 +118,33 @@ describe('Hono Cloudflare Middleware', () => {
117118
expect(middleware.constructor.name).toBe('AsyncFunction');
118119
});
119120

121+
describe('ignoreSpans', () => {
122+
it('adds universal low-quality transaction patterns to ignoreSpans', () => {
123+
const app = new Hono();
124+
sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' });
125+
126+
const optionsCallback = withSentryMock.mock.calls[0]?.[0];
127+
const result = optionsCallback();
128+
129+
expect(result.ignoreSpans).toEqual(expect.arrayContaining(LOW_QUALITY_TRANSACTION_PATTERNS));
130+
});
131+
132+
it('preserves user-supplied ignoreSpans and appends defaults', () => {
133+
const app = new Hono();
134+
const userPattern = /^GET \/health$/;
135+
sentry(app, {
136+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
137+
ignoreSpans: [userPattern],
138+
});
139+
140+
const optionsCallback = withSentryMock.mock.calls[0]?.[0];
141+
const result = optionsCallback();
142+
143+
expect(result.ignoreSpans[0]).toBe(userPattern);
144+
expect(result.ignoreSpans).toEqual(expect.arrayContaining(LOW_QUALITY_TRANSACTION_PATTERNS));
145+
});
146+
});
147+
120148
describe('when options is a function (env callback)', () => {
121149
it('calls the options function with the env argument passed by withSentry', () => {
122150
type Bindings = { SENTRY_DSN: string };

packages/hono/test/node/sdk.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as SentryCore from '@sentry/core';
22
import { SDK_VERSION } from '@sentry/core';
33
import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
44
import { init } from '../../src/node/sdk';
5+
import { LOW_QUALITY_TRANSACTION_PATTERNS } from '../../src/shared/lowQualityTransactionPatterns';
56

67
vi.mock('@sentry/node', () => ({
78
init: vi.fn().mockReturnValue({
@@ -172,4 +173,30 @@ describe('Hono Node SDK – init()', () => {
172173
expect(initNodeMock).toHaveBeenCalledTimes(1);
173174
expect(applySdkMetadataMock).toHaveBeenCalledTimes(1);
174175
});
176+
177+
it('adds default low-quality transaction patterns to ignoreSpans', () => {
178+
init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' });
179+
180+
const callArgs = (initNodeMock as Mock).mock.calls[0]?.[0];
181+
expect(callArgs.ignoreSpans).toEqual(expect.arrayContaining(LOW_QUALITY_TRANSACTION_PATTERNS));
182+
});
183+
184+
it('preserves user-supplied ignoreSpans and appends defaults', () => {
185+
const userPattern = /^GET \/health$/;
186+
init({
187+
dsn: 'https://public@dsn.ingest.sentry.io/1337',
188+
ignoreSpans: [userPattern],
189+
});
190+
191+
const callArgs = (initNodeMock as Mock).mock.calls[0]?.[0];
192+
expect(callArgs.ignoreSpans[0]).toBe(userPattern);
193+
expect(callArgs.ignoreSpans).toEqual(expect.arrayContaining(LOW_QUALITY_TRANSACTION_PATTERNS));
194+
});
195+
196+
it('handles undefined ignoreSpans gracefully', () => {
197+
init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' });
198+
199+
const callArgs = (initNodeMock as Mock).mock.calls[0]?.[0];
200+
expect(callArgs.ignoreSpans).toHaveLength(LOW_QUALITY_TRANSACTION_PATTERNS.length);
201+
});
175202
});

0 commit comments

Comments
 (0)