Skip to content

Commit 7130d22

Browse files
committed
feat(redis): support migration between legacy and stable semantic conventions
1 parent 441a3ac commit 7130d22

File tree

6 files changed

+309
-82
lines changed

6 files changed

+309
-82
lines changed

packages/instrumentation-redis/README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,23 +73,54 @@ const redisInstrumentation = new RedisInstrumentation({
7373
});
7474
```
7575

76+
See [examples/redis](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/examples/redis) for a short example.
77+
78+
## Semantic Conventions Stability
79+
80+
To select which semconv version(s) is emitted from this instrumentation, use the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable.
81+
82+
- `database` - Emit only the stable database conventions.
83+
- `database/dup` - Emit both the old and stable database conventions.
84+
- By default, if `OTEL_SEMCONV_STABILITY_OPT_IN` includes neither of the above tokens, the old v1.27.0 semconv is used.
85+
86+
The migration guide for the database conventions can be found [here](https://opentelemetry.io/docs/specs/semconv/non-normative/database-migration/).
87+
88+
### Migrating to stable database semantic conventions
89+
90+
1. Update your telemetry infrastructure to support the new stable database attributes
91+
2. Set `OTEL_SEMCONV_STABILITY_OPT_IN=database/dup` to emit both old and new semantic conventions
92+
3. Update your applications and infrastructure to use the new attributes
93+
4. Set `OTEL_SEMCONV_STABILITY_OPT_IN=database` to emit only the new semantic conventions
94+
```
95+
7696
## Semantic Conventions
7797
78-
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)
98+
This package supports both old semantic conventions (v1.27.0 and prior) and the stable v1.33.0 semantic conventions.
99+
By default, old semantic conventions are used. Use the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable to control which version to emit.
79100
80101
Attributes collected:
81102
103+
### Old Semantic Conventions (default)
104+
82105
| Attribute | Short Description |
83106
|------------------------|--------------------------------------------------------------|
84107
| `db.connection_string` | URL to Redis server address, of the form `redis://host:port` |
108+
| `db.statement` | Executed Redis statement |
109+
| `db.system` | Database identifier; always `redis` |
110+
| `net.peer.name` | Hostname or IP of the connected Redis server |
111+
| `net.peer.port` | Port of the connected Redis server |
112+
113+
### Stable Semantic Conventions (v1.33.0)
114+
115+
| Attribute | Short Description |
116+
|------------------------|--------------------------------------------------------------|
85117
| `db.operation.name` | Redis command name |
86118
| `db.operation.batch.size` | Number of commands in a Redis `MULTI/EXEC` transaction |
87119
| `db.query.text` | The database query being executed |
88120
| `db.system.name` | Database identifier; always `redis` |
89121
| `server.address` | Hostname or IP of the connected Redis server |
90122
| `server.port` | Port of the connected Redis server |
91123
92-
93124
## Useful links
94125
95126
- For more information on OpenTelemetry, visit: <https://opentelemetry.io/>

packages/instrumentation-redis/src/v2-v3/instrumentation.ts

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
InstrumentationBase,
2020
InstrumentationNodeModuleDefinition,
2121
safeExecuteInTheMiddle,
22+
SemconvStability,
23+
semconvStabilityFromStr,
2224
} from '@opentelemetry/instrumentation';
2325
import {
2426
endSpan,
@@ -31,6 +33,12 @@ import { PACKAGE_NAME, PACKAGE_VERSION } from '../version';
3133
import type { RedisCommand, RedisPluginClientTypes } from './internal-types';
3234
import { SpanKind, context, trace } from '@opentelemetry/api';
3335
import {
36+
DBSYSTEMVALUES_REDIS,
37+
SEMATTRS_DB_CONNECTION_STRING,
38+
SEMATTRS_DB_STATEMENT,
39+
SEMATTRS_DB_SYSTEM,
40+
SEMATTRS_NET_PEER_NAME,
41+
SEMATTRS_NET_PEER_PORT,
3442
ATTR_DB_SYSTEM_NAME,
3543
ATTR_DB_QUERY_TEXT,
3644
ATTR_DB_OPERATION_NAME,
@@ -41,9 +49,14 @@ import { defaultDbStatementSerializer } from '@opentelemetry/redis-common';
4149

4250
export class RedisInstrumentationV2_V3 extends InstrumentationBase<RedisInstrumentationConfig> {
4351
static readonly COMPONENT = 'redis';
52+
private _semconvStability: SemconvStability;
4453

4554
constructor(config: RedisInstrumentationConfig = {}) {
4655
super(PACKAGE_NAME, PACKAGE_VERSION, config);
56+
this._semconvStability = semconvStabilityFromStr(
57+
'database',
58+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN
59+
);
4760
}
4861

4962
protected init() {
@@ -126,27 +139,64 @@ export class RedisInstrumentationV2_V3 extends InstrumentationBase<RedisInstrume
126139

127140
const dbStatementSerializer =
128141
config?.dbStatementSerializer || defaultDbStatementSerializer;
142+
143+
const attributes: { [key: string]: any } = {};
144+
145+
if (instrumentation._semconvStability & SemconvStability.OLD) {
146+
Object.assign(attributes, {
147+
[SEMATTRS_DB_SYSTEM]: DBSYSTEMVALUES_REDIS,
148+
[SEMATTRS_DB_STATEMENT]: dbStatementSerializer(
149+
cmd.command,
150+
cmd.args
151+
),
152+
});
153+
}
154+
155+
if (instrumentation._semconvStability & SemconvStability.STABLE) {
156+
Object.assign(attributes, {
157+
[ATTR_DB_SYSTEM_NAME]: 'redis',
158+
[ATTR_DB_OPERATION_NAME]: cmd.command,
159+
[ATTR_DB_QUERY_TEXT]: dbStatementSerializer(cmd.command, cmd.args),
160+
});
161+
}
162+
129163
const span = instrumentation.tracer.startSpan(
130164
`${RedisInstrumentationV2_V3.COMPONENT}-${cmd.command}`,
131165
{
132166
kind: SpanKind.CLIENT,
133-
attributes: {
134-
[ATTR_DB_SYSTEM_NAME]: 'redis',
135-
[ATTR_DB_OPERATION_NAME]: cmd.command,
136-
[ATTR_DB_QUERY_TEXT]: dbStatementSerializer(
137-
cmd.command,
138-
cmd.args
139-
),
140-
},
167+
attributes,
141168
}
142169
);
143170

144171
// Set attributes for not explicitly typed RedisPluginClientTypes
145172
if (this.connection_options) {
146-
span.setAttributes({
147-
[ATTR_SERVER_ADDRESS]: this.connection_options.host,
148-
[ATTR_SERVER_PORT]: this.connection_options.port,
149-
});
173+
const connectionAttributes: { [key: string]: any } = {};
174+
175+
if (instrumentation._semconvStability & SemconvStability.OLD) {
176+
Object.assign(connectionAttributes, {
177+
[SEMATTRS_NET_PEER_NAME]: this.connection_options.host,
178+
[SEMATTRS_NET_PEER_PORT]: this.connection_options.port,
179+
});
180+
}
181+
182+
if (instrumentation._semconvStability & SemconvStability.STABLE) {
183+
Object.assign(connectionAttributes, {
184+
[ATTR_SERVER_ADDRESS]: this.connection_options.host,
185+
[ATTR_SERVER_PORT]: this.connection_options.port,
186+
});
187+
}
188+
189+
span.setAttributes(connectionAttributes);
190+
}
191+
192+
if (
193+
this.address &&
194+
instrumentation._semconvStability & SemconvStability.OLD
195+
) {
196+
span.setAttribute(
197+
SEMATTRS_DB_CONNECTION_STRING,
198+
`redis://${this.address}`
199+
);
150200
}
151201

152202
const originalCallback = arguments[0].callback;

packages/instrumentation-redis/src/v4-v5/instrumentation.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
InstrumentationBase,
2727
InstrumentationNodeModuleDefinition,
2828
InstrumentationNodeModuleFile,
29+
SemconvStability,
30+
semconvStabilityFromStr,
2931
} from '@opentelemetry/instrumentation';
3032
import { getClientAttributes } from './utils';
3133
import { defaultDbStatementSerializer } from '@opentelemetry/redis-common';
@@ -36,6 +38,7 @@ import {
3638
ATTR_DB_OPERATION_NAME,
3739
ATTR_DB_QUERY_TEXT,
3840
ATTR_DB_OPERATION_BATCH_SIZE,
41+
SEMATTRS_DB_STATEMENT,
3942
} from '@opentelemetry/semantic-conventions';
4043
import type { MultiErrorReply } from './internal-types';
4144

@@ -54,9 +57,14 @@ interface MutliCommandInfo {
5457

5558
export class RedisInstrumentationV4_V5 extends InstrumentationBase<RedisInstrumentationConfig> {
5659
static readonly COMPONENT = 'redis';
60+
private _semconvStability: SemconvStability;
5761

5862
constructor(config: RedisInstrumentationConfig = {}) {
5963
super(PACKAGE_NAME, PACKAGE_VERSION, config);
64+
this._semconvStability = semconvStabilityFromStr(
65+
'database',
66+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN
67+
);
6068
}
6169

6270
protected init() {
@@ -331,7 +339,11 @@ export class RedisInstrumentationV4_V5 extends InstrumentationBase<RedisInstrume
331339
return function connectWrapper(original: Function) {
332340
return function patchedConnect(this: any): Promise<void> {
333341
const options = this.options;
334-
const attributes = getClientAttributes(options);
342+
const attributes = getClientAttributes(
343+
plugin._diag,
344+
options,
345+
plugin._semconvStability
346+
);
335347

336348
const span = plugin.tracer.startSpan(
337349
`${RedisInstrumentationV4_V5.COMPONENT}-connect`,
@@ -382,13 +394,24 @@ export class RedisInstrumentationV4_V5 extends InstrumentationBase<RedisInstrume
382394
const dbStatementSerializer =
383395
this.getConfig().dbStatementSerializer || defaultDbStatementSerializer;
384396

385-
const attributes = getClientAttributes(clientOptions);
397+
const attributes = getClientAttributes(
398+
this._diag,
399+
clientOptions,
400+
this._semconvStability
401+
);
386402

387403
try {
388-
attributes[ATTR_DB_OPERATION_NAME] = commandName;
404+
if (this._semconvStability & SemconvStability.STABLE) {
405+
attributes[ATTR_DB_OPERATION_NAME] = commandName;
406+
}
389407
const dbStatement = dbStatementSerializer(commandName, commandArgs);
390408
if (dbStatement != null) {
391-
attributes[ATTR_DB_QUERY_TEXT] = dbStatement;
409+
if (this._semconvStability & SemconvStability.OLD) {
410+
attributes[SEMATTRS_DB_STATEMENT] = dbStatement;
411+
}
412+
if (this._semconvStability & SemconvStability.STABLE) {
413+
attributes[ATTR_DB_QUERY_TEXT] = dbStatement;
414+
}
392415
}
393416
} catch (e) {
394417
this._diag.error('dbStatementSerializer throw an exception', e, {
@@ -454,7 +477,10 @@ export class RedisInstrumentationV4_V5 extends InstrumentationBase<RedisInstrume
454477

455478
for (let i = 0; i < openSpans.length; i++) {
456479
const { span, commandName, commandArgs } = openSpans[i];
457-
if (openSpans.length >= 2) {
480+
if (
481+
openSpans.length >= 2 &&
482+
this._semconvStability & SemconvStability.STABLE
483+
) {
458484
span.setAttribute(ATTR_DB_OPERATION_BATCH_SIZE, openSpans.length);
459485
}
460486
const currCommandRes = replies[i];

packages/instrumentation-redis/src/v4-v5/utils.ts

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,70 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { Attributes } from '@opentelemetry/api';
16+
import { Attributes, DiagLogger } from '@opentelemetry/api';
1717
import {
18+
SEMATTRS_DB_SYSTEM,
19+
SEMATTRS_DB_CONNECTION_STRING,
20+
SEMATTRS_NET_PEER_NAME,
21+
SEMATTRS_NET_PEER_PORT,
22+
DBSYSTEMVALUES_REDIS,
1823
ATTR_DB_SYSTEM_NAME,
1924
ATTR_SERVER_ADDRESS,
2025
ATTR_SERVER_PORT,
2126
} from '@opentelemetry/semantic-conventions';
27+
import { SemconvStability } from '@opentelemetry/instrumentation';
2228

23-
export function getClientAttributes(options: any): Attributes {
24-
const attrs: Attributes = {
25-
[ATTR_DB_SYSTEM_NAME]: 'redis',
26-
[ATTR_SERVER_ADDRESS]: options?.socket?.host,
27-
[ATTR_SERVER_PORT]: options?.socket?.port,
28-
};
29+
export function getClientAttributes(
30+
diag: DiagLogger,
31+
options: any,
32+
semconvStability: SemconvStability
33+
): Attributes {
34+
const attributes: Attributes = {};
2935

30-
return attrs;
36+
if (semconvStability & SemconvStability.OLD) {
37+
Object.assign(attributes, {
38+
[SEMATTRS_DB_SYSTEM]: DBSYSTEMVALUES_REDIS,
39+
[SEMATTRS_NET_PEER_NAME]: options?.socket?.host,
40+
[SEMATTRS_NET_PEER_PORT]: options?.socket?.port,
41+
[SEMATTRS_DB_CONNECTION_STRING]:
42+
removeCredentialsFromDBConnectionStringAttribute(diag, options?.url),
43+
});
44+
}
45+
46+
if (semconvStability & SemconvStability.STABLE) {
47+
Object.assign(attributes, {
48+
[ATTR_DB_SYSTEM_NAME]: 'redis',
49+
[ATTR_SERVER_ADDRESS]: options?.socket?.host,
50+
[ATTR_SERVER_PORT]: options?.socket?.port,
51+
});
52+
}
53+
54+
return attributes;
55+
}
56+
57+
/**
58+
* removeCredentialsFromDBConnectionStringAttribute removes basic auth from url and user_pwd from query string
59+
*
60+
* Examples:
61+
* redis://user:pass@localhost:6379/mydb => redis://localhost:6379/mydb
62+
* redis://localhost:6379?db=mydb&user_pwd=pass => redis://localhost:6379?db=mydb
63+
*/
64+
function removeCredentialsFromDBConnectionStringAttribute(
65+
diag: DiagLogger,
66+
url?: unknown
67+
): string | undefined {
68+
if (typeof url !== 'string' || !url) {
69+
return;
70+
}
71+
72+
try {
73+
const u = new URL(url);
74+
u.searchParams.delete('user_pwd');
75+
u.username = '';
76+
u.password = '';
77+
return u.href;
78+
} catch (err) {
79+
diag.error('failed to sanitize redis connection url', err);
80+
}
81+
return;
3182
}

0 commit comments

Comments
 (0)