Skip to content

Commit ecd2dcf

Browse files
trentmmaryliag
andauthored
feat(instrumentation-mysql2): support net.* and database semconv migration (#3137)
This adds support for using `OTEL_SEMCONV_STABILITY_OPT_IN` for controlled migration to stable `net.*` and `db.*` semconv. The `net.*` attributes are controlled by the `http[/dup]` token in `OTEL_SEMCONV_STABILITY_OPT_IN` (as [discussed here](open-telemetry/opentelemetry-js#5663 (comment))) and `db.*` with the `database[/dup]` token. Refs: open-telemetry/opentelemetry-js#5663 Refs: #2953 Co-authored-by: Marylia Gutierrez <maryliag@gmail.com>
1 parent 1685420 commit ecd2dcf

File tree

6 files changed

+205
-46
lines changed

6 files changed

+205
-46
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/instrumentation-mysql2/README.md

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,19 +54,30 @@ You can set the following instrumentation options:
5454

5555
## Semantic Conventions
5656

57-
This package uses `@opentelemetry/semantic-conventions` version `1.22+`, which implements Semantic Convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md)
57+
This instrumentation implements Semantic Conventions (semconv) v1.7.0. Since then, networking (in semconv v1.23.1) and database (in semconv v1.33.0) semantic conventions were stabilized. As of `@opentelemetry/instrumentation-mysql2@0.53.0` support has been added for migrating to the stable semantic conventions using the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable as follows:
58+
59+
1. Upgrade to the latest version of this instrumentation package.
60+
2. Set `OTEL_SEMCONV_STABILITY_OPT_IN=http/dup,database/dup` to emit both old and stable semantic conventions. (The `http` token is used to control the `net.*` attributes, the `database` token to control to `db.*` attributes.)
61+
3. Modify alerts, dashboards, metrics, and other processes in your Observability system to use the stable semantic conventions.
62+
4. Set `OTEL_SEMCONV_STABILITY_OPT_IN=http,database` to emit only the stable semantic conventions.
63+
64+
By default, if `OTEL_SEMCONV_STABILITY_OPT_IN` includes neither of the above tokens, the old v1.7.0 semconv is used.
65+
The intent is to provide an approximate 6 month time window for users of this instrumentation to migrate to the new database and networking semconv, after which a new minor version will use the new semconv by default and drop support for the old semconv.
66+
See [the HTTP migration guide](https://opentelemetry.io/docs/specs/semconv/non-normative/http-migration/) and the [database migration guide](https://opentelemetry.io/docs/specs/semconv/non-normative/db-migration/) for details.
5867

5968
Attributes collected:
6069

61-
| Attribute | Short Description |
62-
| ----------------------- | ------------------------------------------------------------------------------ |
63-
| `db.connection_string` | The connection string used to connect to the database. |
64-
| `db.name` | This attribute is used to report the name of the database being accessed. |
65-
| `db.statement` | The database statement being executed. |
66-
| `db.system` | An identifier for the database management system (DBMS) product being used. |
67-
| `db.user` | Username for accessing the database. |
68-
| `net.peer.name` | Remote hostname or similar. |
69-
| `net.peer.port` | Remote port number. |
70+
| Old semconv | Stable semconv | Description |
71+
| ---------------------- | ---------------- | ----------- |
72+
| `db.connection_string` | Removed | The connection string used to connect to the database. |
73+
| `db.name` | Removed, integrated into the new `db.namespace` | The name of the database. |
74+
| `db.system` | `db.system.name` | 'mysql' |
75+
| `db.statement` | `db.query.text` | The database query being executed. |
76+
| `db.user` | Removed | User used to connect to the database. |
77+
| (not included) | `db.namespace` | The name of the database, fully qualified within the server address and port. |
78+
| `net.peer.port` | `server.port` | Remote port number. |
79+
| `net.peer.name` | `server.address` | Remote hostname or similar. |
80+
7081

7182
## Useful links
7283

packages/instrumentation-mysql2/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
},
6969
"dependencies": {
7070
"@opentelemetry/instrumentation": "^0.206.0",
71+
"@opentelemetry/semantic-conventions": "^1.33.0",
7172
"@opentelemetry/sql-common": "^0.41.2"
7273
},
7374
"homepage": "https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-mysql2#readme"

packages/instrumentation-mysql2/src/instrumentation.ts

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
InstrumentationNodeModuleFile,
2222
isWrapped,
2323
safeExecuteInTheMiddle,
24+
SemconvStability,
25+
semconvStabilityFromStr,
2426
} from '@opentelemetry/instrumentation';
2527
import {
2628
DB_SYSTEM_VALUE_MYSQL,
@@ -33,24 +35,41 @@ import { MySQL2InstrumentationConfig } from './types';
3335
import {
3436
getConnectionAttributes,
3537
getConnectionPrototypeToInstrument,
36-
getDbStatement,
38+
getQueryText,
3739
getSpanName,
3840
once,
3941
} from './utils';
4042
/** @knipignore */
4143
import { PACKAGE_NAME, PACKAGE_VERSION } from './version';
44+
import {
45+
ATTR_DB_QUERY_TEXT,
46+
ATTR_DB_SYSTEM_NAME,
47+
DB_SYSTEM_NAME_VALUE_MYSQL,
48+
} from '@opentelemetry/semantic-conventions';
4249

4350
type formatType = typeof mysqlTypes.format;
4451

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

4754
export class MySQL2Instrumentation extends InstrumentationBase<MySQL2InstrumentationConfig> {
48-
static readonly COMMON_ATTRIBUTES = {
49-
[ATTR_DB_SYSTEM]: DB_SYSTEM_VALUE_MYSQL,
50-
};
55+
private _netSemconvStability!: SemconvStability;
56+
private _dbSemconvStability!: SemconvStability;
5157

5258
constructor(config: MySQL2InstrumentationConfig = {}) {
5359
super(PACKAGE_NAME, PACKAGE_VERSION, config);
60+
this._setSemconvStabilityFromEnv();
61+
}
62+
63+
// Used for testing.
64+
private _setSemconvStabilityFromEnv() {
65+
this._netSemconvStability = semconvStabilityFromStr(
66+
'http',
67+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN
68+
);
69+
this._dbSemconvStability = semconvStabilityFromStr(
70+
'database',
71+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN
72+
);
5473
}
5574

5675
protected init() {
@@ -139,19 +158,31 @@ export class MySQL2Instrumentation extends InstrumentationBase<MySQL2Instrumenta
139158
}
140159
const { maskStatement, maskStatementHook, responseHook } =
141160
thisPlugin.getConfig();
161+
162+
const attributes: api.Attributes = getConnectionAttributes(
163+
this.config,
164+
thisPlugin._dbSemconvStability,
165+
thisPlugin._netSemconvStability
166+
);
167+
const dbQueryText = getQueryText(
168+
query,
169+
format,
170+
values,
171+
maskStatement,
172+
maskStatementHook
173+
);
174+
if (thisPlugin._dbSemconvStability & SemconvStability.OLD) {
175+
attributes[ATTR_DB_SYSTEM] = DB_SYSTEM_VALUE_MYSQL;
176+
attributes[ATTR_DB_STATEMENT] = dbQueryText;
177+
}
178+
if (thisPlugin._dbSemconvStability & SemconvStability.STABLE) {
179+
attributes[ATTR_DB_SYSTEM_NAME] = DB_SYSTEM_NAME_VALUE_MYSQL;
180+
attributes[ATTR_DB_QUERY_TEXT] = dbQueryText;
181+
}
182+
142183
const span = thisPlugin.tracer.startSpan(getSpanName(query), {
143184
kind: api.SpanKind.CLIENT,
144-
attributes: {
145-
...MySQL2Instrumentation.COMMON_ATTRIBUTES,
146-
...getConnectionAttributes(this.config),
147-
[ATTR_DB_STATEMENT]: getDbStatement(
148-
query,
149-
format,
150-
values,
151-
maskStatement,
152-
maskStatementHook
153-
),
154-
},
185+
attributes,
155186
});
156187

157188
if (

packages/instrumentation-mysql2/src/utils.ts

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ import {
2424
} from './semconv';
2525
import type * as mysqlTypes from 'mysql2';
2626
import { MySQL2InstrumentationQueryMaskingHook } from './types';
27+
import { SemconvStability } from '@opentelemetry/instrumentation';
28+
import {
29+
ATTR_DB_NAMESPACE,
30+
ATTR_SERVER_ADDRESS,
31+
ATTR_SERVER_PORT,
32+
} from '@opentelemetry/semantic-conventions';
2733

2834
type formatType = typeof mysqlTypes.format;
2935

@@ -51,29 +57,44 @@ interface Config {
5157
user?: string;
5258
connectionConfig?: Config;
5359
}
60+
5461
/**
5562
* Get an Attributes map from a mysql connection config object
5663
*
5764
* @param config ConnectionConfig
5865
*/
59-
export function getConnectionAttributes(config: Config): Attributes {
66+
export function getConnectionAttributes(
67+
config: Config,
68+
dbSemconvStability: SemconvStability,
69+
netSemconvStability: SemconvStability
70+
): Attributes {
6071
const { host, port, database, user } = getConfig(config);
72+
73+
const attrs: Attributes = {};
74+
if (dbSemconvStability & SemconvStability.OLD) {
75+
attrs[ATTR_DB_CONNECTION_STRING] = getJDBCString(host, port, database);
76+
attrs[ATTR_DB_NAME] = database;
77+
attrs[ATTR_DB_USER] = user;
78+
}
79+
if (dbSemconvStability & SemconvStability.STABLE) {
80+
attrs[ATTR_DB_NAMESPACE] = database;
81+
}
82+
6183
const portNumber = parseInt(port, 10);
62-
if (!isNaN(portNumber)) {
63-
return {
64-
[ATTR_NET_PEER_NAME]: host,
65-
[ATTR_NET_PEER_PORT]: portNumber,
66-
[ATTR_DB_CONNECTION_STRING]: getJDBCString(host, port, database),
67-
[ATTR_DB_NAME]: database,
68-
[ATTR_DB_USER]: user,
69-
};
84+
if (netSemconvStability & SemconvStability.OLD) {
85+
attrs[ATTR_NET_PEER_NAME] = host;
86+
if (!isNaN(portNumber)) {
87+
attrs[ATTR_NET_PEER_PORT] = portNumber;
88+
}
7089
}
71-
return {
72-
[ATTR_NET_PEER_NAME]: host,
73-
[ATTR_DB_CONNECTION_STRING]: getJDBCString(host, port, database),
74-
[ATTR_DB_NAME]: database,
75-
[ATTR_DB_USER]: user,
76-
};
90+
if (netSemconvStability & SemconvStability.STABLE) {
91+
attrs[ATTR_SERVER_ADDRESS] = host;
92+
if (!isNaN(portNumber)) {
93+
attrs[ATTR_SERVER_PORT] = portNumber;
94+
}
95+
}
96+
97+
return attrs;
7798
}
7899

79100
function getConfig(config: any) {
@@ -101,11 +122,9 @@ function getJDBCString(
101122
}
102123

103124
/**
104-
* Conjures up the value for the db.statement attribute by formatting a SQL query.
105-
*
106-
* @returns the database statement being executed.
125+
* Conjures up the value for the db.query.text attribute by formatting a SQL query.
107126
*/
108-
export function getDbStatement(
127+
export function getQueryText(
109128
query: string | Query | QueryOptions,
110129
format?: formatType,
111130
values?: any[],

packages/instrumentation-mysql2/test/mysql.test.ts

Lines changed: 98 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import {
3636
import * as assert from 'assert';
3737
import { MySQL2Instrumentation, MySQL2InstrumentationConfig } from '../src';
3838

39+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN = 'http/dup,database/dup';
40+
3941
const LIB_VERSION = testUtils.getPackageVersion('mysql2');
4042
const port = Number(process.env.MYSQL_PORT) || 33306;
4143
const database = process.env.MYSQL_DATABASE || 'test_db';
@@ -59,6 +61,14 @@ import type {
5961
Connection as ConnectionAsync,
6062
createConnection as createConnectionAsync,
6163
} from 'mysql2/promise';
64+
import {
65+
ATTR_DB_NAMESPACE,
66+
ATTR_DB_QUERY_TEXT,
67+
ATTR_DB_SYSTEM_NAME,
68+
ATTR_SERVER_ADDRESS,
69+
ATTR_SERVER_PORT,
70+
DB_SYSTEM_NAME_VALUE_MYSQL,
71+
} from '@opentelemetry/semantic-conventions';
6272

6373
interface Result extends RowDataPacket {
6474
solution: number;
@@ -435,6 +445,80 @@ describe('mysql2', () => {
435445
});
436446
});
437447

448+
describe('various values of OTEL_SEMCONV_STABILITY_OPT_IN', () => {
449+
// Restore OTEL_SEMCONV_STABILITY_OPT_IN after we are done.
450+
const _origOptInEnv = process.env.OTEL_SEMCONV_STABILITY_OPT_IN;
451+
after(() => {
452+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN = _origOptInEnv;
453+
(instrumentation as any)._setSemconvStabilityFromEnv();
454+
});
455+
456+
const sql = 'SELECT 1+1 as solution';
457+
const queryAndGetSpan = (): Promise<ReadableSpan> => {
458+
return new Promise(resolve => {
459+
const span = provider.getTracer('default').startSpan('test span');
460+
context.with(trace.setSpan(context.active(), span), () => {
461+
connection.query(sql, (err, res: RowDataPacket[]) => {
462+
const spans = memoryExporter.getFinishedSpans();
463+
assert.strictEqual(spans.length, 1);
464+
resolve(spans[0]);
465+
});
466+
});
467+
});
468+
};
469+
470+
it('OTEL_SEMCONV_STABILITY_OPT_IN=(empty)', async () => {
471+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN = '';
472+
(instrumentation as any)._setSemconvStabilityFromEnv();
473+
474+
const { attributes } = await queryAndGetSpan();
475+
476+
// old `db.*`
477+
assert.strictEqual(attributes[ATTR_DB_SYSTEM], DB_SYSTEM_VALUE_MYSQL);
478+
assert.strictEqual(attributes[ATTR_DB_NAME], database);
479+
assert.strictEqual(attributes[ATTR_DB_USER], user);
480+
assert.strictEqual(attributes[ATTR_DB_STATEMENT], format(sql));
481+
// stable `db.*`
482+
assert.strictEqual(attributes[ATTR_DB_SYSTEM_NAME], undefined);
483+
assert.strictEqual(attributes[ATTR_DB_NAMESPACE], undefined);
484+
assert.strictEqual(attributes[ATTR_DB_QUERY_TEXT], undefined);
485+
486+
// old `net.*`
487+
assert.strictEqual(attributes[ATTR_NET_PEER_NAME], host);
488+
assert.strictEqual(attributes[ATTR_NET_PEER_PORT], port);
489+
// stable `net.*`
490+
assert.strictEqual(attributes[ATTR_SERVER_ADDRESS], undefined);
491+
assert.strictEqual(attributes[ATTR_SERVER_PORT], undefined);
492+
});
493+
494+
it('OTEL_SEMCONV_STABILITY_OPT_IN=http,database', async () => {
495+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN = 'http,database';
496+
(instrumentation as any)._setSemconvStabilityFromEnv();
497+
498+
const { attributes } = await queryAndGetSpan();
499+
500+
// old `db.*`
501+
assert.strictEqual(attributes[ATTR_DB_SYSTEM], undefined);
502+
assert.strictEqual(attributes[ATTR_DB_STATEMENT], undefined);
503+
assert.strictEqual(attributes[ATTR_DB_NAME], undefined);
504+
assert.strictEqual(attributes[ATTR_DB_USER], undefined);
505+
// stable `db.*`
506+
assert.strictEqual(
507+
attributes[ATTR_DB_SYSTEM_NAME],
508+
DB_SYSTEM_NAME_VALUE_MYSQL
509+
);
510+
assert.strictEqual(attributes[ATTR_DB_NAMESPACE], database);
511+
assert.strictEqual(attributes[ATTR_DB_QUERY_TEXT], format(sql));
512+
513+
// old `net.*`
514+
assert.strictEqual(attributes[ATTR_NET_PEER_NAME], undefined);
515+
assert.strictEqual(attributes[ATTR_NET_PEER_PORT], undefined);
516+
// stable `net.*`
517+
assert.strictEqual(attributes[ATTR_SERVER_ADDRESS], host);
518+
assert.strictEqual(attributes[ATTR_SERVER_PORT], port);
519+
});
520+
});
521+
438522
describe('#Connection.execute', () => {
439523
it('should intercept connection.execute(text: string)', done => {
440524
const span = provider.getTracer('default').startSpan('test span');
@@ -1568,12 +1652,24 @@ function assertSpan(
15681652
values?: any,
15691653
errorMessage?: string
15701654
) {
1655+
// Assert both old and stable `db.*` semconv.
15711656
assert.strictEqual(span.attributes[ATTR_DB_SYSTEM], DB_SYSTEM_VALUE_MYSQL);
15721657
assert.strictEqual(span.attributes[ATTR_DB_NAME], database);
1573-
assert.strictEqual(span.attributes[ATTR_NET_PEER_PORT], port);
1574-
assert.strictEqual(span.attributes[ATTR_NET_PEER_NAME], host);
15751658
assert.strictEqual(span.attributes[ATTR_DB_USER], user);
15761659
assert.strictEqual(span.attributes[ATTR_DB_STATEMENT], format(sql, values));
1660+
assert.strictEqual(
1661+
span.attributes[ATTR_DB_SYSTEM_NAME],
1662+
DB_SYSTEM_NAME_VALUE_MYSQL
1663+
);
1664+
assert.strictEqual(span.attributes[ATTR_DB_NAMESPACE], database);
1665+
assert.strictEqual(span.attributes[ATTR_DB_QUERY_TEXT], format(sql, values));
1666+
1667+
// Assert both old and stable `net.*` semconv.
1668+
assert.strictEqual(span.attributes[ATTR_NET_PEER_NAME], host);
1669+
assert.strictEqual(span.attributes[ATTR_NET_PEER_PORT], port);
1670+
assert.strictEqual(span.attributes[ATTR_SERVER_ADDRESS], host);
1671+
assert.strictEqual(span.attributes[ATTR_SERVER_PORT], port);
1672+
15771673
if (errorMessage) {
15781674
assert.strictEqual(span.status.message, errorMessage);
15791675
assert.strictEqual(span.status.code, SpanStatusCode.ERROR);

0 commit comments

Comments
 (0)