Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .oxlintrc.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@
"typescript/no-explicit-any": "off"
}
},
{
"files": ["**/integrations/tracing/knex/vendored/**/*.ts"],
"rules": {
"typescript/no-explicit-any": "off"
}
},
{
"files": ["**/scenarios/**", "**/rollup-utils/**"],
"rules": {
Expand Down
1 change: 0 additions & 1 deletion packages/node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@
"@opentelemetry/instrumentation-hapi": "0.60.0",
"@opentelemetry/instrumentation-http": "0.214.0",
"@opentelemetry/instrumentation-kafkajs": "0.23.0",
"@opentelemetry/instrumentation-knex": "0.58.0",
"@opentelemetry/instrumentation-koa": "0.62.0",
"@opentelemetry/instrumentation-mongodb": "0.67.0",
"@opentelemetry/instrumentation-mongoose": "0.60.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { KnexInstrumentation } from '@opentelemetry/instrumentation-knex';
import { KnexInstrumentation } from './vendored/instrumentation';
import type { IntegrationFn } from '@sentry/core';
import { defineIntegration, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, spanToJSON } from '@sentry/core';
import { generateInstrumentOnce, instrumentWhenWrapped } from '@sentry/node-core';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* NOTICE from the Sentry authors:
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-knex
* - Upstream version: @opentelemetry/instrumentation-knex@0.62.0
*/
/* eslint-disable */

export const MODULE_NAME = 'knex';
export const SUPPORTED_VERSIONS = [
// use "lib/execution" for runner.js, "lib" for client.js as basepath, latest tested 0.95.6
'>=0.22.0 <4',
// use "lib" as basepath
'>=0.10.0 <0.18.0',
'>=0.19.0 <0.22.0',
// use "src" as basepath
'>=0.18.0 <0.19.0',
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* NOTICE from the Sentry authors:
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-knex
* - Upstream version: @opentelemetry/instrumentation-knex@0.62.0
* - Minor TypeScript strictness adjustments for this repository's compiler settings
*/
/* eslint-disable */

import * as api from '@opentelemetry/api';
import { SDK_VERSION } from '@sentry/core';
import * as constants from './constants';
import {
InstrumentationBase,
InstrumentationNodeModuleDefinition,
InstrumentationNodeModuleFile,
isWrapped,
SemconvStability,
semconvStabilityFromStr,
} from '@opentelemetry/instrumentation';
import * as utils from './utils';
import { KnexInstrumentationConfig } from './types';
import {
ATTR_DB_COLLECTION_NAME,
ATTR_DB_NAMESPACE,
ATTR_DB_OPERATION_NAME,
ATTR_DB_QUERY_TEXT,
ATTR_DB_SYSTEM_NAME,
ATTR_SERVER_ADDRESS,
ATTR_SERVER_PORT,
} from '@opentelemetry/semantic-conventions';
import {
ATTR_DB_NAME,
ATTR_DB_OPERATION,
ATTR_DB_SQL_TABLE,
ATTR_DB_STATEMENT,
ATTR_DB_SYSTEM,
ATTR_DB_USER,
ATTR_NET_PEER_NAME,
ATTR_NET_PEER_PORT,
ATTR_NET_TRANSPORT,
} from './semconv';

const PACKAGE_NAME = '@sentry/instrumentation-knex';

const contextSymbol = Symbol('opentelemetry.instrumentation-knex.context');
const DEFAULT_CONFIG: KnexInstrumentationConfig = {
maxQueryLength: 1022,
requireParentSpan: false,
};

export class KnexInstrumentation extends InstrumentationBase<KnexInstrumentationConfig> {
private _semconvStability: SemconvStability;

constructor(config: KnexInstrumentationConfig = {}) {
super(PACKAGE_NAME, SDK_VERSION, { ...DEFAULT_CONFIG, ...config });

this._semconvStability = semconvStabilityFromStr('database', process.env.OTEL_SEMCONV_STABILITY_OPT_IN);
}

override setConfig(config: KnexInstrumentationConfig = {}) {
super.setConfig({ ...DEFAULT_CONFIG, ...config });
}

init() {
const module = new InstrumentationNodeModuleDefinition(constants.MODULE_NAME, constants.SUPPORTED_VERSIONS);

module.files.push(
this.getClientNodeModuleFileInstrumentation('src'),
this.getClientNodeModuleFileInstrumentation('lib'),
this.getRunnerNodeModuleFileInstrumentation('src'),
this.getRunnerNodeModuleFileInstrumentation('lib'),
this.getRunnerNodeModuleFileInstrumentation('lib/execution'),
);

return module;
}

private getRunnerNodeModuleFileInstrumentation(basePath: string) {
return new InstrumentationNodeModuleFile(
`knex/${basePath}/runner.js`,
constants.SUPPORTED_VERSIONS,
(Runner: any, moduleVersion?: string) => {
this.ensureWrapped(Runner.prototype, 'query', this.createQueryWrapper(moduleVersion));
return Runner;
},
(Runner: any, _moduleVersion?: string) => {
this._unwrap(Runner.prototype, 'query');
return Runner;
},
);
}

private getClientNodeModuleFileInstrumentation(basePath: string) {
return new InstrumentationNodeModuleFile(
`knex/${basePath}/client.js`,
constants.SUPPORTED_VERSIONS,
(Client: any) => {
this.ensureWrapped(Client.prototype, 'queryBuilder', this.storeContext.bind(this));
this.ensureWrapped(Client.prototype, 'schemaBuilder', this.storeContext.bind(this));
this.ensureWrapped(Client.prototype, 'raw', this.storeContext.bind(this));
return Client;
},
(Client: any) => {
this._unwrap(Client.prototype, 'queryBuilder');
this._unwrap(Client.prototype, 'schemaBuilder');
this._unwrap(Client.prototype, 'raw');
return Client;
},
);
}

private createQueryWrapper(moduleVersion?: string) {
const instrumentation = this;

return function wrapQuery(original: (...args: any[]) => any) {
return function wrapped_logging_method(this: any, query: any) {
const config = this.client.config;

const table = utils.extractTableName(this.builder);
const operation = query?.method;
const connectionString = config?.connection?.connectionString;
const name =
config?.connection?.filename ||
config?.connection?.database ||
utils.extractDatabaseFromConnectionString(connectionString);
const { maxQueryLength } = instrumentation.getConfig();

const attributes: api.Attributes = {
'knex.version': moduleVersion,
};
const transport = config?.connection?.filename === ':memory:' ? 'inproc' : undefined;

if (instrumentation._semconvStability & SemconvStability.OLD) {
Object.assign(attributes, {
[ATTR_DB_SYSTEM]: utils.mapSystem(this.client.driverName),
[ATTR_DB_SQL_TABLE]: table,
[ATTR_DB_OPERATION]: operation,
[ATTR_DB_USER]: config?.connection?.user,
[ATTR_DB_NAME]: name,
[ATTR_NET_PEER_NAME]: config?.connection?.host ?? utils.extractHostFromConnectionString(connectionString),
[ATTR_NET_PEER_PORT]: config?.connection?.port ?? utils.extractPortFromConnectionString(connectionString),
[ATTR_NET_TRANSPORT]: transport,
});
}
if (instrumentation._semconvStability & SemconvStability.STABLE) {
Object.assign(attributes, {
[ATTR_DB_SYSTEM_NAME]: utils.mapSystem(this.client.driverName),
[ATTR_DB_COLLECTION_NAME]: table,
[ATTR_DB_OPERATION_NAME]: operation,
[ATTR_DB_NAMESPACE]: name,
[ATTR_SERVER_ADDRESS]: config?.connection?.host ?? utils.extractHostFromConnectionString(connectionString),
[ATTR_SERVER_PORT]: config?.connection?.port ?? utils.extractPortFromConnectionString(connectionString),
});
}
if (maxQueryLength) {
const queryText = utils.limitLength(query?.sql, maxQueryLength);
if (instrumentation._semconvStability & SemconvStability.STABLE) {
attributes[ATTR_DB_QUERY_TEXT] = queryText;
}
if (instrumentation._semconvStability & SemconvStability.OLD) {
attributes[ATTR_DB_STATEMENT] = queryText;
}
}

const parentContext = this.builder[contextSymbol] || api.context.active();
const parentSpan = api.trace.getSpan(parentContext);
const hasActiveParent = parentSpan && api.trace.isSpanContextValid(parentSpan.spanContext());
if (instrumentation._config.requireParentSpan && !hasActiveParent) {
return original.bind(this)(...arguments);
}

const span = instrumentation.tracer.startSpan(
utils.getName(name, operation, table),
{
kind: api.SpanKind.CLIENT,
attributes,
},
parentContext,
);
const spanContext = api.trace.setSpan(api.context.active(), span);

return api.context
.with(spanContext, original, this, ...arguments)
.then((result: unknown) => {
span.end();
return result;
})
.catch((err: any) => {
const formatter = utils.getFormatter(this);
const fullQuery = formatter(query.sql, query.bindings || []);
const message = err.message.replace(fullQuery + ' - ', '');
const exc = utils.otelExceptionFromKnexError(err, message);
Comment thread
nicohrubec marked this conversation as resolved.
span.recordException(exc);
span.setStatus({ code: api.SpanStatusCode.ERROR, message });
span.end();
throw err;
});
};
};
}

private storeContext(original: Function) {
return function wrapped_logging_method(this: any) {
const builder = original.apply(this, arguments);
Object.defineProperty(builder, contextSymbol, {
value: api.context.active(),
});
return builder;
};
}

ensureWrapped(obj: any, methodName: string, wrapper: (original: any) => any) {
if (isWrapped(obj[methodName])) {
this._unwrap(obj, methodName);
}
this._wrap(obj, methodName, wrapper);
}
}
67 changes: 67 additions & 0 deletions packages/node/src/integrations/tracing/knex/vendored/semconv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* NOTICE from the Sentry authors:
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-knex
* - Upstream version: @opentelemetry/instrumentation-knex@0.62.0
*/
/* eslint-disable */

/**
* @deprecated Replaced by `db.namespace`.
*/
export const ATTR_DB_NAME = 'db.name' as const;

/**
* @deprecated Replaced by `db.operation.name`.
*/
export const ATTR_DB_OPERATION = 'db.operation' as const;

/**
* @deprecated Replaced by `db.collection.name`.
*/
export const ATTR_DB_SQL_TABLE = 'db.sql.table' as const;

/**
* @deprecated Replaced by `db.query.text`.
*/
export const ATTR_DB_STATEMENT = 'db.statement' as const;

/**
* @deprecated Replaced by `db.system.name`.
*/
export const ATTR_DB_SYSTEM = 'db.system' as const;

/**
* @deprecated Removed, no replacement at this time.
*/
export const ATTR_DB_USER = 'db.user' as const;

/**
* @deprecated Replaced by `server.address` on client spans and `client.address` on server spans.
*/
export const ATTR_NET_PEER_NAME = 'net.peer.name' as const;

/**
* @deprecated Replaced by `server.port` on client spans and `client.port` on server spans.
*/
export const ATTR_NET_PEER_PORT = 'net.peer.port' as const;

/**
* @deprecated Replaced by `network.transport`.
*/
export const ATTR_NET_TRANSPORT = 'net.transport' as const;

export const DB_SYSTEM_NAME_VALUE_SQLITE = 'sqlite' as const;
29 changes: 29 additions & 0 deletions packages/node/src/integrations/tracing/knex/vendored/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* NOTICE from the Sentry authors:
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-knex
* - Upstream version: @opentelemetry/instrumentation-knex@0.62.0
*/
/* eslint-disable */

import { InstrumentationConfig } from '@opentelemetry/instrumentation';

export interface KnexInstrumentationConfig extends InstrumentationConfig {
/** max query length in db.statement attribute ".." is added to the end when query is truncated */
maxQueryLength?: number;
/** only create spans if part of an existing trace */
requireParentSpan?: boolean;
}
Loading
Loading