From 96ec56fccbaf42ccdffefb807f1faf29404b1e42 Mon Sep 17 00:00:00 2001 From: Ben Miner Date: Wed, 14 Oct 2026 17:10:42 -0500 Subject: [PATCH 1/3] feat: drop Express middleware and router spans from Sentry Adds `filters.ignoredSpanOps`, passed to Sentry as `ignoreSpans`, which defaults to `middleware.express` and `router.express`. Those two ops are about 90% of agentic-api's Sentry span volume (45M of 50M prod spans over 7 days) and use up the performance quota. Sentry drops them client-side and re-parents their children. OTLP export is unaffected. Also aligns the biome.json schema with the installed CLI (2.5.15). Co-Authored-By: Claude --- README.md | 1 + biome.json | 2 +- src/__tests__/otel-setup.test.ts | 11 +++++++++++ src/config.ts | 4 ++++ src/provider.ts | 1 + src/types.ts | 7 +++++++ 6 files changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f9fa114..e6d726d 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,7 @@ All options are passed to `init()` as an `ObservabilityConfig` object. | `ignoredRoutes` | `string[]` | `[]` | Routes never sampled by Sentry (e.g. `['/health', '/metrics']`). Trailing slashes and query strings are handled automatically. | | `ignoredBreadcrumbCategories` | `string[]` | `['http', 'fetch', 'xhr']` | Breadcrumb categories to apply URL-pattern filtering to. | | `ignoredBreadcrumbPatterns` | `RegExp[]` | Pyroscope + PostHog patterns | URL patterns for breadcrumbs that should be dropped entirely. | +| `ignoredSpanOps` | `string[]` | `['middleware.express', 'router.express']` | Span ops dropped before sending to Sentry. Children are re-parented; OTLP export is unaffected. Pass `[]` to keep them. | ## API diff --git a/biome.json b/biome.json index 415a5ab..18ddb8a 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.5.6/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.15/schema.json", "files": { "ignoreUnknown": true, "includes": ["**", "!dist", "!node_modules", "!coverage"] diff --git a/src/__tests__/otel-setup.test.ts b/src/__tests__/otel-setup.test.ts index e6adb8d..0645f3c 100644 --- a/src/__tests__/otel-setup.test.ts +++ b/src/__tests__/otel-setup.test.ts @@ -172,3 +172,14 @@ describe('trace provider registration on the Sentry client', () => { } }, 2000) }) + +describe('Sentry span filtering', () => { + it('drops Express middleware and router spans by default', () => { + initWithOtel() + + expect(Sentry.getClient()?.getOptions().ignoreSpans).toEqual([ + { op: 'middleware.express' }, + { op: 'router.express' }, + ]) + }) +}) diff --git a/src/config.ts b/src/config.ts index 12e7712..2fe5734 100644 --- a/src/config.ts +++ b/src/config.ts @@ -81,6 +81,10 @@ export function resolveConfig(config: ObservabilityConfig): ResolvedConfig { /posthog\.com/i, /i\.posthog\.com/i, ], + ignoredSpanOps: config.filters?.ignoredSpanOps ?? [ + 'middleware.express', + 'router.express', + ], }, shouldDropError: config.shouldDropError, diff --git a/src/provider.ts b/src/provider.ts index 050ad37..b860eaf 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -109,6 +109,7 @@ function initializeSentry(config: ResolvedConfig): void { ], enabled: config.sentry.enabled, tracesSampler: buildTracesSampler(config), + ignoreSpans: config.filters.ignoredSpanOps.map((op) => ({ op })), beforeSend(event, hint) { const error = hint?.originalException diff --git a/src/types.ts b/src/types.ts index caef78e..f782482 100644 --- a/src/types.ts +++ b/src/types.ts @@ -89,6 +89,12 @@ export interface FilterConfig { * Defaults to patterns matching Pyroscope and PostHog URLs. */ ignoredBreadcrumbPatterns?: RegExp[] + /** + * Sentry span ops dropped before spans are sent to Sentry. Children of a + * dropped span are re-parented, and OTLP export is unaffected. + * Defaults to `['middleware.express', 'router.express']`. Pass `[]` to keep them. + */ + ignoredSpanOps?: string[] } /** Top-level configuration passed to {@link init}. */ @@ -177,6 +183,7 @@ export interface ResolvedConfig { ignoredRoutes: string[] ignoredBreadcrumbCategories: string[] ignoredBreadcrumbPatterns: RegExp[] + ignoredSpanOps: string[] } shouldDropError?: (error: unknown) => boolean From 3a7fe6ec82b27ace63b201ee1aa9fce8c8441941 Mon Sep 17 00:00:00 2001 From: Ben Miner Date: Wed, 14 Oct 2026 17:10:43 -0500 Subject: [PATCH 2/3] chore: release 2.4.0 Co-Authored-By: Claude --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e89032b..6660840 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scope3/observability-js", - "version": "2.3.0", + "version": "2.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scope3/observability-js", - "version": "2.3.0", + "version": "2.4.0", "license": "MIT", "dependencies": { "@opentelemetry/api": "^1.9.0", diff --git a/package.json b/package.json index bbc841b..b755946 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@scope3/observability-js", - "version": "2.3.0", + "version": "2.4.0", "description": "Unified observability (Sentry, OpenTelemetry, Pyroscope) for Node.js services", "keywords": [ "observability", From 7af90b881f2a0de0b68538e4e561403eefa76acf Mon Sep 17 00:00:00 2001 From: Ben Miner Date: Wed, 14 Oct 2026 17:17:38 -0500 Subject: [PATCH 3/3] fix: make ignoredSpanOps opt-in The shared package shouldn't decide which spans other services lose, so the default is now an empty list and each consumer lists the ops it wants dropped. Co-Authored-By: Claude --- README.md | 2 +- src/__tests__/otel-setup.test.ts | 16 ++++++++++++++-- src/config.ts | 5 +---- src/types.ts | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e6d726d..cf2ab1f 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ All options are passed to `init()` as an `ObservabilityConfig` object. | `ignoredRoutes` | `string[]` | `[]` | Routes never sampled by Sentry (e.g. `['/health', '/metrics']`). Trailing slashes and query strings are handled automatically. | | `ignoredBreadcrumbCategories` | `string[]` | `['http', 'fetch', 'xhr']` | Breadcrumb categories to apply URL-pattern filtering to. | | `ignoredBreadcrumbPatterns` | `RegExp[]` | Pyroscope + PostHog patterns | URL patterns for breadcrumbs that should be dropped entirely. | -| `ignoredSpanOps` | `string[]` | `['middleware.express', 'router.express']` | Span ops dropped before sending to Sentry. Children are re-parented; OTLP export is unaffected. Pass `[]` to keep them. | +| `ignoredSpanOps` | `string[]` | `[]` | Span ops dropped before sending to Sentry (e.g. `['middleware.express', 'router.express']`). Children are re-parented; OTLP export is unaffected. | ## API diff --git a/src/__tests__/otel-setup.test.ts b/src/__tests__/otel-setup.test.ts index 0645f3c..b3428cf 100644 --- a/src/__tests__/otel-setup.test.ts +++ b/src/__tests__/otel-setup.test.ts @@ -174,12 +174,24 @@ describe('trace provider registration on the Sentry client', () => { }) describe('Sentry span filtering', () => { - it('drops Express middleware and router spans by default', () => { - initWithOtel() + it('passes ignoredSpanOps to Sentry as ignoreSpans', () => { + init({ + serviceName: 'otel-setup-test', + environment: 'development', + enableOtel: true, + sentry: { dsn: SENTRY_DSN, enabled: false }, + filters: { ignoredSpanOps: ['middleware.express', 'router.express'] }, + }) expect(Sentry.getClient()?.getOptions().ignoreSpans).toEqual([ { op: 'middleware.express' }, { op: 'router.express' }, ]) }) + + it('ignores no spans by default', () => { + initWithOtel() + + expect(Sentry.getClient()?.getOptions().ignoreSpans).toEqual([]) + }) }) diff --git a/src/config.ts b/src/config.ts index 2fe5734..c87b407 100644 --- a/src/config.ts +++ b/src/config.ts @@ -81,10 +81,7 @@ export function resolveConfig(config: ObservabilityConfig): ResolvedConfig { /posthog\.com/i, /i\.posthog\.com/i, ], - ignoredSpanOps: config.filters?.ignoredSpanOps ?? [ - 'middleware.express', - 'router.express', - ], + ignoredSpanOps: config.filters?.ignoredSpanOps ?? [], }, shouldDropError: config.shouldDropError, diff --git a/src/types.ts b/src/types.ts index f782482..7d4c871 100644 --- a/src/types.ts +++ b/src/types.ts @@ -92,7 +92,7 @@ export interface FilterConfig { /** * Sentry span ops dropped before spans are sent to Sentry. Children of a * dropped span are re-parented, and OTLP export is unaffected. - * Defaults to `['middleware.express', 'router.express']`. Pass `[]` to keep them. + * Example: `['middleware.express', 'router.express']`. Defaults to `[]`. */ ignoredSpanOps?: string[] }