Skip to content

Commit b6a46aa

Browse files
authored
feat(instrumentations-mysql): support net.* and database semconv migration (open-telemetry#3288)
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: open-telemetry#2953
1 parent d0f9491 commit b6a46aa

File tree

8 files changed

+405
-341
lines changed

8 files changed

+405
-341
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-mysql/README.md

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,43 @@ See [examples/mysql](https://github.com/open-telemetry/opentelemetry-js-contrib/
4444

4545
### MySQL instrumentation Options
4646

47-
| Options | Type | Default | Description |
48-
| ------- | ---- | ------- | ----------- |
49-
| [`enhancedDatabaseReporting`](./src/types.ts#L24) | `boolean` | `false` | If true, an attribute containing the query's parameters will be attached the spans generated to represent the query |
50-
47+
| Options | Type | Default | Description |
48+
| ------------------------------------------------- | --------- | ------- | ----------- |
49+
| [`enhancedDatabaseReporting`](./src/types.ts#L24) | `boolean` | `false` | If true, a `db.mysql.values` attribute containing the query's parameters will be add to database spans. Note that this is not an attribute defined in [Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/database/mysql/). |
5150

5251
## Semantic Conventions
5352

54-
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)
53+
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-mysql@0.55.0` support has been added for migrating to the stable semantic conventions using the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable as follows:
54+
55+
1. Upgrade to the latest version of this instrumentation package.
56+
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.)
57+
3. Modify alerts, dashboards, metrics, and other processes in your Observability system to use the stable semantic conventions.
58+
4. Set `OTEL_SEMCONV_STABILITY_OPT_IN=http,database` to emit only the stable semantic conventions.
59+
60+
By default, if `OTEL_SEMCONV_STABILITY_OPT_IN` includes neither of the above tokens, the old v1.7.0 semconv is used.
61+
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.
62+
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.
5563

5664
Attributes collected:
5765

58-
| Attribute | Short Description |
59-
| ----------------------- | ------------------------------------------------------------------------------ |
60-
| `db.connection_string` | The connection string used to connect to the database. |
61-
| `db.name` | This attribute is used to report the name of the database being accessed. |
62-
| `db.operation` | The name of the operation being executed. |
63-
| `db.statement` | The database statement being executed. |
64-
| `db.system` | An identifier for the database management system (DBMS) product being used. |
65-
| `db.user` | Username for accessing the database. |
66-
| `net.peer.name` | Remote hostname or similar. |
67-
| `net.peer.port` | Remote port number. |
66+
| Old semconv | Stable semconv | Description |
67+
| ---------------------- | ---------------- | ----------- |
68+
| `db.system` | `db.system.name` | 'mssql' (old), 'microsoft.sql_server' (stable) |
69+
| `db.connection_string` | Removed | The connection string used to connect to the database. |
70+
| `db.statement` | `db.query.text` | The database query being executed. |
71+
| `db.user` | Removed | Username for accessing the database. |
72+
| `db.name` | Removed | Integrated into new `db.namespace`. |
73+
| (not included) | `db.namespace` | The database associated with the connection, as provided at connection time. (This does not track changes made via `SELECT DATABASE()`.) |
74+
| `net.peer.name` | `server.address` | Remote hostname or similar. |
75+
| `net.peer.port` | `server.port` | Remote port number. |
76+
77+
Metrics collected:
78+
79+
| Old semconv | Stable semconv | Description |
80+
| ----------------------------- | -------------- | ----------- |
81+
| `db.client.connections.usage` | (removed) | The number of connections currently in a given state. See note below. |
82+
83+
Note: While `db.client.connections.usage` was replaced with `db.client.connection.count` in the [semconv database migration](https://opentelemetry.io/docs/specs/semconv/non-normative/db-migration/#database-client-connection-count), the replacement metric is still unstable, so cannot be enabled via `OTEL_SEMCONV_STABILITY_OPT_IN=database`.
6884

6985
## Useful links
7086

packages/instrumentation-mysql/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
},
5757
"dependencies": {
5858
"@opentelemetry/instrumentation": "^0.208.0",
59+
"@opentelemetry/semantic-conventions": "^1.33.0",
5960
"@types/mysql": "2.15.27"
6061
},
6162
"homepage": "https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-mysql#readme"

packages/instrumentation-mysql/src/instrumentation.ts

Lines changed: 108 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,44 @@ import {
2121
Span,
2222
SpanKind,
2323
SpanStatusCode,
24+
type Attributes,
2425
} from '@opentelemetry/api';
2526
import {
2627
InstrumentationBase,
2728
InstrumentationNodeModuleDefinition,
2829
isWrapped,
30+
SemconvStability,
31+
semconvStabilityFromStr,
2932
} from '@opentelemetry/instrumentation';
3033
import {
31-
DB_SYSTEM_VALUE_MYSQL,
34+
ATTR_DB_NAMESPACE,
35+
ATTR_DB_QUERY_TEXT,
36+
ATTR_DB_SYSTEM_NAME,
37+
ATTR_SERVER_ADDRESS,
38+
ATTR_SERVER_PORT,
39+
DB_SYSTEM_NAME_VALUE_MYSQL,
40+
} from '@opentelemetry/semantic-conventions';
41+
import {
42+
ATTR_DB_CONNECTION_STRING,
43+
ATTR_DB_NAME,
3244
ATTR_DB_STATEMENT,
3345
ATTR_DB_SYSTEM,
46+
ATTR_DB_USER,
47+
ATTR_NET_PEER_NAME,
48+
ATTR_NET_PEER_PORT,
49+
DB_SYSTEM_VALUE_MYSQL,
3450
METRIC_DB_CLIENT_CONNECTIONS_USAGE,
3551
} from './semconv';
3652
import type * as mysqlTypes from 'mysql';
3753
import { AttributeNames } from './AttributeNames';
3854
import { MySQLInstrumentationConfig } from './types';
3955
import {
40-
getConnectionAttributes,
41-
getDbStatement,
56+
getConfig,
57+
getDbQueryText,
4258
getDbValues,
59+
getJDBCString,
4360
getSpanName,
44-
getPoolName,
61+
getPoolNameOld,
4562
} from './utils';
4663
/** @knipignore */
4764
import { PACKAGE_NAME, PACKAGE_VERSION } from './version';
@@ -53,24 +70,48 @@ type getConnectionCallbackType = (
5370
) => void;
5471

5572
export class MySQLInstrumentation extends InstrumentationBase<MySQLInstrumentationConfig> {
56-
static readonly COMMON_ATTRIBUTES = {
57-
[ATTR_DB_SYSTEM]: DB_SYSTEM_VALUE_MYSQL,
58-
};
59-
declare private _connectionsUsage: UpDownCounter;
73+
private _netSemconvStability!: SemconvStability;
74+
private _dbSemconvStability!: SemconvStability;
75+
declare private _connectionsUsageOld: UpDownCounter;
6076

6177
constructor(config: MySQLInstrumentationConfig = {}) {
6278
super(PACKAGE_NAME, PACKAGE_VERSION, config);
79+
this._setSemconvStabilityFromEnv();
6380
}
6481

65-
protected override _updateMetricInstruments() {
66-
this._connectionsUsage = this.meter.createUpDownCounter(
67-
METRIC_DB_CLIENT_CONNECTIONS_USAGE,
68-
{
69-
description:
70-
'The number of connections that are currently in state described by the state attribute.',
71-
unit: '{connection}',
72-
}
82+
// Used for testing.
83+
private _setSemconvStabilityFromEnv() {
84+
this._netSemconvStability = semconvStabilityFromStr(
85+
'http',
86+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN
87+
);
88+
this._dbSemconvStability = semconvStabilityFromStr(
89+
'database',
90+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN
7391
);
92+
this._updateMetricInstruments();
93+
}
94+
95+
protected override _updateMetricInstruments() {
96+
if (this._dbSemconvStability & SemconvStability.OLD) {
97+
this._connectionsUsageOld = this.meter.createUpDownCounter(
98+
METRIC_DB_CLIENT_CONNECTIONS_USAGE,
99+
{
100+
description:
101+
'The number of connections that are currently in state described by the state attribute.',
102+
unit: '{connection}',
103+
}
104+
);
105+
}
106+
}
107+
108+
/**
109+
* Convenience function for updating the `db.client.connections.usage` metric.
110+
* The name "count" comes from the eventually replacement for this metric per
111+
* https://opentelemetry.io/docs/specs/semconv/non-normative/db-migration/#database-client-connection-count
112+
*/
113+
private _connCountAdd(n: number, poolNameOld: string, state: string) {
114+
this._connectionsUsageOld?.add(n, { state, name: poolNameOld });
74115
}
75116

76117
protected init() {
@@ -154,28 +195,23 @@ export class MySQLInstrumentation extends InstrumentationBase<MySQLInstrumentati
154195
thisPlugin._patchGetConnection(pool)
155196
);
156197
thisPlugin._wrap(pool, 'end', thisPlugin._patchPoolEnd(pool));
157-
thisPlugin._setPoolcallbacks(pool, thisPlugin, '');
198+
thisPlugin._setPoolCallbacks(pool, '');
158199

159200
return pool;
160201
};
161202
};
162203
}
204+
163205
private _patchPoolEnd(pool: any) {
164206
return (originalPoolEnd: Function) => {
165207
const thisPlugin = this;
166208
return function end(callback?: unknown) {
167209
const nAll = (pool as any)._allConnections.length;
168210
const nFree = (pool as any)._freeConnections.length;
169211
const nUsed = nAll - nFree;
170-
const poolName = getPoolName(pool);
171-
thisPlugin._connectionsUsage.add(-nUsed, {
172-
state: 'used',
173-
name: poolName,
174-
});
175-
thisPlugin._connectionsUsage.add(-nFree, {
176-
state: 'idle',
177-
name: poolName,
178-
});
212+
const poolNameOld = getPoolNameOld(pool);
213+
thisPlugin._connCountAdd(-nUsed, poolNameOld, 'used');
214+
thisPlugin._connCountAdd(-nFree, poolNameOld, 'idle');
179215
originalPoolEnd.apply(pool, arguments);
180216
};
181217
};
@@ -218,7 +254,7 @@ export class MySQLInstrumentation extends InstrumentationBase<MySQLInstrumentati
218254
: String(id);
219255

220256
const pool = nodes[nodeId].pool;
221-
thisPlugin._setPoolcallbacks(pool, thisPlugin, id);
257+
thisPlugin._setPoolCallbacks(pool, id);
222258
}
223259
};
224260
};
@@ -303,16 +339,43 @@ export class MySQLInstrumentation extends InstrumentationBase<MySQLInstrumentati
303339
return originalQuery.apply(connection, arguments);
304340
}
305341

342+
const attributes: Attributes = {};
343+
const { host, port, database, user } = getConfig(connection.config);
344+
const portNumber = parseInt(port, 10);
345+
const dbQueryText = getDbQueryText(query);
346+
if (thisPlugin._dbSemconvStability & SemconvStability.OLD) {
347+
attributes[ATTR_DB_SYSTEM] = DB_SYSTEM_VALUE_MYSQL;
348+
attributes[ATTR_DB_CONNECTION_STRING] = getJDBCString(
349+
host,
350+
port,
351+
database
352+
);
353+
attributes[ATTR_DB_NAME] = database;
354+
attributes[ATTR_DB_USER] = user;
355+
attributes[ATTR_DB_STATEMENT] = dbQueryText;
356+
}
357+
if (thisPlugin._dbSemconvStability & SemconvStability.STABLE) {
358+
attributes[ATTR_DB_SYSTEM_NAME] = DB_SYSTEM_NAME_VALUE_MYSQL;
359+
attributes[ATTR_DB_NAMESPACE] = database;
360+
attributes[ATTR_DB_QUERY_TEXT] = dbQueryText;
361+
}
362+
if (thisPlugin._netSemconvStability & SemconvStability.OLD) {
363+
attributes[ATTR_NET_PEER_NAME] = host;
364+
if (!isNaN(portNumber)) {
365+
attributes[ATTR_NET_PEER_PORT] = portNumber;
366+
}
367+
}
368+
if (thisPlugin._netSemconvStability & SemconvStability.STABLE) {
369+
attributes[ATTR_SERVER_ADDRESS] = host;
370+
if (!isNaN(portNumber)) {
371+
attributes[ATTR_SERVER_PORT] = portNumber;
372+
}
373+
}
306374
const span = thisPlugin.tracer.startSpan(getSpanName(query), {
307375
kind: SpanKind.CLIENT,
308-
attributes: {
309-
...MySQLInstrumentation.COMMON_ATTRIBUTES,
310-
...getConnectionAttributes(connection.config),
311-
},
376+
attributes,
312377
});
313378

314-
span.setAttribute(ATTR_DB_STATEMENT, getDbStatement(query));
315-
316379
if (thisPlugin.getConfig().enhancedDatabaseReporting) {
317380
let values;
318381

@@ -388,41 +451,22 @@ export class MySQLInstrumentation extends InstrumentationBase<MySQLInstrumentati
388451
};
389452
};
390453
}
391-
private _setPoolcallbacks(
392-
pool: mysqlTypes.Pool,
393-
thisPlugin: MySQLInstrumentation,
394-
id: string
395-
) {
396-
//TODO:: use semantic convention
397-
const poolName = id || getPoolName(pool);
398-
399-
pool.on('connection', connection => {
400-
thisPlugin._connectionsUsage.add(1, {
401-
state: 'idle',
402-
name: poolName,
403-
});
454+
455+
private _setPoolCallbacks(pool: mysqlTypes.Pool, id: string) {
456+
const poolNameOld = id || getPoolNameOld(pool);
457+
458+
pool.on('connection', _connection => {
459+
this._connCountAdd(1, poolNameOld, 'idle');
404460
});
405461

406-
pool.on('acquire', connection => {
407-
thisPlugin._connectionsUsage.add(-1, {
408-
state: 'idle',
409-
name: poolName,
410-
});
411-
thisPlugin._connectionsUsage.add(1, {
412-
state: 'used',
413-
name: poolName,
414-
});
462+
pool.on('acquire', _connection => {
463+
this._connCountAdd(-1, poolNameOld, 'idle');
464+
this._connCountAdd(1, poolNameOld, 'used');
415465
});
416466

417-
pool.on('release', connection => {
418-
thisPlugin._connectionsUsage.add(-1, {
419-
state: 'used',
420-
name: poolName,
421-
});
422-
thisPlugin._connectionsUsage.add(1, {
423-
state: 'idle',
424-
name: poolName,
425-
});
467+
pool.on('release', _connection => {
468+
this._connCountAdd(1, poolNameOld, 'idle');
469+
this._connCountAdd(-1, poolNameOld, 'used');
426470
});
427471
}
428472
}

0 commit comments

Comments
 (0)