Skip to content

Commit e507110

Browse files
authored
feat(tracing): allow direct pg module to enable esbuild support (#9227)
1 parent f60f763 commit e507110

File tree

2 files changed

+62
-18
lines changed

2 files changed

+62
-18
lines changed

packages/tracing-internal/src/node/integrations/postgres.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,15 @@ import { fill, isThenable, loadModule, logger } from '@sentry/utils';
55
import type { LazyLoadedIntegration } from './lazy';
66
import { shouldDisableAutoInstrumentation } from './utils/node-utils';
77

8+
type PgClientQuery = (
9+
config: unknown,
10+
values?: unknown,
11+
callback?: (err: unknown, result: unknown) => void,
12+
) => void | Promise<unknown>;
13+
814
interface PgClient {
915
prototype: {
10-
query: () => void | Promise<unknown>;
16+
query: PgClientQuery;
1117
};
1218
}
1319

@@ -20,9 +26,23 @@ interface PgClientThis {
2026

2127
interface PgOptions {
2228
usePgNative?: boolean;
29+
/**
30+
* Supply your postgres module directly, instead of having Sentry attempt automatic resolution.
31+
* Use this if you (a) use a module that's not `pg`, or (b) use a bundler that breaks resolution (e.g. esbuild).
32+
*
33+
* Usage:
34+
* ```
35+
* import pg from 'pg';
36+
*
37+
* Sentry.init({
38+
* integrations: [new Sentry.Integrations.Postgres({ module: pg })],
39+
* });
40+
* ```
41+
*/
42+
module?: PGModule;
2343
}
2444

25-
type PGModule = { Client: PgClient; native: { Client: PgClient } };
45+
type PGModule = { Client: PgClient; native: { Client: PgClient } | null };
2646

2747
/** Tracing integration for node-postgres package */
2848
export class Postgres implements LazyLoadedIntegration<PGModule> {
@@ -43,6 +63,7 @@ export class Postgres implements LazyLoadedIntegration<PGModule> {
4363
public constructor(options: PgOptions = {}) {
4464
this.name = Postgres.id;
4565
this._usePgNative = !!options.usePgNative;
66+
this._module = options.module;
4667
}
4768

4869
/** @inheritdoc */
@@ -66,21 +87,21 @@ export class Postgres implements LazyLoadedIntegration<PGModule> {
6687
return;
6788
}
6889

69-
if (this._usePgNative && !pkg.native?.Client) {
90+
const Client = this._usePgNative ? pkg.native?.Client : pkg.Client;
91+
92+
if (!Client) {
7093
__DEBUG_BUILD__ && logger.error("Postgres Integration was unable to access 'pg-native' bindings.");
7194
return;
7295
}
7396

74-
const { Client } = this._usePgNative ? pkg.native : pkg;
75-
7697
/**
7798
* function (query, callback) => void
7899
* function (query, params, callback) => void
79100
* function (query) => Promise
80101
* function (query, params) => Promise
81102
* function (pg.Cursor) => pg.Cursor
82103
*/
83-
fill(Client.prototype, 'query', function (orig: () => void | Promise<unknown>) {
104+
fill(Client.prototype, 'query', function (orig: PgClientQuery) {
84105
return function (this: PgClientThis, config: unknown, values: unknown, callback: unknown) {
85106
const scope = getCurrentHub().getScope();
86107
const parentSpan = scope.getSpan();

packages/tracing/test/integrations/node/postgres.test.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
/* eslint-disable deprecation/deprecation */
22
/* eslint-disable @typescript-eslint/unbound-method */
33
import { Hub, Scope } from '@sentry/core';
4-
import { logger } from '@sentry/utils';
4+
import { loadModule, logger } from '@sentry/utils';
5+
import pg from 'pg';
56

67
import { Integrations, Span } from '../../../src';
78
import { getTestClient } from '../../testutils';
89

910
class PgClient {
1011
// https://node-postgres.com/api/client#clientquery
11-
public query(_text: unknown, values: unknown, callback?: () => void) {
12+
public query(_text: unknown, values: unknown, callback?: (err: unknown, result: unknown) => void) {
1213
if (typeof callback === 'function') {
13-
callback();
14+
callback(null, null);
1415
return;
1516
}
1617

@@ -25,25 +26,28 @@ class PgClient {
2526

2627
// Jest mocks get hoisted. vars starting with `mock` are hoisted before imports.
2728
/* eslint-disable no-var */
28-
var mockClient = PgClient;
29+
var mockModule = {
30+
Client: PgClient,
31+
native: {
32+
Client: PgClient,
33+
},
34+
};
2935

3036
// mock for 'pg' / 'pg-native' package
3137
jest.mock('@sentry/utils', () => {
3238
const actual = jest.requireActual('@sentry/utils');
3339
return {
3440
...actual,
35-
loadModule() {
36-
return {
37-
Client: mockClient,
38-
native: {
39-
Client: mockClient,
40-
},
41-
};
42-
},
41+
loadModule: jest.fn(() => mockModule),
4342
};
4443
});
4544

4645
describe('setupOnce', () => {
46+
beforeEach(() => {
47+
jest.clearAllMocks();
48+
jest.resetAllMocks();
49+
});
50+
4751
['pg', 'pg-native'].forEach(pgApi => {
4852
const Client: PgClient = new PgClient();
4953
let scope = new Scope();
@@ -127,4 +131,23 @@ describe('setupOnce', () => {
127131

128132
expect(loggerLogSpy).toBeCalledWith('Postgres Integration is skipped because of instrumenter configuration.');
129133
});
134+
135+
it('does not attempt resolution when module is passed directly', async () => {
136+
const scope = new Scope();
137+
jest.spyOn(scope, 'getSpan').mockReturnValueOnce(new Span());
138+
139+
new Integrations.Postgres({ module: mockModule }).setupOnce(
140+
() => undefined,
141+
() => new Hub(undefined, scope),
142+
);
143+
144+
await new PgClient().query('SELECT NOW()', null);
145+
146+
expect(loadModule).not.toBeCalled();
147+
expect(scope.getSpan).toBeCalled();
148+
});
149+
150+
it('has valid module type', () => {
151+
expect(() => new Integrations.Postgres({ module: pg })).not.toThrow();
152+
});
130153
});

0 commit comments

Comments
 (0)