Skip to content

Commit 11a20b6

Browse files
nicohrubecclaude
andauthored
ref(node): Vendor knex instrumentation (#20963)
Vendors `@opentelemetry/instrumentation-knex` into the SDK with no logic changes. Closes #20155 Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 576fd81 commit 11a20b6

9 files changed

Lines changed: 487 additions & 11 deletions

File tree

.oxlintrc.base.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@
152152
"typescript/no-explicit-any": "off"
153153
}
154154
},
155+
{
156+
"files": ["**/integrations/tracing/knex/vendored/**/*.ts"],
157+
"rules": {
158+
"typescript/no-explicit-any": "off"
159+
}
160+
},
155161
{
156162
"files": ["**/scenarios/**", "**/rollup-utils/**"],
157163
"rules": {

packages/node/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@
7575
"@opentelemetry/instrumentation-hapi": "0.60.0",
7676
"@opentelemetry/instrumentation-http": "0.214.0",
7777
"@opentelemetry/instrumentation-kafkajs": "0.23.0",
78-
"@opentelemetry/instrumentation-knex": "0.58.0",
7978
"@opentelemetry/instrumentation-koa": "0.62.0",
8079
"@opentelemetry/instrumentation-mongodb": "0.67.0",
8180
"@opentelemetry/instrumentation-mongoose": "0.60.0",

packages/node/src/integrations/tracing/knex.ts renamed to packages/node/src/integrations/tracing/knex/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { KnexInstrumentation } from '@opentelemetry/instrumentation-knex';
1+
import { KnexInstrumentation } from './vendored/instrumentation';
22
import type { IntegrationFn } from '@sentry/core';
33
import { defineIntegration, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, spanToJSON } from '@sentry/core';
44
import { generateInstrumentOnce, instrumentWhenWrapped } from '@sentry/node-core';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
* NOTICE from the Sentry authors:
17+
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-knex
18+
* - Upstream version: @opentelemetry/instrumentation-knex@0.62.0
19+
*/
20+
/* eslint-disable */
21+
22+
export const MODULE_NAME = 'knex';
23+
export const SUPPORTED_VERSIONS = [
24+
// use "lib/execution" for runner.js, "lib" for client.js as basepath, latest tested 0.95.6
25+
'>=0.22.0 <4',
26+
// use "lib" as basepath
27+
'>=0.10.0 <0.18.0',
28+
'>=0.19.0 <0.22.0',
29+
// use "src" as basepath
30+
'>=0.18.0 <0.19.0',
31+
];
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
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+
* NOTICE from the Sentry authors:
17+
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-knex
18+
* - Upstream version: @opentelemetry/instrumentation-knex@0.62.0
19+
* - Minor TypeScript strictness adjustments for this repository's compiler settings
20+
*/
21+
/* eslint-disable */
22+
23+
import * as api from '@opentelemetry/api';
24+
import { SDK_VERSION } from '@sentry/core';
25+
import * as constants from './constants';
26+
import {
27+
InstrumentationBase,
28+
InstrumentationNodeModuleDefinition,
29+
InstrumentationNodeModuleFile,
30+
isWrapped,
31+
SemconvStability,
32+
semconvStabilityFromStr,
33+
} from '@opentelemetry/instrumentation';
34+
import * as utils from './utils';
35+
import { KnexInstrumentationConfig } from './types';
36+
import {
37+
ATTR_DB_COLLECTION_NAME,
38+
ATTR_DB_NAMESPACE,
39+
ATTR_DB_OPERATION_NAME,
40+
ATTR_DB_QUERY_TEXT,
41+
ATTR_DB_SYSTEM_NAME,
42+
ATTR_SERVER_ADDRESS,
43+
ATTR_SERVER_PORT,
44+
} from '@opentelemetry/semantic-conventions';
45+
import {
46+
ATTR_DB_NAME,
47+
ATTR_DB_OPERATION,
48+
ATTR_DB_SQL_TABLE,
49+
ATTR_DB_STATEMENT,
50+
ATTR_DB_SYSTEM,
51+
ATTR_DB_USER,
52+
ATTR_NET_PEER_NAME,
53+
ATTR_NET_PEER_PORT,
54+
ATTR_NET_TRANSPORT,
55+
} from './semconv';
56+
57+
const PACKAGE_NAME = '@sentry/instrumentation-knex';
58+
59+
const contextSymbol = Symbol('opentelemetry.instrumentation-knex.context');
60+
const DEFAULT_CONFIG: KnexInstrumentationConfig = {
61+
maxQueryLength: 1022,
62+
requireParentSpan: false,
63+
};
64+
65+
export class KnexInstrumentation extends InstrumentationBase<KnexInstrumentationConfig> {
66+
private _semconvStability: SemconvStability;
67+
68+
constructor(config: KnexInstrumentationConfig = {}) {
69+
super(PACKAGE_NAME, SDK_VERSION, { ...DEFAULT_CONFIG, ...config });
70+
71+
this._semconvStability = semconvStabilityFromStr('database', process.env.OTEL_SEMCONV_STABILITY_OPT_IN);
72+
}
73+
74+
override setConfig(config: KnexInstrumentationConfig = {}) {
75+
super.setConfig({ ...DEFAULT_CONFIG, ...config });
76+
}
77+
78+
init() {
79+
const module = new InstrumentationNodeModuleDefinition(constants.MODULE_NAME, constants.SUPPORTED_VERSIONS);
80+
81+
module.files.push(
82+
this.getClientNodeModuleFileInstrumentation('src'),
83+
this.getClientNodeModuleFileInstrumentation('lib'),
84+
this.getRunnerNodeModuleFileInstrumentation('src'),
85+
this.getRunnerNodeModuleFileInstrumentation('lib'),
86+
this.getRunnerNodeModuleFileInstrumentation('lib/execution'),
87+
);
88+
89+
return module;
90+
}
91+
92+
private getRunnerNodeModuleFileInstrumentation(basePath: string) {
93+
return new InstrumentationNodeModuleFile(
94+
`knex/${basePath}/runner.js`,
95+
constants.SUPPORTED_VERSIONS,
96+
(Runner: any, moduleVersion?: string) => {
97+
this.ensureWrapped(Runner.prototype, 'query', this.createQueryWrapper(moduleVersion));
98+
return Runner;
99+
},
100+
(Runner: any, _moduleVersion?: string) => {
101+
this._unwrap(Runner.prototype, 'query');
102+
return Runner;
103+
},
104+
);
105+
}
106+
107+
private getClientNodeModuleFileInstrumentation(basePath: string) {
108+
return new InstrumentationNodeModuleFile(
109+
`knex/${basePath}/client.js`,
110+
constants.SUPPORTED_VERSIONS,
111+
(Client: any) => {
112+
this.ensureWrapped(Client.prototype, 'queryBuilder', this.storeContext.bind(this));
113+
this.ensureWrapped(Client.prototype, 'schemaBuilder', this.storeContext.bind(this));
114+
this.ensureWrapped(Client.prototype, 'raw', this.storeContext.bind(this));
115+
return Client;
116+
},
117+
(Client: any) => {
118+
this._unwrap(Client.prototype, 'queryBuilder');
119+
this._unwrap(Client.prototype, 'schemaBuilder');
120+
this._unwrap(Client.prototype, 'raw');
121+
return Client;
122+
},
123+
);
124+
}
125+
126+
private createQueryWrapper(moduleVersion?: string) {
127+
const instrumentation = this;
128+
129+
return function wrapQuery(original: (...args: any[]) => any) {
130+
return function wrapped_logging_method(this: any, query: any) {
131+
const config = this.client.config;
132+
133+
const table = utils.extractTableName(this.builder);
134+
const operation = query?.method;
135+
const connectionString = config?.connection?.connectionString;
136+
const name =
137+
config?.connection?.filename ||
138+
config?.connection?.database ||
139+
utils.extractDatabaseFromConnectionString(connectionString);
140+
const { maxQueryLength } = instrumentation.getConfig();
141+
142+
const attributes: api.Attributes = {
143+
'knex.version': moduleVersion,
144+
};
145+
const transport = config?.connection?.filename === ':memory:' ? 'inproc' : undefined;
146+
147+
if (instrumentation._semconvStability & SemconvStability.OLD) {
148+
Object.assign(attributes, {
149+
[ATTR_DB_SYSTEM]: utils.mapSystem(this.client.driverName),
150+
[ATTR_DB_SQL_TABLE]: table,
151+
[ATTR_DB_OPERATION]: operation,
152+
[ATTR_DB_USER]: config?.connection?.user,
153+
[ATTR_DB_NAME]: name,
154+
[ATTR_NET_PEER_NAME]: config?.connection?.host ?? utils.extractHostFromConnectionString(connectionString),
155+
[ATTR_NET_PEER_PORT]: config?.connection?.port ?? utils.extractPortFromConnectionString(connectionString),
156+
[ATTR_NET_TRANSPORT]: transport,
157+
});
158+
}
159+
if (instrumentation._semconvStability & SemconvStability.STABLE) {
160+
Object.assign(attributes, {
161+
[ATTR_DB_SYSTEM_NAME]: utils.mapSystem(this.client.driverName),
162+
[ATTR_DB_COLLECTION_NAME]: table,
163+
[ATTR_DB_OPERATION_NAME]: operation,
164+
[ATTR_DB_NAMESPACE]: name,
165+
[ATTR_SERVER_ADDRESS]: config?.connection?.host ?? utils.extractHostFromConnectionString(connectionString),
166+
[ATTR_SERVER_PORT]: config?.connection?.port ?? utils.extractPortFromConnectionString(connectionString),
167+
});
168+
}
169+
if (maxQueryLength) {
170+
const queryText = utils.limitLength(query?.sql, maxQueryLength);
171+
if (instrumentation._semconvStability & SemconvStability.STABLE) {
172+
attributes[ATTR_DB_QUERY_TEXT] = queryText;
173+
}
174+
if (instrumentation._semconvStability & SemconvStability.OLD) {
175+
attributes[ATTR_DB_STATEMENT] = queryText;
176+
}
177+
}
178+
179+
const parentContext = this.builder[contextSymbol] || api.context.active();
180+
const parentSpan = api.trace.getSpan(parentContext);
181+
const hasActiveParent = parentSpan && api.trace.isSpanContextValid(parentSpan.spanContext());
182+
if (instrumentation._config.requireParentSpan && !hasActiveParent) {
183+
return original.bind(this)(...arguments);
184+
}
185+
186+
const span = instrumentation.tracer.startSpan(
187+
utils.getName(name, operation, table),
188+
{
189+
kind: api.SpanKind.CLIENT,
190+
attributes,
191+
},
192+
parentContext,
193+
);
194+
const spanContext = api.trace.setSpan(api.context.active(), span);
195+
196+
return api.context
197+
.with(spanContext, original, this, ...arguments)
198+
.then((result: unknown) => {
199+
span.end();
200+
return result;
201+
})
202+
.catch((err: any) => {
203+
const formatter = utils.getFormatter(this);
204+
const fullQuery = formatter(query.sql, query.bindings || []);
205+
const message = err.message.replace(fullQuery + ' - ', '');
206+
const exc = utils.otelExceptionFromKnexError(err, message);
207+
span.recordException(exc);
208+
span.setStatus({ code: api.SpanStatusCode.ERROR, message });
209+
span.end();
210+
throw err;
211+
});
212+
};
213+
};
214+
}
215+
216+
private storeContext(original: Function) {
217+
return function wrapped_logging_method(this: any) {
218+
const builder = original.apply(this, arguments);
219+
Object.defineProperty(builder, contextSymbol, {
220+
value: api.context.active(),
221+
});
222+
return builder;
223+
};
224+
}
225+
226+
ensureWrapped(obj: any, methodName: string, wrapper: (original: any) => any) {
227+
if (isWrapped(obj[methodName])) {
228+
this._unwrap(obj, methodName);
229+
}
230+
this._wrap(obj, methodName, wrapper);
231+
}
232+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
* NOTICE from the Sentry authors:
17+
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-knex
18+
* - Upstream version: @opentelemetry/instrumentation-knex@0.62.0
19+
*/
20+
/* eslint-disable */
21+
22+
/**
23+
* @deprecated Replaced by `db.namespace`.
24+
*/
25+
export const ATTR_DB_NAME = 'db.name' as const;
26+
27+
/**
28+
* @deprecated Replaced by `db.operation.name`.
29+
*/
30+
export const ATTR_DB_OPERATION = 'db.operation' as const;
31+
32+
/**
33+
* @deprecated Replaced by `db.collection.name`.
34+
*/
35+
export const ATTR_DB_SQL_TABLE = 'db.sql.table' as const;
36+
37+
/**
38+
* @deprecated Replaced by `db.query.text`.
39+
*/
40+
export const ATTR_DB_STATEMENT = 'db.statement' as const;
41+
42+
/**
43+
* @deprecated Replaced by `db.system.name`.
44+
*/
45+
export const ATTR_DB_SYSTEM = 'db.system' as const;
46+
47+
/**
48+
* @deprecated Removed, no replacement at this time.
49+
*/
50+
export const ATTR_DB_USER = 'db.user' as const;
51+
52+
/**
53+
* @deprecated Replaced by `server.address` on client spans and `client.address` on server spans.
54+
*/
55+
export const ATTR_NET_PEER_NAME = 'net.peer.name' as const;
56+
57+
/**
58+
* @deprecated Replaced by `server.port` on client spans and `client.port` on server spans.
59+
*/
60+
export const ATTR_NET_PEER_PORT = 'net.peer.port' as const;
61+
62+
/**
63+
* @deprecated Replaced by `network.transport`.
64+
*/
65+
export const ATTR_NET_TRANSPORT = 'net.transport' as const;
66+
67+
export const DB_SYSTEM_NAME_VALUE_SQLITE = 'sqlite' as const;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
* NOTICE from the Sentry authors:
17+
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-knex
18+
* - Upstream version: @opentelemetry/instrumentation-knex@0.62.0
19+
*/
20+
/* eslint-disable */
21+
22+
import { InstrumentationConfig } from '@opentelemetry/instrumentation';
23+
24+
export interface KnexInstrumentationConfig extends InstrumentationConfig {
25+
/** max query length in db.statement attribute ".." is added to the end when query is truncated */
26+
maxQueryLength?: number;
27+
/** only create spans if part of an existing trace */
28+
requireParentSpan?: boolean;
29+
}

0 commit comments

Comments
 (0)