Skip to content

Commit 2034a18

Browse files
committed
review comments
1 parent f887ff0 commit 2034a18

5 files changed

Lines changed: 211 additions & 138 deletions

File tree

packages/hono/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ NODE_OPTIONS="--import ./instrument.mjs"
138138

139139
### 4. Add the Sentry middleware to your Hono app
140140

141-
Add the `sentry` middleware to your Hono app. Since Sentry was already initialized in the instrument file, no options are needed:
141+
Add the `sentry` middleware to your Hono app. Since Sentry was already initialized in the instrument file, no options are passed here:
142142

143143
```ts
144144
import { Hono } from 'hono';

packages/hono/src/node/middleware.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type BaseTransportOptions, debug, type Options } from '@sentry/core';
1+
import { type BaseTransportOptions, debug, type Options, getClient } from '@sentry/core';
22
import { init } from './sdk';
33
import type { Hono, MiddlewareHandler } from 'hono';
44
import { requestHandler, responseHandler } from '../shared/middlewareHandlers';
@@ -7,16 +7,22 @@ import { applyPatches } from '../shared/applyPatches';
77
export interface HonoNodeOptions extends Options<BaseTransportOptions> {}
88

99
/**
10-
* Sentry middleware for Hono running in a Node runtime environment.
10+
* Sentry middleware for Hono applications running in a Node.js environment.
1111
*
12-
* @param app The root Hono application instance to which the middleware will be applied.
13-
* @param options Optional Sentry initialization options, which **should usually be omitted** when Sentry is initialized externally (e.g. in an `instrument.ts` file loaded via `--import`).
14-
* If provided, the middleware will initialize Sentry internally using these options. If omitted, the middleware assumes Sentry has already been initialized externally.
12+
* This middleware enhances your Hono application by automatically instrumenting incoming requests and outgoing responses.
13+
* It also applies the necessary patches to ensure Sentry captures execution context correctly in Node.js.
14+
*
15+
* **Note:** You must initialize Sentry separately before using this middleware. Typically, this is done by calling `Sentry.init()` in an `instrument.ts` file and loading it via the Node `--import` flag.
1516
*/
16-
export const sentry = (app: Hono, options?: HonoNodeOptions): MiddlewareHandler => {
17-
if (options) {
18-
options.debug && debug.log('Initialized Sentry Hono middleware (Node)');
19-
init(options);
17+
export const sentry = (app: Hono): MiddlewareHandler => {
18+
const sentryClient = getClient();
19+
if (sentryClient === undefined) {
20+
debug.warn(
21+
'Sentry is not initialized. Call `init()` from @sentry/hono/node in an `instrument.ts` file loaded via `--import` to set up Sentry for your application.',
22+
);
23+
} else {
24+
sentryClient.getOptions().debug &&
25+
debug.log('Sentry is initialized, proceeding to set up Hono `sentry` middleware.');
2026
}
2127

2228
applyPatches(app);

packages/hono/src/node/sdk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { buildFilteredIntegrations } from '../shared/buildFilteredIntegrations';
1212
export function init(options: HonoNodeOptions): Client | undefined {
1313
const existingClient = getClient();
1414
if (existingClient) {
15-
debug.log('Sentry is already initialized, skipping re-initialization.');
15+
existingClient.getOptions().debug && debug.log('Sentry is already initialized, skipping re-initialization.');
1616
return existingClient;
1717
}
1818

packages/hono/test/node/middleware.test.ts

Lines changed: 16 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as SentryCore from '@sentry/core';
2-
import { SDK_VERSION } from '@sentry/core';
32
import { Hono } from 'hono';
4-
import { beforeEach, describe, expect, it, type Mock, vi } from 'vitest';
3+
import { beforeEach, describe, expect, it, vi } from 'vitest';
54
import { sentry } from '../../src/node/middleware';
65
import { init } from '../../src/node/sdk';
76

@@ -12,129 +11,17 @@ vi.mock('@sentry/node', () => ({
1211
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
1312
const { init: initNodeMock } = await vi.importMock<typeof import('@sentry/node')>('@sentry/node');
1413

15-
vi.mock('@sentry/core', async () => {
16-
const actual = await vi.importActual('@sentry/core');
17-
return {
18-
...actual,
19-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
20-
// @ts-ignore
21-
applySdkMetadata: vi.fn(actual.applySdkMetadata),
22-
};
23-
});
24-
25-
const applySdkMetadataMock = SentryCore.applySdkMetadata as Mock;
26-
2714
describe('Hono Node Middleware', () => {
2815
beforeEach(() => {
2916
vi.clearAllMocks();
3017
});
3118

32-
describe('sentry middleware with options (inline init)', () => {
33-
it('calls applySdkMetadata with "hono"', () => {
34-
const app = new Hono();
35-
const options = {
36-
dsn: 'https://public@dsn.ingest.sentry.io/1337',
37-
};
38-
39-
sentry(app, options);
40-
41-
expect(applySdkMetadataMock).toHaveBeenCalledTimes(1);
42-
expect(applySdkMetadataMock).toHaveBeenCalledWith(options, 'hono', ['hono', 'node']);
43-
});
44-
45-
it('calls init from @sentry/node', () => {
46-
const app = new Hono();
47-
const options = {
48-
dsn: 'https://public@dsn.ingest.sentry.io/1337',
49-
};
50-
51-
sentry(app, options);
52-
53-
expect(initNodeMock).toHaveBeenCalledTimes(1);
54-
expect(initNodeMock).toHaveBeenCalledWith(
55-
expect.objectContaining({
56-
dsn: 'https://public@dsn.ingest.sentry.io/1337',
57-
}),
58-
);
59-
});
60-
61-
it('sets SDK metadata before calling Node init', () => {
62-
const app = new Hono();
63-
const options = {
64-
dsn: 'https://public@dsn.ingest.sentry.io/1337',
65-
};
66-
67-
sentry(app, options);
68-
69-
const applySdkMetadataCallOrder = applySdkMetadataMock.mock.invocationCallOrder[0];
70-
const initNodeCallOrder = (initNodeMock as Mock).mock.invocationCallOrder[0];
71-
72-
expect(applySdkMetadataCallOrder).toBeLessThan(initNodeCallOrder as number);
73-
});
74-
75-
it('preserves all user options', () => {
76-
const app = new Hono();
77-
const options = {
78-
dsn: 'https://public@dsn.ingest.sentry.io/1337',
79-
environment: 'production',
80-
sampleRate: 0.5,
81-
tracesSampleRate: 1.0,
82-
debug: true,
83-
};
84-
85-
sentry(app, options);
86-
87-
expect(initNodeMock).toHaveBeenCalledWith(
88-
expect.objectContaining({
89-
dsn: 'https://public@dsn.ingest.sentry.io/1337',
90-
environment: 'production',
91-
sampleRate: 0.5,
92-
tracesSampleRate: 1.0,
93-
debug: true,
94-
}),
95-
);
96-
});
97-
98-
it('passes an integrations function to initNode (never a raw array)', () => {
99-
const app = new Hono();
100-
sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' });
101-
102-
const callArgs = (initNodeMock as Mock).mock.calls[0]?.[0];
103-
expect(typeof callArgs.integrations).toBe('function');
104-
});
105-
106-
it('includes hono SDK metadata', () => {
107-
const app = new Hono();
108-
const options = {
109-
dsn: 'https://public@dsn.ingest.sentry.io/1337',
110-
};
111-
112-
sentry(app, options);
113-
114-
expect(initNodeMock).toHaveBeenCalledWith(
115-
expect.objectContaining({
116-
_metadata: expect.objectContaining({
117-
sdk: expect.objectContaining({
118-
name: 'sentry.javascript.hono',
119-
version: SDK_VERSION,
120-
packages: [
121-
{ name: 'npm:@sentry/hono', version: SDK_VERSION },
122-
{ name: 'npm:@sentry/node', version: SDK_VERSION },
123-
],
124-
}),
125-
}),
126-
}),
127-
);
128-
});
129-
});
130-
131-
describe('sentry middleware without options (external init)', () => {
132-
it('does not call init when no options are provided', () => {
19+
describe('sentry middleware (external init)', () => {
20+
it('does not call init', () => {
13321
const app = new Hono();
13422
sentry(app);
13523

13624
expect(initNodeMock).not.toHaveBeenCalled();
137-
expect(applySdkMetadataMock).not.toHaveBeenCalled();
13825
});
13926

14027
it('returns a middleware handler function', () => {
@@ -152,23 +39,26 @@ describe('Hono Node Middleware', () => {
15239

15340
expect(middleware.constructor.name).toBe('AsyncFunction');
15441
});
155-
});
15642

157-
describe('common behavior', () => {
158-
it('returns a middleware handler function when options are provided', () => {
43+
it('emits a warning when Sentry is not initialized', () => {
44+
const warnSpy = vi.spyOn(SentryCore.debug, 'warn');
45+
vi.spyOn(SentryCore, 'getClient').mockReturnValue(undefined);
46+
15947
const app = new Hono();
160-
const middleware = sentry(app, { dsn: 'https://public@dsn.ingest.sentry.io/1337' });
48+
sentry(app);
16149

162-
expect(middleware).toBeDefined();
163-
expect(typeof middleware).toBe('function');
164-
expect(middleware).toHaveLength(2);
50+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('Sentry is not initialized'));
16551
});
16652

167-
it('returns an async middleware handler when options are provided', () => {
53+
it('does not emit a warning when Sentry is already initialized', () => {
54+
const warnSpy = vi.spyOn(SentryCore.debug, 'warn');
55+
const fakeClient = { getOptions: () => ({ debug: false }) };
56+
vi.spyOn(SentryCore, 'getClient').mockReturnValue(fakeClient as unknown as SentryCore.Client);
57+
16858
const app = new Hono();
169-
const middleware = sentry(app, {});
59+
sentry(app);
17060

171-
expect(middleware.constructor.name).toBe('AsyncFunction');
61+
expect(warnSpy).not.toHaveBeenCalled();
17262
});
17363
});
17464

@@ -183,7 +73,6 @@ describe('Hono Node Middleware', () => {
18373

18474
expect(result).toBe(fakeClient);
18575
expect(initNodeMock).not.toHaveBeenCalled();
186-
expect(applySdkMetadataMock).not.toHaveBeenCalled();
18776

18877
getClientSpy.mockRestore();
18978
});

0 commit comments

Comments
 (0)