Skip to content

Commit f6bc4cc

Browse files
maryliagtrentmpichlermarc
authored
fix(pg): fix instrumentation of ESM-imported pg-pool (#2807)
The ESM imported top-level object is a Module Namespace Object per ECMA262 spec that is, apparently, incompatible with the 'PG' class object that 'pg' returns. The ".defaults" holds the equivalent of require('pg'). Fixes: #2759 Co-authored-by: Trent Mick <[email protected]> Co-authored-by: Marc Pichler <[email protected]>
1 parent e8e3cbd commit f6bc4cc

File tree

5 files changed

+124
-9
lines changed

5 files changed

+124
-9
lines changed

packages/opentelemetry-test-utils/src/test-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const dockerRunCmds = {
4545
oracledb:
4646
'docker run --rm -d --name otel-oracledb -p 1521:1521 -e ORACLE_PASSWORD=oracle -e APP_USER=otel -e APP_USER_PASSWORD=secret gvenzl/oracle-free:slim',
4747
postgres:
48-
'docker run --rm -d --name otel-postgres -p 54320:5432 -e POSTGRES_PASSWORD=postgres postgres:16-alpine',
48+
'docker run --rm -d --name otel-postgres -p 54320:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=otel_pg_database postgres:16-alpine',
4949
redis: 'docker run --rm -d --name otel-redis -p 63790:6379 redis:alpine',
5050
};
5151

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
133133

134134
protected init() {
135135
const SUPPORTED_PG_VERSIONS = ['>=8.0.3 <9'];
136+
const SUPPORTED_PG_POOL_VERSIONS = ['>=2.0.0 <4'];
136137

137138
const modulePgNativeClient = new InstrumentationNodeModuleFile(
138139
'pg/lib/native/client.js',
@@ -168,8 +169,9 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
168169

169170
const modulePGPool = new InstrumentationNodeModuleDefinition(
170171
'pg-pool',
171-
['>=2.0.0 <4'],
172-
(moduleExports: typeof pgPoolTypes) => {
172+
SUPPORTED_PG_POOL_VERSIONS,
173+
(module: any) => {
174+
const moduleExports = extractModuleExports(module);
173175
if (isWrapped(moduleExports.prototype.connect)) {
174176
this._unwrap(moduleExports.prototype, 'connect');
175177
}
@@ -180,7 +182,8 @@ export class PgInstrumentation extends InstrumentationBase<PgInstrumentationConf
180182
);
181183
return moduleExports;
182184
},
183-
(moduleExports: typeof pgPoolTypes) => {
185+
(module: any) => {
186+
const moduleExports = extractModuleExports(module);
184187
if (isWrapped(moduleExports.prototype.connect)) {
185188
this._unwrap(moduleExports.prototype, 'connect');
186189
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 pg-pool from an ES module:
18+
// node --experimental-loader=@opentelemetry/instrumentation/hook.mjs use-pg-pool.mjs
19+
20+
import { trace } from '@opentelemetry/api';
21+
import { createTestNodeSdk } from '@opentelemetry/contrib-test-utils';
22+
import assert from 'assert';
23+
24+
import { PgInstrumentation } from '../../build/src/index.js';
25+
26+
const CONFIG = {
27+
user: process.env.POSTGRES_USER || 'postgres',
28+
password: process.env.POSTGRES_PASSWORD || 'postgres',
29+
database: process.env.POSTGRES_DB || 'otel_pg_database',
30+
host: process.env.POSTGRES_HOST || 'localhost',
31+
port: process.env.POSTGRES_PORT
32+
? parseInt(process.env.POSTGRES_PORT, 10)
33+
: 54320,
34+
};
35+
36+
const sdk = createTestNodeSdk({
37+
serviceName: 'use-pg-pool',
38+
instrumentations: [new PgInstrumentation()],
39+
});
40+
sdk.start();
41+
42+
import Pool from 'pg-pool';
43+
const pgPool = new Pool(CONFIG);
44+
45+
const tracer = trace.getTracer();
46+
47+
await tracer.startActiveSpan('test-span', async span => {
48+
const client = await pgPool.connect();
49+
try {
50+
const res = await pgPool.query('SELECT NOW()');
51+
assert.ok(res);
52+
console.log('rows:', res.rows);
53+
} finally {
54+
client.release();
55+
pgPool.end();
56+
span.end();
57+
sdk.shutdown();
58+
}
59+
});

plugins/node/opentelemetry-instrumentation-pg/test/fixtures/use-pg.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import { PgInstrumentation } from '../../build/src/index.js';
2626
const CONFIG = {
2727
user: process.env.POSTGRES_USER || 'postgres',
2828
password: process.env.POSTGRES_PASSWORD || 'postgres',
29-
database: process.env.POSTGRES_DB || 'postgres',
29+
database: process.env.POSTGRES_DB || 'otel_pg_database',
3030
host: process.env.POSTGRES_HOST || 'localhost',
3131
port: process.env.POSTGRES_PORT
3232
? parseInt(process.env.POSTGRES_PORT, 10)

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

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,12 @@ describe('pg-pool', () => {
650650

651651
it('should not add duplicate event listeners to PgPool events', done => {
652652
const poolAux: pgPool<pg.Client> = new pgPool(CONFIG);
653+
654+
const finish = () => {
655+
poolAux.end();
656+
done();
657+
};
658+
653659
let completed = 0;
654660
poolAux.connect((err, client, release) => {
655661
if (err) {
@@ -687,7 +693,7 @@ describe('pg-pool', () => {
687693

688694
completed++;
689695
if (completed >= 2) {
690-
done();
696+
finish();
691697
}
692698
});
693699

@@ -727,7 +733,7 @@ describe('pg-pool', () => {
727733

728734
completed++;
729735
if (completed >= 2) {
730-
done();
736+
finish();
731737
}
732738
});
733739
});
@@ -808,6 +814,8 @@ describe('pg-pool', () => {
808814
1,
809815
'expected to have 1 used connection'
810816
);
817+
818+
poolAux.end();
811819
done();
812820
});
813821
});
@@ -817,6 +825,12 @@ describe('pg-pool', () => {
817825
const pool1: pgPool<pg.Client> = new pgPool(CONFIG);
818826
const pool2: pgPool<pg.Client> = new pgPool(CONFIG);
819827

828+
const finish = () => {
829+
pool1.end();
830+
pool2.end();
831+
done();
832+
};
833+
820834
let completed = 0;
821835
pool1.connect((err, client, release) => {
822836
if (err) {
@@ -862,7 +876,7 @@ describe('pg-pool', () => {
862876

863877
completed++;
864878
if (completed >= 2) {
865-
done();
879+
finish();
866880
}
867881
});
868882

@@ -910,9 +924,48 @@ describe('pg-pool', () => {
910924

911925
completed++;
912926
if (completed >= 2) {
913-
done();
927+
finish();
914928
}
915929
});
916930
});
917931
});
918932
});
933+
934+
describe('pg-pool (ESM)', () => {
935+
it('should work with ESM usage', async () => {
936+
await testUtils.runTestFixture({
937+
cwd: __dirname,
938+
argv: ['fixtures/use-pg-pool.mjs'],
939+
env: {
940+
NODE_OPTIONS:
941+
'--experimental-loader=@opentelemetry/instrumentation/hook.mjs',
942+
NODE_NO_WARNINGS: '1',
943+
},
944+
checkResult: (err, stdout, stderr) => {
945+
assert.ifError(err);
946+
},
947+
checkCollector: (collector: testUtils.TestCollector) => {
948+
const spans = collector.sortedSpans;
949+
950+
assert.strictEqual(spans.length, 6);
951+
952+
let span = spans.shift()!;
953+
assert.strictEqual(span.name, 'test-span');
954+
assert.strictEqual(span.kind, 1 /* OtlpSpanKind.INTERNAL */);
955+
const expectedRemainingSpanNames = [
956+
// I believe two sets of `*.connect` spans because pg-pool opens
957+
// two connections to start.
958+
'pg-pool.connect',
959+
'pg.connect',
960+
'pg-pool.connect',
961+
'pg.connect',
962+
'pg.query:SELECT otel_pg_database',
963+
];
964+
for (const expectedName of expectedRemainingSpanNames) {
965+
span = spans.shift()!;
966+
assert.strictEqual(span.name, expectedName);
967+
}
968+
},
969+
});
970+
});
971+
});

0 commit comments

Comments
 (0)