Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mysql] implement semantic conventions #183

Merged
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
1 change: 1 addition & 0 deletions plugins/node/opentelemetry-plugin-mysql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"devDependencies": {
"@opentelemetry/context-async-hooks": "0.10.2",
"@opentelemetry/node": "0.10.2",
"@opentelemetry/semantic-conventions": "^0.10.2",
"@opentelemetry/test-utils": "^0.9.0",
"@opentelemetry/tracing": "0.10.2",
"@types/mocha": "7.0.2",
Expand Down
36 changes: 0 additions & 36 deletions plugins/node/opentelemetry-plugin-mysql/src/enums.ts

This file was deleted.

41 changes: 17 additions & 24 deletions plugins/node/opentelemetry-plugin-mysql/src/mysql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,19 @@

import { BasePlugin, isWrapped } from '@opentelemetry/core';
import { CanonicalCode, Span, SpanKind } from '@opentelemetry/api';
import * as mysqlTypes from 'mysql';
import type * as mysqlTypes from 'mysql';
import * as shimmer from 'shimmer';
import { AttributeNames } from './enums';
import { getConnectionAttributes, getSpanName } from './utils';
import { getConnectionAttributes, getDbStatement } from './utils';
import { VERSION } from './version';
import { DatabaseAttribute } from '@opentelemetry/semantic-conventions';

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

static readonly COMPONENT = 'mysql';
static readonly DB_TYPE = 'sql';

static readonly COMMON_ATTRIBUTES = {
[AttributeNames.COMPONENT]: MysqlPlugin.COMPONENT,
[AttributeNames.DB_TYPE]: MysqlPlugin.DB_TYPE,
[AttributeNames.PEER_SERVICE]: MysqlPlugin.COMPONENT,
[DatabaseAttribute.DB_SYSTEM]: MysqlPlugin.COMPONENT,
naseemkullah marked this conversation as resolved.
Show resolved Hide resolved
};

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

const format = this._moduleExports.format;

return function query(
query: string | mysqlTypes.Query | mysqlTypes.QueryOptions,
_valuesOrCallback?: unknown[] | mysqlTypes.queryCallback,
Expand All @@ -208,28 +207,27 @@ export class MysqlPlugin extends BasePlugin<typeof mysqlTypes> {
return originalQuery.apply(connection, arguments);
}

const spanName = getSpanName(query);

const span = thisPlugin._tracer.startSpan(spanName, {
const span = thisPlugin._tracer.startSpan(`${query}`, {
kind: SpanKind.CLIENT,
attributes: {
...MysqlPlugin.COMMON_ATTRIBUTES,
...getConnectionAttributes(connection.config),
},
});

if (typeof query === 'string') {
span.setAttribute(AttributeNames.DB_STATEMENT, query);
} else if (typeof query === 'object') {
if (query.sql) {
span.setAttribute(AttributeNames.DB_STATEMENT, query.sql);
}
let values;

if (query.values) {
span.setAttribute(AttributeNames.MYSQL_VALUES, query.values);
}
if (Array.isArray(_valuesOrCallback)) {
values = _valuesOrCallback;
} else if (arguments[2]) {
values = [_valuesOrCallback];
}

span.setAttribute(
DatabaseAttribute.DB_STATEMENT,
getDbStatement(query, format, values)
);

if (arguments.length === 1) {
const streamableQuery: mysqlTypes.Query = originalQuery.apply(
connection,
Expand All @@ -251,11 +249,6 @@ export class MysqlPlugin extends BasePlugin<typeof mysqlTypes> {
if (typeof arguments[1] === 'function') {
shimmer.wrap(arguments, 1, thisPlugin._patchCallbackQuery(span));
} else if (typeof arguments[2] === 'function') {
if (Array.isArray(_valuesOrCallback)) {
span.setAttribute(AttributeNames.MYSQL_VALUES, _valuesOrCallback);
} else if (arguments[2]) {
span.setAttribute(AttributeNames.MYSQL_VALUES, [_valuesOrCallback]);
}
shimmer.wrap(arguments, 2, thisPlugin._patchCallbackQuery(span));
}

Expand Down
19 changes: 0 additions & 19 deletions plugins/node/opentelemetry-plugin-mysql/src/types.ts

This file was deleted.

75 changes: 41 additions & 34 deletions plugins/node/opentelemetry-plugin-mysql/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,16 @@
*/

import { Attributes } from '@opentelemetry/api';
import { AttributeNames } from './enums';
import { Query } from './types';
import type { ConnectionConfig, PoolActualConfig } from 'mysql';

/**
* Get a span name from a mysql query
*
* @param query mysql Query or string
*/
export function getSpanName(query: string | Query) {
return `mysql.query:${getCommand(query)}`;
}

/**
* Get the low cardinality command name from a query.
*
* @param query mysql Query or string
*/
function getCommand(query: string | Query) {
const queryString = typeof query === 'string' ? query : query.sql;

if (!queryString) {
return 'UNKNOWN_COMMAND';
}

// Command is the first non-whitespace token in the query
const match = queryString.match(/^\s*(\w+)/);
return (match && match[1]) || 'UNKNOWN_COMMAND';
}
import {
DatabaseAttribute,
GeneralAttribute,
} from '@opentelemetry/semantic-conventions';
import type {
ConnectionConfig,
PoolActualConfig,
Query,
QueryOptions,
} from 'mysql';

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

return {
[AttributeNames.PEER_ADDRESS]: getJDBCString(host, port, database),
[AttributeNames.DB_INSTANCE]: database,
[AttributeNames.PEER_HOSTNAME]: host,
[AttributeNames.PEER_PORT]: port,
[AttributeNames.DB_USER]: user,
[GeneralAttribute.NET_PEER_HOSTNAME]: host,
[GeneralAttribute.NET_PEER_PORT]: port,
[GeneralAttribute.NET_PEER_ADDRESS]: getJDBCString(host, port, database),
[DatabaseAttribute.DB_NAME]: database,
[DatabaseAttribute.DB_USER]: user,
};
}

Expand All @@ -87,3 +68,29 @@ function getJDBCString(

return jdbcString;
}

/**
* Conjures up the value for the db.statement attribute by formatting a SQL query.
*
* @returns the database statement being executed.
*/
export function getDbStatement(
query: string | Query | QueryOptions,
format: (
sql: string,
values: any[],
stringifyObjects?: boolean,
timeZone?: string
) => string,
values?: any[]
): string {
if (typeof query === 'string') {
return values ? format(query, values) : query;
} else {
// According to https://github.com/mysqljs/mysql#performing-queries
// The values argument will override the values in the option object.
return values || query.values
? format(query.sql, values || query.values)
: query.sql;
}
}
Loading