Skip to content

Commit b856e10

Browse files
author
Naseem
authored
feat: implement mysql semantic conventions (open-telemetry#183)
Signed-off-by: Naseem <naseem@transit.app>
1 parent cb18633 commit b856e10

File tree

6 files changed

+156
-218
lines changed

6 files changed

+156
-218
lines changed

Diff for: plugins/node/opentelemetry-plugin-mysql/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"devDependencies": {
4444
"@opentelemetry/context-async-hooks": "0.10.2",
4545
"@opentelemetry/node": "0.10.2",
46+
"@opentelemetry/semantic-conventions": "^0.10.2",
4647
"@opentelemetry/test-utils": "^0.9.0",
4748
"@opentelemetry/tracing": "0.10.2",
4849
"@types/mocha": "7.0.2",

Diff for: plugins/node/opentelemetry-plugin-mysql/src/enums.ts

-36
This file was deleted.

Diff for: plugins/node/opentelemetry-plugin-mysql/src/mysql.ts

+17-24
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,19 @@
1616

1717
import { BasePlugin, isWrapped } from '@opentelemetry/core';
1818
import { CanonicalCode, Span, SpanKind } from '@opentelemetry/api';
19-
import * as mysqlTypes from 'mysql';
19+
import type * as mysqlTypes from 'mysql';
2020
import * as shimmer from 'shimmer';
21-
import { AttributeNames } from './enums';
22-
import { getConnectionAttributes, getSpanName } from './utils';
21+
import { getConnectionAttributes, getDbStatement } from './utils';
2322
import { VERSION } from './version';
23+
import { DatabaseAttribute } from '@opentelemetry/semantic-conventions';
2424

2525
export class MysqlPlugin extends BasePlugin<typeof mysqlTypes> {
2626
readonly supportedVersions = ['2.*'];
2727

2828
static readonly COMPONENT = 'mysql';
29-
static readonly DB_TYPE = 'sql';
3029

3130
static readonly COMMON_ATTRIBUTES = {
32-
[AttributeNames.COMPONENT]: MysqlPlugin.COMPONENT,
33-
[AttributeNames.DB_TYPE]: MysqlPlugin.DB_TYPE,
34-
[AttributeNames.PEER_SERVICE]: MysqlPlugin.COMPONENT,
31+
[DatabaseAttribute.DB_SYSTEM]: MysqlPlugin.COMPONENT,
3532
};
3633

3734
private _enabled = false;
@@ -198,6 +195,8 @@ export class MysqlPlugin extends BasePlugin<typeof mysqlTypes> {
198195
const thisPlugin = this;
199196
thisPlugin._logger.debug('MysqlPlugin: patched mysql query');
200197

198+
const format = this._moduleExports.format;
199+
201200
return function query(
202201
query: string | mysqlTypes.Query | mysqlTypes.QueryOptions,
203202
_valuesOrCallback?: unknown[] | mysqlTypes.queryCallback,
@@ -208,28 +207,27 @@ export class MysqlPlugin extends BasePlugin<typeof mysqlTypes> {
208207
return originalQuery.apply(connection, arguments);
209208
}
210209

211-
const spanName = getSpanName(query);
212-
213-
const span = thisPlugin._tracer.startSpan(spanName, {
210+
const span = thisPlugin._tracer.startSpan(`${query}`, {
214211
kind: SpanKind.CLIENT,
215212
attributes: {
216213
...MysqlPlugin.COMMON_ATTRIBUTES,
217214
...getConnectionAttributes(connection.config),
218215
},
219216
});
220217

221-
if (typeof query === 'string') {
222-
span.setAttribute(AttributeNames.DB_STATEMENT, query);
223-
} else if (typeof query === 'object') {
224-
if (query.sql) {
225-
span.setAttribute(AttributeNames.DB_STATEMENT, query.sql);
226-
}
218+
let values;
227219

228-
if (query.values) {
229-
span.setAttribute(AttributeNames.MYSQL_VALUES, query.values);
230-
}
220+
if (Array.isArray(_valuesOrCallback)) {
221+
values = _valuesOrCallback;
222+
} else if (arguments[2]) {
223+
values = [_valuesOrCallback];
231224
}
232225

226+
span.setAttribute(
227+
DatabaseAttribute.DB_STATEMENT,
228+
getDbStatement(query, format, values)
229+
);
230+
233231
if (arguments.length === 1) {
234232
const streamableQuery: mysqlTypes.Query = originalQuery.apply(
235233
connection,
@@ -251,11 +249,6 @@ export class MysqlPlugin extends BasePlugin<typeof mysqlTypes> {
251249
if (typeof arguments[1] === 'function') {
252250
shimmer.wrap(arguments, 1, thisPlugin._patchCallbackQuery(span));
253251
} else if (typeof arguments[2] === 'function') {
254-
if (Array.isArray(_valuesOrCallback)) {
255-
span.setAttribute(AttributeNames.MYSQL_VALUES, _valuesOrCallback);
256-
} else if (arguments[2]) {
257-
span.setAttribute(AttributeNames.MYSQL_VALUES, [_valuesOrCallback]);
258-
}
259252
shimmer.wrap(arguments, 2, thisPlugin._patchCallbackQuery(span));
260253
}
261254

Diff for: plugins/node/opentelemetry-plugin-mysql/src/types.ts

-19
This file was deleted.

Diff for: plugins/node/opentelemetry-plugin-mysql/src/utils.ts

+41-34
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,16 @@
1515
*/
1616

1717
import { Attributes } from '@opentelemetry/api';
18-
import { AttributeNames } from './enums';
19-
import { Query } from './types';
20-
import type { ConnectionConfig, PoolActualConfig } from 'mysql';
21-
22-
/**
23-
* Get a span name from a mysql query
24-
*
25-
* @param query mysql Query or string
26-
*/
27-
export function getSpanName(query: string | Query) {
28-
return `mysql.query:${getCommand(query)}`;
29-
}
30-
31-
/**
32-
* Get the low cardinality command name from a query.
33-
*
34-
* @param query mysql Query or string
35-
*/
36-
function getCommand(query: string | Query) {
37-
const queryString = typeof query === 'string' ? query : query.sql;
38-
39-
if (!queryString) {
40-
return 'UNKNOWN_COMMAND';
41-
}
42-
43-
// Command is the first non-whitespace token in the query
44-
const match = queryString.match(/^\s*(\w+)/);
45-
return (match && match[1]) || 'UNKNOWN_COMMAND';
46-
}
18+
import {
19+
DatabaseAttribute,
20+
GeneralAttribute,
21+
} from '@opentelemetry/semantic-conventions';
22+
import type {
23+
ConnectionConfig,
24+
PoolActualConfig,
25+
Query,
26+
QueryOptions,
27+
} from 'mysql';
4728

4829
/**
4930
* Get an Attributes map from a mysql connection config object
@@ -56,11 +37,11 @@ export function getConnectionAttributes(
5637
const { host, port, database, user } = getConfig(config);
5738

5839
return {
59-
[AttributeNames.PEER_ADDRESS]: getJDBCString(host, port, database),
60-
[AttributeNames.DB_INSTANCE]: database,
61-
[AttributeNames.PEER_HOSTNAME]: host,
62-
[AttributeNames.PEER_PORT]: port,
63-
[AttributeNames.DB_USER]: user,
40+
[GeneralAttribute.NET_PEER_HOSTNAME]: host,
41+
[GeneralAttribute.NET_PEER_PORT]: port,
42+
[GeneralAttribute.NET_PEER_ADDRESS]: getJDBCString(host, port, database),
43+
[DatabaseAttribute.DB_NAME]: database,
44+
[DatabaseAttribute.DB_USER]: user,
6445
};
6546
}
6647

@@ -87,3 +68,29 @@ function getJDBCString(
8768

8869
return jdbcString;
8970
}
71+
72+
/**
73+
* Conjures up the value for the db.statement attribute by formatting a SQL query.
74+
*
75+
* @returns the database statement being executed.
76+
*/
77+
export function getDbStatement(
78+
query: string | Query | QueryOptions,
79+
format: (
80+
sql: string,
81+
values: any[],
82+
stringifyObjects?: boolean,
83+
timeZone?: string
84+
) => string,
85+
values?: any[]
86+
): string {
87+
if (typeof query === 'string') {
88+
return values ? format(query, values) : query;
89+
} else {
90+
// According to https://github.com/mysqljs/mysql#performing-queries
91+
// The values argument will override the values in the option object.
92+
return values || query.values
93+
? format(query.sql, values || query.values)
94+
: query.sql;
95+
}
96+
}

0 commit comments

Comments
 (0)