Skip to content

Commit f6f9e4c

Browse files
committed
feat(instrumentation-knex): Use newer semantic conventions
1 parent d579630 commit f6f9e4c

File tree

7 files changed

+218
-23
lines changed

7 files changed

+218
-23
lines changed

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

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,28 @@ registerInstrumentations({
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 package uses `@opentelemetry/semantic-conventions` version `1.27+`, which implements Semantic Convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md)
58+
59+
This package is capable of emitting both Semantic Convention [Version 1.7.0](https://github.com/open-telemetry/opentelemetry-specification/blob/v1.7.0/semantic_conventions/README.md) and [Version 1.32.0](https://github.com/open-telemetry/semantic-conventions/blob/v1.32.0/docs/database/database-metrics.md)
60+
It is controlled using the environment variable `OTEL_SEMCONV_STABILITY_OPT_IN`, which is a comma separated list of values.
61+
The values `database` and `database/dup` control this instrumentation.
62+
See details for the behavior of each of these values below.
63+
If neither `database` or `database/dup` is included in `OTEL_SEMCONV_STABILITY_OPT_IN`, the old experimental semantic conventions will be used by default.
64+
65+
### Upgrading Semantic Conventions
66+
67+
When upgrading to the new semantic conventions, it is recommended to do so in the following order:
68+
69+
1. Upgrade `@opentelemetry/instrumentation-knex` to the latest version
70+
2. Set `OTEL_SEMCONV_STABILITY_OPT_IN=database/dup` to emit both old and new semantic conventions
71+
3. Modify alerts, dashboards, metrics, and other processes to expect the new semantic conventions
72+
4. Set `OTEL_SEMCONV_STABILITY_OPT_IN=database` to emit only the new semantic conventions
73+
74+
This will cause both the old and new semantic conventions to be emitted during the transition period.
75+
76+
### ### Legacy Behavior (default)
77+
78+
Enabled when `OTEL_SEMCONV_STABILITY_OPT_IN` contains `database/dup` or DOES NOT CONTAIN `database`.
5879

5980
Attributes collected:
6081

@@ -70,6 +91,25 @@ Attributes collected:
7091
| `net.peer.port` | Remote port number. |
7192
| `net.transport` | Transport protocol used. |
7293

94+
### RC Semantic Conventions 1.32
95+
96+
Enabled when `OTEL_SEMCONV_STABILITY_OPT_IN` contains `database` OR `database/dup`.
97+
This is the recommended configuration, and will soon become the default behavior.
98+
99+
Attributes collected:
100+
101+
| Attribute | Short Description |
102+
|----------------------|-----------------------------------------------------------------------------|
103+
| `db.namespace` | This attribute is used to report the name of the database being accessed. |
104+
| `db.operation.name` | The name of the operation being executed. |
105+
| `db.collection.name` | The name of the primary table that the operation is acting upon. |
106+
| `db.query.text` | The database statement being executed. |
107+
| `db.system` | An identifier for the database management system (DBMS) product being used. |
108+
| `db.user` | Username for accessing the database. |
109+
| `server.address` | Remote hostname or similar. |
110+
| `server.port` | Remote port number. |
111+
| `network.transport` | Transport protocol used. |
112+
73113
## Useful links
74114

75115
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>

plugins/node/opentelemetry-instrumentation-knex/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
"files": [
3434
"build/src/**/*.js",
3535
"build/src/**/*.js.map",
36-
"build/src/**/*.d.ts"
36+
"build/src/**/*.d.ts",
37+
"LICENSE",
38+
"README.md"
3739
],
3840
"publishConfig": {
3941
"access": "public"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Retrieves a string from an environment variable.
19+
* - Returns `undefined` if the environment variable is empty, unset, or contains only whitespace.
20+
*
21+
* @param {string} key - The name of the environment variable to retrieve.
22+
* @returns {string | undefined} - The string value or `undefined`.
23+
*/
24+
export function getStringFromEnv(key: string): string | undefined {
25+
const raw = process.env[key];
26+
if (raw == null || raw.trim() === '') {
27+
return undefined;
28+
}
29+
return raw;
30+
}
31+
32+
/**
33+
* Retrieves a list of strings from an environment variable.
34+
* - Uses ',' as the delimiter.
35+
* - Trims leading and trailing whitespace from each entry.
36+
* - Excludes empty entries.
37+
* - Returns `undefined` if the environment variable is empty or contains only whitespace.
38+
* - Returns an empty array if all entries are empty or whitespace.
39+
*
40+
* @param {string} key - The name of the environment variable to retrieve.
41+
* @returns {string[] | undefined} - The list of strings or `undefined`.
42+
*/
43+
export function getStringListFromEnv(key: string): string[] | undefined {
44+
return getStringFromEnv(key)
45+
?.split(',')
46+
.map(v => v.trim())
47+
.filter(s => s !== '');
48+
}

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

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,14 @@ import {
2424
InstrumentationNodeModuleFile,
2525
isWrapped,
2626
} from '@opentelemetry/instrumentation';
27+
import * as utils from './utils';
28+
import { KnexInstrumentationConfig } from './types';
29+
import { SemconvStability } from './internal-types';
30+
import { getStringListFromEnv } from './env';
2731
import {
32+
ATTR_NETWORK_TRANSPORT,
33+
ATTR_SERVER_ADDRESS,
34+
ATTR_SERVER_PORT,
2835
SEMATTRS_DB_NAME,
2936
SEMATTRS_DB_OPERATION,
3037
SEMATTRS_DB_SQL_TABLE,
@@ -35,8 +42,15 @@ import {
3542
SEMATTRS_NET_PEER_PORT,
3643
SEMATTRS_NET_TRANSPORT,
3744
} from '@opentelemetry/semantic-conventions';
38-
import * as utils from './utils';
39-
import { KnexInstrumentationConfig } from './types';
45+
import {
46+
ATTR_DB_COLLECTION_NAME,
47+
ATTR_DB_NAMESPACE,
48+
ATTR_DB_OPERATION_NAME,
49+
ATTR_DB_QUERY_TEXT,
50+
ATTR_DB_SYSTEM_NAME,
51+
ATTR_DB_USER,
52+
} from './semconv';
53+
import { mapSystem } from './utils';
4054

4155
const contextSymbol = Symbol('opentelemetry.instrumentation-knex.context');
4256
const DEFAULT_CONFIG: KnexInstrumentationConfig = {
@@ -45,8 +59,21 @@ const DEFAULT_CONFIG: KnexInstrumentationConfig = {
4559
};
4660

4761
export class KnexInstrumentation extends InstrumentationBase<KnexInstrumentationConfig> {
62+
private _semconvStability: SemconvStability = SemconvStability.OLD;
63+
4864
constructor(config: KnexInstrumentationConfig = {}) {
4965
super(PACKAGE_NAME, PACKAGE_VERSION, { ...DEFAULT_CONFIG, ...config });
66+
67+
for (const entry of getStringListFromEnv('OTEL_SEMCONV_STABILITY_OPT_IN') ??
68+
[]) {
69+
if (entry.toLowerCase() === 'database/dup') {
70+
// database/dup takes highest precedence. If it is found, there is no need to read the rest of the list
71+
this._semconvStability = SemconvStability.DUPLICATE;
72+
break;
73+
} else if (entry.toLowerCase() === 'database') {
74+
this._semconvStability = SemconvStability.STABLE;
75+
}
76+
}
5077
}
5178

5279
override setConfig(config: KnexInstrumentationConfig = {}) {
@@ -122,6 +149,10 @@ export class KnexInstrumentation extends InstrumentationBase<KnexInstrumentation
122149

123150
private createQueryWrapper(moduleVersion?: string) {
124151
const instrumentation = this;
152+
153+
// We need to bind it here, because `this` is not the same in the wrapper
154+
const semConv = this._semconvStability;
155+
125156
return function wrapQuery(original: (...args: any[]) => any) {
126157
return function wrapped_logging_method(this: any, query: any) {
127158
const config = this.client.config;
@@ -134,24 +165,45 @@ export class KnexInstrumentation extends InstrumentationBase<KnexInstrumentation
134165
config?.connection?.filename || config?.connection?.database;
135166
const { maxQueryLength } = instrumentation.getConfig();
136167

137-
const attributes: api.SpanAttributes = {
168+
const attributes: api.Attributes = {
138169
'knex.version': moduleVersion,
139-
[SEMATTRS_DB_SYSTEM]: utils.mapSystem(config.client),
140-
[SEMATTRS_DB_SQL_TABLE]: table,
141-
[SEMATTRS_DB_OPERATION]: operation,
142-
[SEMATTRS_DB_USER]: config?.connection?.user,
143-
[SEMATTRS_DB_NAME]: name,
144-
[SEMATTRS_NET_PEER_NAME]: config?.connection?.host,
145-
[SEMATTRS_NET_PEER_PORT]: config?.connection?.port,
146-
[SEMATTRS_NET_TRANSPORT]:
147-
config?.connection?.filename === ':memory:' ? 'inproc' : undefined,
148170
};
171+
const transport =
172+
config?.connection?.filename === ':memory:' ? 'inproc' : undefined;
173+
174+
if ((semConv & SemconvStability.OLD) === SemconvStability.OLD) {
175+
Object.assign(attributes, {
176+
[SEMATTRS_DB_SYSTEM]: mapSystem(config.client),
177+
[SEMATTRS_DB_SQL_TABLE]: table,
178+
[SEMATTRS_DB_OPERATION]: operation,
179+
[SEMATTRS_DB_USER]: config?.connection?.user,
180+
[SEMATTRS_DB_NAME]: name,
181+
[SEMATTRS_NET_PEER_NAME]: config?.connection?.host,
182+
[SEMATTRS_NET_PEER_PORT]: config?.connection?.port,
183+
[SEMATTRS_NET_TRANSPORT]: transport,
184+
});
185+
}
186+
if ((semConv & SemconvStability.STABLE) === SemconvStability.STABLE) {
187+
Object.assign(attributes, {
188+
[ATTR_DB_SYSTEM_NAME]: mapSystem(config.client),
189+
[ATTR_DB_COLLECTION_NAME]: table,
190+
[ATTR_DB_OPERATION_NAME]: operation,
191+
[ATTR_DB_USER]: config?.connection?.user,
192+
[ATTR_DB_NAMESPACE]: name,
193+
[ATTR_SERVER_ADDRESS]: config?.connection?.host,
194+
[ATTR_SERVER_PORT]: config?.connection?.port,
195+
[ATTR_NETWORK_TRANSPORT]: transport,
196+
});
197+
}
149198
if (maxQueryLength) {
150199
// filters both undefined and 0
151-
attributes[SEMATTRS_DB_STATEMENT] = utils.limitLength(
152-
query?.sql,
153-
maxQueryLength
154-
);
200+
const queryText = utils.limitLength(query?.sql, maxQueryLength);
201+
if (semConv & SemconvStability.STABLE) {
202+
attributes[ATTR_DB_QUERY_TEXT] = queryText;
203+
}
204+
if (semConv & SemconvStability.OLD) {
205+
attributes[SEMATTRS_DB_STATEMENT] = queryText;
206+
}
155207
}
156208

157209
const parentContext =
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
export const enum SemconvStability {
17+
/** Emit only stable semantic conventions */
18+
STABLE = 0x1,
19+
/** Emit only old semantic conventions*/
20+
OLD = 0x2,
21+
/** Emit both stable and old semantic conventions*/
22+
DUPLICATE = 0x1 | 0x2,
23+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
export const ATTR_DB_NAMESPACE = 'db.namespace';
18+
export const ATTR_DB_OPERATION_NAME = 'db.operation.name';
19+
export const ATTR_DB_SYSTEM_NAME = 'db.system.name';
20+
export const ATTR_DB_COLLECTION_NAME = 'db.collection.name';
21+
export const ATTR_DB_USER = 'db.user';
22+
export const ATTR_DB_QUERY_TEXT = 'db.query.text';
23+
/**
24+
* Enum value "sqlite" for attribute {@link ATTR_DB_SYSTEM_NAME}.
25+
*/
26+
export const DB_SYSTEM_NAME_VALUE_SQLITE = 'sqlite' as const;
27+
/**
28+
* Enum value "postgresql" for attribute {@link ATTR_DB_SYSTEM_NAME}.
29+
*/
30+
export const DB_SYSTEM_NAME_VALUE_POSTGRESQL = 'postgresql' as const;

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616

1717
import { Exception } from '@opentelemetry/api';
1818
import {
19-
DBSYSTEMVALUES_SQLITE,
20-
DBSYSTEMVALUES_POSTGRESQL,
21-
} from '@opentelemetry/semantic-conventions';
19+
DB_SYSTEM_NAME_VALUE_POSTGRESQL,
20+
DB_SYSTEM_NAME_VALUE_SQLITE,
21+
} from './semconv';
2222

2323
type KnexError = Error & {
2424
code?: string;
@@ -57,8 +57,8 @@ export function otelExceptionFromKnexError(
5757
}
5858

5959
const systemMap = new Map([
60-
['sqlite3', DBSYSTEMVALUES_SQLITE],
61-
['pg', DBSYSTEMVALUES_POSTGRESQL],
60+
['sqlite3', DB_SYSTEM_NAME_VALUE_SQLITE],
61+
['pg', DB_SYSTEM_NAME_VALUE_POSTGRESQL],
6262
]);
6363

6464
export const mapSystem = (knexSystem: string) => {

0 commit comments

Comments
 (0)