From 3e556a85ed85c4bae1843944ba8f37fe707b0946 Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Wed, 27 May 2026 15:44:50 +0200 Subject: [PATCH 1/2] feat(hono): Add warning in Bun for double init --- packages/hono/src/bun/sdk.ts | 10 +++- packages/hono/test/bun/middleware.test.ts | 58 ++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/packages/hono/src/bun/sdk.ts b/packages/hono/src/bun/sdk.ts index d30269058f4d..129a3db65844 100644 --- a/packages/hono/src/bun/sdk.ts +++ b/packages/hono/src/bun/sdk.ts @@ -1,5 +1,5 @@ import type { Client } from '@sentry/core'; -import { applySdkMetadata } from '@sentry/core'; +import { applySdkMetadata, debug, getClient } from '@sentry/core'; import { init as initBun } from '@sentry/bun'; import type { HonoBunOptions } from './middleware'; import { buildFilteredIntegrations } from '../shared/buildFilteredIntegrations'; @@ -12,6 +12,14 @@ import { buildFilteredIntegrations } from '../shared/buildFilteredIntegrations'; * When manually calling `init`, add the `honoIntegration` to the `integrations` array to set up the Hono integration. */ export function init(options: HonoBunOptions): Client | undefined { + const existingClient = getClient(); + if (existingClient) { + debug.warn( + 'Sentry is already initialized. Remove the duplicate `Sentry.init()` call, if one exists. Sentry should only be initialized **once**, through the `sentry()` middleware.', + ); + return existingClient; + } + applySdkMetadata(options, 'hono', ['hono', 'bun']); // Remove Hono from the SDK defaults to prevent double instrumentation: @sentry/bun diff --git a/packages/hono/test/bun/middleware.test.ts b/packages/hono/test/bun/middleware.test.ts index 8e3135e52afb..1979034283a6 100644 --- a/packages/hono/test/bun/middleware.test.ts +++ b/packages/hono/test/bun/middleware.test.ts @@ -3,6 +3,7 @@ import { SDK_VERSION } from '@sentry/core'; import { Hono } from 'hono'; import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest'; import { sentry } from '../../src/bun/middleware'; +import { init } from '../../src/bun/sdk'; vi.mock('@sentry/bun', () => ({ init: vi.fn(), @@ -18,10 +19,12 @@ vi.mock('@sentry/core', async () => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore applySdkMetadata: vi.fn(actual.applySdkMetadata), + getClient: vi.fn(() => undefined), }; }); const applySdkMetadataMock = SentryCore.applySdkMetadata as Mock; +const getClientMock = SentryCore.getClient as Mock; describe('Hono Bun Middleware', () => { beforeEach(() => { @@ -51,7 +54,8 @@ describe('Hono Bun Middleware', () => { expect(applySdkMetadataMock).toHaveBeenCalledWith(options, 'hono', ['hono', 'bun']); }); - it('calls init from @sentry/bun', () => { + it('calls init from @sentry/bun when no client exists yet', () => { + getClientMock.mockReturnValue(undefined); const app = new Hono(); const options = { dsn: 'https://public@dsn.ingest.sentry.io/1337', @@ -156,4 +160,56 @@ describe('Hono Bun Middleware', () => { ); }); }); + + describe('double-init guard', () => { + it('does not call init when Sentry is already initialized', () => { + const fakeClient = { getOptions: () => ({}) }; + getClientMock.mockReturnValue(fakeClient as unknown as SentryCore.Client); + + const app = new Hono(); + sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' }); + + expect(initBunMock).not.toHaveBeenCalled(); + }); + + it('emits a warning when Sentry is already initialized', () => { + const warnSpy = vi.spyOn(SentryCore.debug, 'warn'); + const fakeClient = { getOptions: () => ({}) }; + getClientMock.mockReturnValue(fakeClient as unknown as SentryCore.Client); + + const app = new Hono(); + sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' }); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Sentry is already initialized')); + }); + + it('warns that initialization should only happen through the sentry() middleware', () => { + const warnSpy = vi.spyOn(SentryCore.debug, 'warn'); + const fakeClient = { getOptions: () => ({}) }; + getClientMock.mockReturnValue(fakeClient as unknown as SentryCore.Client); + + const app = new Hono(); + sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' }); + + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Remove the duplicate `Sentry.init()` call')); + }); + + it('returns the existing client when already initialized', () => { + const fakeClient = { getOptions: () => ({}) }; + getClientMock.mockReturnValue(fakeClient as unknown as SentryCore.Client); + + const result = init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); + + expect(result).toBe(fakeClient); + expect(initBunMock).not.toHaveBeenCalled(); + }); + + it('initializes normally when no client exists yet', () => { + getClientMock.mockReturnValue(undefined); + + init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); + + expect(initBunMock).toHaveBeenCalledTimes(1); + }); + }); }); From 1f2a4753ed16054c98395d34236e6551e43c4ceb Mon Sep 17 00:00:00 2001 From: s1gr1d <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 28 May 2026 09:53:22 +0200 Subject: [PATCH 2/2] review comments --- packages/hono/src/bun/sdk.ts | 15 ++++++----- packages/hono/test/bun/middleware.test.ts | 33 +++++++++-------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/packages/hono/src/bun/sdk.ts b/packages/hono/src/bun/sdk.ts index 129a3db65844..892b7535bca6 100644 --- a/packages/hono/src/bun/sdk.ts +++ b/packages/hono/src/bun/sdk.ts @@ -1,5 +1,5 @@ import type { Client } from '@sentry/core'; -import { applySdkMetadata, debug, getClient } from '@sentry/core'; +import { applySdkMetadata, consoleSandbox, getClient } from '@sentry/core'; import { init as initBun } from '@sentry/bun'; import type { HonoBunOptions } from './middleware'; import { buildFilteredIntegrations } from '../shared/buildFilteredIntegrations'; @@ -12,12 +12,13 @@ import { buildFilteredIntegrations } from '../shared/buildFilteredIntegrations'; * When manually calling `init`, add the `honoIntegration` to the `integrations` array to set up the Hono integration. */ export function init(options: HonoBunOptions): Client | undefined { - const existingClient = getClient(); - if (existingClient) { - debug.warn( - 'Sentry is already initialized. Remove the duplicate `Sentry.init()` call, if one exists. Sentry should only be initialized **once**, through the `sentry()` middleware.', - ); - return existingClient; + if (getClient()) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + '[Sentry] Sentry is already initialized. Sentry should only be initialized once, through the `sentry()` middleware. Remove the `Sentry.init()` call, if one exists.', + ); + }); } applySdkMetadata(options, 'hono', ['hono', 'bun']); diff --git a/packages/hono/test/bun/middleware.test.ts b/packages/hono/test/bun/middleware.test.ts index 1979034283a6..0506a211e06e 100644 --- a/packages/hono/test/bun/middleware.test.ts +++ b/packages/hono/test/bun/middleware.test.ts @@ -20,6 +20,8 @@ vi.mock('@sentry/core', async () => { // @ts-ignore applySdkMetadata: vi.fn(actual.applySdkMetadata), getClient: vi.fn(() => undefined), + // Pass-through so console.warn calls inside consoleSandbox are observable in tests + consoleSandbox: vi.fn((cb: () => unknown) => cb()), }; }); @@ -162,49 +164,40 @@ describe('Hono Bun Middleware', () => { }); describe('double-init guard', () => { - it('does not call init when Sentry is already initialized', () => { + it('still calls init even when Sentry is already initialized', () => { const fakeClient = { getOptions: () => ({}) }; getClientMock.mockReturnValue(fakeClient as unknown as SentryCore.Client); const app = new Hono(); sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - expect(initBunMock).not.toHaveBeenCalled(); + expect(initBunMock).toHaveBeenCalledTimes(1); }); - it('emits a warning when Sentry is already initialized', () => { - const warnSpy = vi.spyOn(SentryCore.debug, 'warn'); + it('emits a console.warn directing to remove the duplicate init call when Sentry is already initialized', () => { + const warnSpy = vi.spyOn(console, 'warn'); const fakeClient = { getOptions: () => ({}) }; getClientMock.mockReturnValue(fakeClient as unknown as SentryCore.Client); const app = new Hono(); sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' }); + expect(warnSpy).toHaveBeenCalledTimes(1); expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Sentry is already initialized')); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Remove the `Sentry.init()` call')); }); - it('warns that initialization should only happen through the sentry() middleware', () => { - const warnSpy = vi.spyOn(SentryCore.debug, 'warn'); - const fakeClient = { getOptions: () => ({}) }; - getClientMock.mockReturnValue(fakeClient as unknown as SentryCore.Client); + it('does not emit a console.warn when no client exists yet', () => { + const warnSpy = vi.spyOn(console, 'warn'); + getClientMock.mockReturnValue(undefined); const app = new Hono(); sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Remove the duplicate `Sentry.init()` call')); - }); - - it('returns the existing client when already initialized', () => { - const fakeClient = { getOptions: () => ({}) }; - getClientMock.mockReturnValue(fakeClient as unknown as SentryCore.Client); - - const result = init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' }); - - expect(result).toBe(fakeClient); - expect(initBunMock).not.toHaveBeenCalled(); + expect(warnSpy).not.toHaveBeenCalled(); }); - it('initializes normally when no client exists yet', () => { + it('always calls init regardless of whether a client already exists', () => { getClientMock.mockReturnValue(undefined); init({ dsn: 'https://public@dsn.ingest.sentry.io/1337' });