Skip to content

Commit 4b699a5

Browse files
committed
feat(pg): Patch client inside lib and lib/pg-native
1 parent d7773a2 commit 4b699a5

File tree

3 files changed

+142
-38
lines changed

3 files changed

+142
-38
lines changed

plugins/node/opentelemetry-instrumentation-pg/src/instrumentation.ts

Lines changed: 78 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
InstrumentationBase,
1919
InstrumentationNodeModuleDefinition,
2020
safeExecuteInTheMiddle,
21+
InstrumentationNodeModuleFile,
2122
} from '@opentelemetry/instrumentation';
2223
import {
2324
context,
@@ -67,6 +68,12 @@ import {
6768
ATTR_DB_OPERATION_NAME,
6869
} from '@opentelemetry/semantic-conventions/incubating';
6970

71+
function extractModuleExports(module: any) {
72+
return module[Symbol.toStringTag] === 'Module'
73+
? module.default // ESM
74+
: module; // CommonJS
75+
}
76+
7077
export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConfig> {
7178
private _operationDuration!: Histogram;
7279
private _connectionsCount!: UpDownCounter;
@@ -125,45 +132,33 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
125132
}
126133

127134
protected init() {
128-
const modulePG = new InstrumentationNodeModuleDefinition(
129-
'pg',
130-
['>=8.0.3 <9'],
131-
(module: any) => {
132-
const moduleExports: typeof pgTypes =
133-
module[Symbol.toStringTag] === 'Module'
134-
? module.default // ESM
135-
: module; // CommonJS
136-
if (isWrapped(moduleExports.Client.prototype.query)) {
137-
this._unwrap(moduleExports.Client.prototype, 'query');
138-
}
139-
140-
if (isWrapped(moduleExports.Client.prototype.connect)) {
141-
this._unwrap(moduleExports.Client.prototype, 'connect');
142-
}
135+
const SUPPORTED_PG_VERSIONS = ['>=8.0.3 <9'];
143136

144-
this._wrap(
145-
moduleExports.Client.prototype,
146-
'query',
147-
this._getClientQueryPatch() as any
148-
);
137+
const modulePgNativeClient = new InstrumentationNodeModuleFile(
138+
'pg/lib/native/client.js',
139+
SUPPORTED_PG_VERSIONS,
140+
this._patchPgClient.bind(this),
141+
this._unpatchPgClient.bind(this)
142+
);
149143

150-
this._wrap(
151-
moduleExports.Client.prototype,
152-
'connect',
153-
this._getClientConnectPatch() as any
154-
);
144+
const modulePgClient = new InstrumentationNodeModuleFile(
145+
'pg/lib/client.js',
146+
SUPPORTED_PG_VERSIONS,
147+
this._patchPgClient.bind(this),
148+
this._unpatchPgClient.bind(this)
149+
);
155150

151+
const modulePG = new InstrumentationNodeModuleDefinition(
152+
'pg',
153+
SUPPORTED_PG_VERSIONS,
154+
(module: any) => {
155+
this._patchPgClient(module.Client);
156156
return module;
157157
},
158158
(module: any) => {
159-
const moduleExports: typeof pgTypes =
160-
module[Symbol.toStringTag] === 'Module'
161-
? module.default // ESM
162-
: module; // CommonJS
163-
if (isWrapped(moduleExports.Client.prototype.query)) {
164-
this._unwrap(moduleExports.Client.prototype, 'query');
165-
}
166-
}
159+
this._unpatchPgClient(module.Client);
160+
return module;
161+
}, [modulePgClient, modulePgNativeClient]
167162
);
168163

169164
const modulePGPool = new InstrumentationNodeModuleDefinition(
@@ -190,6 +185,51 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
190185
return [modulePG, modulePGPool];
191186
}
192187

188+
private _patchPgClient(
189+
module: any,
190+
) {
191+
const moduleExports = extractModuleExports(module);
192+
193+
if (isWrapped(moduleExports.prototype.query)) {
194+
this._unwrap(moduleExports.prototype, 'query');
195+
}
196+
197+
if (isWrapped(moduleExports.prototype.connect)) {
198+
this._unwrap(moduleExports.prototype, 'connect');
199+
}
200+
201+
this._wrap(
202+
moduleExports.prototype,
203+
'query',
204+
this._getClientQueryPatch() as any
205+
);
206+
207+
this._wrap(
208+
moduleExports.prototype,
209+
'connect',
210+
this._getClientConnectPatch() as any
211+
);
212+
213+
return module;
214+
}
215+
216+
private _unpatchPgClient(
217+
module: any,
218+
) {
219+
const moduleExports = extractModuleExports(module);
220+
221+
if (isWrapped(moduleExports.prototype.query)) {
222+
this._unwrap(moduleExports.prototype, 'query');
223+
}
224+
225+
if (isWrapped(moduleExports.prototype.connect)) {
226+
this._unwrap(moduleExports.prototype, 'connect');
227+
}
228+
229+
return module;
230+
}
231+
232+
193233
private _getClientConnectPatch() {
194234
const plugin = this;
195235
return (original: PgClientConnect) => {
@@ -272,12 +312,12 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
272312
// to properly narrow arg0, but TS 4.3.5 does not.
273313
const queryConfig = firstArgIsString
274314
? {
275-
text: arg0 as string,
276-
values: Array.isArray(args[1]) ? args[1] : undefined,
277-
}
315+
text: arg0 as string,
316+
values: Array.isArray(args[1]) ? args[1] : undefined,
317+
}
278318
: firstArgIsQueryObjectWithText
279-
? (arg0 as utils.ObjectWithText)
280-
: undefined;
319+
? (arg0 as utils.ObjectWithText)
320+
: undefined;
281321

282322
const attributes: Attributes = {
283323
[SEMATTRS_DB_SYSTEM]: DBSYSTEMVALUES_POSTGRESQL,
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// Use postgres from an ES module:
18+
// node --experimental-loader=@opentelemetry/instrumentation/hook.mjs pg-esm.mjs
19+
20+
import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils';
21+
22+
import { PgInstrumentation } from '../../build/src/index.js';
23+
24+
const sdk = createTestNodeSdk({
25+
serviceName: 'use-pg',
26+
instrumentations: [
27+
new PgInstrumentation()
28+
]
29+
})
30+
sdk.start();
31+
32+
import pg from 'pg';
33+
34+
const client = new pg.Client();
35+
36+
client.connect();
37+
38+
client.query('SELECT NOW()', (err, res) => {
39+
console.log(err, res);
40+
client.end();
41+
});
42+
43+
44+

plugins/node/opentelemetry-instrumentation-pg/test/pg.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,4 +1086,24 @@ describe('pg', () => {
10861086
});
10871087
});
10881088
});
1089+
1090+
it('should work with ESM usage', async () => {
1091+
await testUtils.runTestFixture({
1092+
cwd: __dirname,
1093+
argv: ['fixtures/use-pg.mjs'],
1094+
env: {
1095+
NODE_OPTIONS:
1096+
'--experimental-loader=@opentelemetry/instrumentation/hook.mjs',
1097+
NODE_NO_WARNINGS: '1',
1098+
},
1099+
checkResult: (err, stdout, stderr) => {
1100+
assert.ifError(err);
1101+
},
1102+
checkCollector: (collector: testUtils.TestCollector) => {
1103+
const spans = collector.sortedSpans;
1104+
1105+
assert.strictEqual(spans.length, 2);
1106+
},
1107+
});
1108+
});
10891109
});

0 commit comments

Comments
 (0)