Skip to content

Commit 4fa33f0

Browse files
committed
fix(instrumentation-mysql2): by default do not include parameterized values to db.statement span attribute (#1758)
* feat: add configuration to enable inclusion of parameterized values in db.statement span attribute * feat: add configuration to set max length for db.statement span attribute * fix: replace deprecated SpanAttributes with Attributes
1 parent 34d2dc4 commit 4fa33f0

File tree

6 files changed

+315
-38
lines changed

6 files changed

+315
-38
lines changed

plugins/node/opentelemetry-instrumentation-mysql2/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,12 @@ registerInstrumentations({
4444

4545
You can set the following instrumentation options:
4646

47-
| Options | Type | Description |
48-
| ------- | ---- | ----------- |
47+
| Options | Type | Description |
48+
| ------- | ---- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
4949
| `responseHook` | `MySQL2InstrumentationExecutionResponseHook` (function) | Function for adding custom attributes from db response |
5050
| `addSqlCommenterCommentToQueries` | `boolean` | If true, adds [sqlcommenter](https://github.com/open-telemetry/opentelemetry-sqlcommenter) specification compliant comment to queries with tracing context (default false). _NOTE: A comment will not be added to queries that already contain `--` or `/* ... */` in them, even if these are not actually part of comments_ |
51+
| `includeValuesInDbStatement` | `boolean` | If true, adds parameterized values to `db.statement` Span attribute (default false) |
52+
| `dbStatementMaxLength` | `integer` | If set, truncates the value of `db.statement` Span attribute to the configured length (default unlimited) |
5153

5254
## Useful links
5355

plugins/node/opentelemetry-instrumentation-mysql2/src/instrumentation.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,17 @@ import { VERSION } from './version';
3838

3939
type formatType = typeof mysqlTypes.format;
4040

41-
export class MySQL2Instrumentation extends InstrumentationBase<any> {
41+
export class MySQL2Instrumentation extends InstrumentationBase {
4242
static readonly COMMON_ATTRIBUTES = {
4343
[SemanticAttributes.DB_SYSTEM]: DbSystemValues.MYSQL,
4444
};
4545

46-
constructor(config?: MySQL2InstrumentationConfig) {
47-
super('@opentelemetry/instrumentation-mysql2', VERSION, config);
46+
constructor(protected override _config: MySQL2InstrumentationConfig = {}) {
47+
super('@opentelemetry/instrumentation-mysql2', VERSION, _config);
48+
}
49+
50+
override setConfig(config: MySQL2InstrumentationConfig = {}) {
51+
this._config = config;
4852
}
4953

5054
protected init() {
@@ -100,9 +104,6 @@ export class MySQL2Instrumentation extends InstrumentationBase<any> {
100104
_valuesOrCallback?: unknown[] | Function,
101105
_callback?: Function
102106
) {
103-
const thisPluginConfig: MySQL2InstrumentationConfig =
104-
thisPlugin._config;
105-
106107
let values;
107108
if (Array.isArray(_valuesOrCallback)) {
108109
values = _valuesOrCallback;
@@ -117,13 +118,16 @@ export class MySQL2Instrumentation extends InstrumentationBase<any> {
117118
...getConnectionAttributes(this.config),
118119
[SemanticAttributes.DB_STATEMENT]: getDbStatement(
119120
query,
120-
format,
121-
values
121+
values,
122+
thisPlugin._config.includeValuesInDbStatement
123+
? format
124+
: undefined,
125+
thisPlugin._config.dbStatementMaxLength
122126
),
123127
},
124128
});
125129

126-
if (!isPrepared && thisPluginConfig.addSqlCommenterCommentToQueries) {
130+
if (!isPrepared && thisPlugin._config.addSqlCommenterCommentToQueries) {
127131
arguments[0] = query =
128132
typeof query === 'string'
129133
? addSqlCommenterComment(span, query)
@@ -139,10 +143,10 @@ export class MySQL2Instrumentation extends InstrumentationBase<any> {
139143
message: err.message,
140144
});
141145
} else {
142-
if (typeof thisPluginConfig.responseHook === 'function') {
146+
if (typeof thisPlugin._config.responseHook === 'function') {
143147
safeExecuteInTheMiddle(
144148
() => {
145-
thisPluginConfig.responseHook!(span, {
149+
thisPlugin._config.responseHook!(span, {
146150
queryResults: results,
147151
});
148152
},
@@ -155,7 +159,6 @@ export class MySQL2Instrumentation extends InstrumentationBase<any> {
155159
);
156160
}
157161
}
158-
159162
span.end();
160163
});
161164

plugins/node/opentelemetry-instrumentation-mysql2/src/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,14 @@ export interface MySQL2InstrumentationConfig extends InstrumentationConfig {
3939
* the tracing context, following the {@link https://github.com/open-telemetry/opentelemetry-sqlcommenter sqlcommenter} format
4040
*/
4141
addSqlCommenterCommentToQueries?: boolean;
42+
43+
/**
44+
* If true, queries are modified to include values
45+
*/
46+
includeValuesInDbStatement?: boolean;
47+
48+
/**
49+
* If set, truncate the query to the specified length
50+
*/
51+
dbStatementMaxLength?: number;
4252
}

plugins/node/opentelemetry-instrumentation-mysql2/src/utils.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { SpanAttributes } from '@opentelemetry/api';
17+
import { Attributes } from '@opentelemetry/api';
1818
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
1919

2020
/*
2121
Following types declare an expectation on mysql2 types and define a subset we
22-
use in the instrumentation of the types actually defined in mysql2 pacakge
22+
use in the instrumentation of the types actually defined in mysql2 package
2323
2424
We need to import them here so that the installing party of the instrumentation
2525
doesn't have to absolutely install the mysql2 package as well - specially
26-
important for auto-loaders and meta-pacakges.
26+
important for auto-loaders and meta-packages.
2727
*/
2828
interface QueryOptions {
2929
sql: string;
@@ -41,12 +41,13 @@ interface Config {
4141
user?: string;
4242
connectionConfig?: Config;
4343
}
44+
4445
/**
4546
* Get an SpanAttributes map from a mysql connection config object
4647
*
4748
* @param config ConnectionConfig
4849
*/
49-
export function getConnectionAttributes(config: Config): SpanAttributes {
50+
export function getConnectionAttributes(config: Config): Attributes {
5051
const { host, port, database, user } = getConfig(config);
5152

5253
return {
@@ -93,23 +94,31 @@ function getJDBCString(
9394
*/
9495
export function getDbStatement(
9596
query: string | Query | QueryOptions,
96-
format: (
97+
values?: any[],
98+
format?: (
9799
sql: string,
98100
values: any[],
99101
stringifyObjects?: boolean,
100102
timeZone?: string
101103
) => string,
102-
values?: any[]
104+
statementLimit?: number
103105
): string {
106+
let statement = '';
104107
if (typeof query === 'string') {
105-
return values ? format(query, values) : query;
108+
statement = format ? (values ? format(query, values) : query) : query;
106109
} else {
107110
// According to https://github.com/mysqljs/mysql#performing-queries
108111
// The values argument will override the values in the option object.
109-
return values || (query as QueryOptions).values
110-
? format(query.sql, values || (query as QueryOptions).values)
112+
statement = format
113+
? values || (query as QueryOptions).values
114+
? format(query.sql, values || (query as QueryOptions).values)
115+
: query.sql
111116
: query.sql;
112117
}
118+
if (statementLimit) {
119+
return statement.substring(0, statementLimit) + '[...]';
120+
}
121+
return statement;
113122
}
114123

115124
/**

plugins/node/opentelemetry-instrumentation-mysql2/test/mysql.test.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ const user = process.env.MYSQL_USER || 'otel';
3939
const password = process.env.MYSQL_PASSWORD || 'secret';
4040
const rootPassword = process.env.MYSQL_ROOT_PASSWORD || 'rootpw';
4141

42-
const instrumentation = new MySQL2Instrumentation();
42+
const instrumentationConfig = { includeValuesInDbStatement: true };
43+
const instrumentation = new MySQL2Instrumentation(instrumentationConfig);
4344
instrumentation.enable();
4445
instrumentation.disable();
4546

@@ -49,7 +50,7 @@ interface Result extends mysqlTypes.RowDataPacket {
4950
solution: number;
5051
}
5152

52-
describe('[email protected]', () => {
53+
describe('[email protected] includeValuesInDbStatement=true (old behaviour)', () => {
5354
let contextManager: AsyncHooksContextManager;
5455
let connection: mysqlTypes.Connection;
5556
let rootConnection: mysqlTypes.Connection;
@@ -58,6 +59,9 @@ describe('[email protected]', () => {
5859
const provider = new BasicTracerProvider();
5960
const testMysql = process.env.RUN_MYSQL_TESTS; // For CI: assumes local mysql db is already available
6061
const testMysqlLocally = process.env.RUN_MYSQL_TESTS_LOCAL; // For local: spins up local mysql db via docker
62+
const testMysqlLocallyImage = process.env.RUN_MYSQL_TESTS_LOCAL_USE_MARIADB
63+
? 'mariadb'
64+
: 'mysql'; // For local: spins up mysql (default) or mariadb
6165
const shouldTest = testMysql || testMysqlLocally; // Skips these tests if false (default)
6266
const memoryExporter = new InMemorySpanExporter();
6367

@@ -87,19 +91,27 @@ describe('[email protected]', () => {
8791
this.skip();
8892
}
8993
provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter));
90-
rootConnection = mysqlTypes.createConnection({
91-
port,
92-
user: 'root',
93-
host,
94-
password: rootPassword,
95-
database,
96-
});
94+
95+
function openRootConnection() {
96+
rootConnection = mysqlTypes.createConnection({
97+
port,
98+
user: 'root',
99+
host,
100+
password: rootPassword,
101+
database,
102+
});
103+
}
104+
97105
if (testMysqlLocally) {
98-
testUtils.startDocker('mysql');
106+
testUtils.startDocker(testMysqlLocallyImage);
99107
// wait 15 seconds for docker container to start
100108
this.timeout(20000);
101-
setTimeout(done, 15000);
109+
setTimeout(() => {
110+
openRootConnection();
111+
done();
112+
}, 15000);
102113
} else {
114+
openRootConnection();
103115
done();
104116
}
105117
});
@@ -108,9 +120,13 @@ describe('[email protected]', () => {
108120
rootConnection.end(() => {
109121
if (testMysqlLocally) {
110122
this.timeout(5000);
111-
testUtils.cleanUpDocker('mysql');
123+
setTimeout(() => {
124+
testUtils.cleanUpDocker(testMysqlLocallyImage);
125+
done();
126+
}, 1000);
127+
} else {
128+
done();
112129
}
113-
done();
114130
});
115131
});
116132

@@ -149,7 +165,7 @@ describe('[email protected]', () => {
149165
afterEach(done => {
150166
context.disable();
151167
memoryExporter.reset();
152-
instrumentation.setConfig();
168+
instrumentation.setConfig(instrumentationConfig);
153169
instrumentation.disable();
154170
connection.end(() => {
155171
pool.end(() => {
@@ -1101,7 +1117,7 @@ describe('[email protected]', () => {
11011117
describe('#responseHook', () => {
11021118
const queryResultAttribute = 'query_result';
11031119

1104-
describe('invalid repsonse hook', () => {
1120+
describe('invalid response hook', () => {
11051121
beforeEach(() => {
11061122
const config: MySQL2InstrumentationConfig = {
11071123
responseHook: (span, responseHookInfo) => {

0 commit comments

Comments
 (0)