Skip to content
Open
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
4 changes: 3 additions & 1 deletion .oxlintrc.base.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@
"**/integrations/tracing/fastify/vendored/**/*.ts"
],
"rules": {
"typescript/no-explicit-any": "off"
"typescript/no-explicit-any": "off",
"no-unsafe-member-access": "off",
"no-this-alias": "off"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ describe('mysql2 auto instrumentation', () => {
expect.objectContaining({
description: 'SELECT 1 + 1 AS solution',
op: 'db',
origin: 'auto.db.otel.mysql2',
data: expect.objectContaining({
'db.system': 'mysql',
'db.statement': 'SELECT 1 + 1 AS solution',
'net.peer.name': 'localhost',
'net.peer.port': 3306,
'db.user': 'root',
Expand All @@ -22,8 +24,10 @@ describe('mysql2 auto instrumentation', () => {
expect.objectContaining({
description: 'SELECT NOW()',
op: 'db',
origin: 'auto.db.otel.mysql2',
data: expect.objectContaining({
'db.system': 'mysql',
'db.statement': 'SELECT NOW()',
'net.peer.name': 'localhost',
'net.peer.port': 3306,
'db.user': 'root',
Expand Down
12 changes: 2 additions & 10 deletions packages/node/src/integrations/tracing/mysql2/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { MySQL2Instrumentation } from './vendored/instrumentation';
import type { IntegrationFn } from '@sentry/core';
import { defineIntegration } from '@sentry/core';
import { addOriginToSpan, generateInstrumentOnce } from '@sentry/node-core';
import { generateInstrumentOnce } from '@sentry/node-core';

const INTEGRATION_NAME = 'Mysql2';

export const instrumentMysql2 = generateInstrumentOnce(
INTEGRATION_NAME,
() =>
new MySQL2Instrumentation({
responseHook(span) {
addOriginToSpan(span, 'auto.db.otel.mysql2');
},
}),
);
export const instrumentMysql2 = generateInstrumentOnce(INTEGRATION_NAME, () => new MySQL2Instrumentation());

const _mysql2Integration = (() => {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,75 +18,59 @@
* - Upstream version: @opentelemetry/instrumentation-mysql2@0.64.0
* - Types from 'mysql2' inlined as simplified interfaces
* - Minor TypeScript strictness adjustments for this repository's compiler settings
* - Refactored to use Sentry's span APIs instead of OpenTelemetry tracing APIs
*/
/* eslint-disable */

import * as api from '@opentelemetry/api';
import { SDK_VERSION } from '@sentry/core';
import {
InstrumentationBase,
InstrumentationNodeModuleDefinition,
isWrapped,
safeExecuteInTheMiddle,
SemconvStability,
semconvStabilityFromStr,
} from '@opentelemetry/instrumentation';

import { SpanKind } from '@opentelemetry/api';
import { InstrumentationBase, InstrumentationNodeModuleDefinition, isWrapped } from '@opentelemetry/instrumentation';
import type { SpanAttributes } from '@sentry/core';
import { SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SPAN_STATUS_ERROR, startInactiveSpan } from '@sentry/core';
import { InstrumentationNodeModuleFile } from '../../InstrumentationNodeModuleFile';
import { DB_SYSTEM_VALUE_MYSQL, ATTR_DB_STATEMENT, ATTR_DB_SYSTEM } from './semconv';
import { addSqlCommenterComment } from '../../utils/sql-common';
import type { Connection, Query, QueryOptions, QueryError, FieldPacket, FormatFunction } from './mysql2-types';
import { MySQL2InstrumentationConfig } from './types';
import type { Connection, FormatFunction, Query, QueryError, QueryOptions } from './mysql2-types';
import { ATTR_DB_STATEMENT, ATTR_DB_SYSTEM, DB_SYSTEM_VALUE_MYSQL } from './semconv';
import type { MySQL2InstrumentationConfig } from './types';
import { getConnectionAttributes, getConnectionPrototypeToInstrument, getQueryText, getSpanName, once } from './utils';
import {
ATTR_DB_QUERY_TEXT,
ATTR_DB_SYSTEM_NAME,
DB_SYSTEM_NAME_VALUE_MYSQL,
} from '@opentelemetry/semantic-conventions';

const PACKAGE_NAME = '@sentry/instrumentation-mysql2';
const ORIGIN = 'auto.db.otel.mysql2';

const supportedVersions = ['>=1.4.2 <4'];

export class MySQL2Instrumentation extends InstrumentationBase<MySQL2InstrumentationConfig> {
private _netSemconvStability!: SemconvStability;
private _dbSemconvStability!: SemconvStability;
// The raw imported `mysql2` module exposes the `format` helper used to render
// parameterized queries. Typed shallowly since it is only read internally.
type MySQL2Module = { format?: FormatFunction; [key: string]: unknown };

constructor(config: MySQL2InstrumentationConfig = {}) {
export class MySQL2Instrumentation extends InstrumentationBase<MySQL2InstrumentationConfig> {
public constructor(config: MySQL2InstrumentationConfig = {}) {
super(PACKAGE_NAME, SDK_VERSION, config);
this._setSemconvStabilityFromEnv();
}

private _setSemconvStabilityFromEnv() {
this._netSemconvStability = semconvStabilityFromStr('http', process.env.OTEL_SEMCONV_STABILITY_OPT_IN);
this._dbSemconvStability = semconvStabilityFromStr('database', process.env.OTEL_SEMCONV_STABILITY_OPT_IN);
}

protected init() {
protected init(): InstrumentationNodeModuleDefinition[] {
let format: FormatFunction | undefined;
function setFormatFunction(moduleExports: any) {
function setFormatFunction(moduleExports: MySQL2Module): void {
if (!format && moduleExports.format) {
format = moduleExports.format;
}
}
const patch = (ConnectionPrototype: Connection) => {
const patch = (ConnectionPrototype: Connection): void => {
if (isWrapped(ConnectionPrototype.query)) {
this._unwrap(ConnectionPrototype, 'query');
}
this._wrap(ConnectionPrototype, 'query', this._patchQuery(format, false) as any);
this._wrap(ConnectionPrototype, 'query', this._patchQuery(format) as any);
if (isWrapped(ConnectionPrototype.execute)) {
this._unwrap(ConnectionPrototype, 'execute');
}
this._wrap(ConnectionPrototype, 'execute', this._patchQuery(format, true) as any);
this._wrap(ConnectionPrototype, 'execute', this._patchQuery(format) as any);
};
const unpatch = (ConnectionPrototype: Connection) => {
const unpatch = (ConnectionPrototype: Connection): void => {
this._unwrap(ConnectionPrototype, 'query');
this._unwrap(ConnectionPrototype, 'execute');
};
return [
new InstrumentationNodeModuleDefinition(
'mysql2',
supportedVersions,
(moduleExports: any) => {
(moduleExports: MySQL2Module) => {
setFormatFunction(moduleExports);
return moduleExports;
},
Expand All @@ -95,7 +79,7 @@ export class MySQL2Instrumentation extends InstrumentationBase<MySQL2Instrumenta
new InstrumentationNodeModuleFile(
'mysql2/promise.js',
supportedVersions,
(moduleExports: any) => {
(moduleExports: MySQL2Module) => {
setFormatFunction(moduleExports);
return moduleExports;
},
Expand All @@ -120,9 +104,9 @@ export class MySQL2Instrumentation extends InstrumentationBase<MySQL2Instrumenta
];
}

private _patchQuery(format: FormatFunction | undefined, isPrepared: boolean) {
private _patchQuery(format: FormatFunction | undefined) {
const thisPlugin = this;
return (originalQuery: Function): Function => {
const thisPlugin = this;
return function query(
this: Connection,
query: string | Query | QueryOptions,
Expand All @@ -135,61 +119,24 @@ export class MySQL2Instrumentation extends InstrumentationBase<MySQL2Instrumenta
} else if (arguments[2]) {
values = [_valuesOrCallback];
}
const { maskStatement, maskStatementHook, responseHook } = thisPlugin.getConfig();

const attributes: api.Attributes = getConnectionAttributes(
this.config,
thisPlugin._dbSemconvStability,
thisPlugin._netSemconvStability,
);
const dbQueryText = getQueryText(query, format, values, maskStatement, maskStatementHook);
if (thisPlugin._dbSemconvStability & SemconvStability.OLD) {
attributes[ATTR_DB_SYSTEM] = DB_SYSTEM_VALUE_MYSQL;
attributes[ATTR_DB_STATEMENT] = dbQueryText;
}
if (thisPlugin._dbSemconvStability & SemconvStability.STABLE) {
attributes[ATTR_DB_SYSTEM_NAME] = DB_SYSTEM_NAME_VALUE_MYSQL;
attributes[ATTR_DB_QUERY_TEXT] = dbQueryText;
}

const span = thisPlugin.tracer.startSpan(getSpanName(query), {
kind: api.SpanKind.CLIENT,
const attributes: SpanAttributes = {
...getConnectionAttributes(this.config),
[ATTR_DB_SYSTEM]: DB_SYSTEM_VALUE_MYSQL,
[ATTR_DB_STATEMENT]: getQueryText(query, format, values),
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: ORIGIN,
};

const span = startInactiveSpan({
name: getSpanName(query),
kind: SpanKind.CLIENT,
attributes,
});

if (!isPrepared && thisPlugin.getConfig().addSqlCommenterCommentToQueries) {
arguments[0] = query =
typeof query === 'string'
? addSqlCommenterComment(span, query)
: Object.assign(query, {
sql: addSqlCommenterComment(span, query.sql),
});
}

const endSpan = once((err?: any, results?: any) => {
const endSpan = once((err?: QueryError | null) => {
if (err) {
span.setStatus({
code: api.SpanStatusCode.ERROR,
message: err.message,
});
} else {
if (typeof responseHook === 'function') {
safeExecuteInTheMiddle(
() => {
responseHook(span, {
queryResults: results,
});
},
err => {
if (err) {
thisPlugin._diag.warn('Failed executing responseHook', err);
}
},
true,
);
}
span.setStatus({ code: SPAN_STATUS_ERROR, message: err.message });
}

span.end();
});

Expand All @@ -204,8 +151,8 @@ export class MySQL2Instrumentation extends InstrumentationBase<MySQL2Instrumenta
.once('error', (err: any) => {
endSpan(err);
})
.once('result', (results: any) => {
endSpan(undefined, results);
.once('result', () => {
endSpan();
});

return streamableQuery;
Expand All @@ -222,11 +169,11 @@ export class MySQL2Instrumentation extends InstrumentationBase<MySQL2Instrumenta
};
}

private _patchCallbackQuery(endSpan: Function) {
private _patchCallbackQuery(endSpan: (err?: QueryError | null) => void) {
return (originalCallback: Function) => {
return function (err: QueryError | null, results?: any, _fields?: FieldPacket[]) {
endSpan(err, results);
return originalCallback(...arguments);
return function (...args: [err: QueryError | null, ...rest: unknown[]]) {
endSpan(args[0]);
return originalCallback(...args);
};
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-mysql2
* - Upstream version: @opentelemetry/instrumentation-mysql2@0.64.0
*/
/* eslint-disable */

export const ATTR_DB_CONNECTION_STRING = 'db.connection_string' as const;
export const ATTR_DB_NAME = 'db.name' as const;
Expand Down
24 changes: 3 additions & 21 deletions packages/node/src/integrations/tracing/mysql2/vendored/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,9 @@
* NOTICE from the Sentry authors:
* - Vendored from: https://github.com/open-telemetry/opentelemetry-js-contrib/tree/15ef7506553f631ea4181391e0c5725a56f0d082/packages/instrumentation-mysql2
* - Upstream version: @opentelemetry/instrumentation-mysql2@0.64.0
* - Pruned config options that Sentry's integration never sets (responseHook, query masking, SQL commenter)
*/
/* eslint-disable */

import { InstrumentationConfig } from '@opentelemetry/instrumentation';
import type { Span } from '@opentelemetry/api';
import type { InstrumentationConfig } from '@opentelemetry/instrumentation';

export interface MySQL2ResponseHookInformation {
queryResults: any;
}

export interface MySQL2InstrumentationExecutionResponseHook {
(span: Span, responseHookInfo: MySQL2ResponseHookInformation): void;
}

export interface MySQL2InstrumentationQueryMaskingHook {
(query: string): string;
}

export interface MySQL2InstrumentationConfig extends InstrumentationConfig {
maskStatement?: boolean;
maskStatementHook?: MySQL2InstrumentationQueryMaskingHook;
responseHook?: MySQL2InstrumentationExecutionResponseHook;
addSqlCommenterCommentToQueries?: boolean;
}
export type MySQL2InstrumentationConfig = InstrumentationConfig;
Loading
Loading