Skip to content

Commit 143a61e

Browse files
authored
feat(instrumentation-nestjs-core): support migration to stable HTTP semconv (#3080)
1 parent 692cf0a commit 143a61e

File tree

3 files changed

+83
-18
lines changed

3 files changed

+83
-18
lines changed

packages/instrumentation-nestjs-core/README.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,37 @@ Attributes collected:
6262
| `nestjs.module` | Nest module class name |
6363
| `nestjs.controller` | Nest controller class name |
6464
| `nestjs.callback` | The function name of the member in the controller |
65-
| `http.method` | HTTP method |
66-
| `http.url` | Full request URL |
6765
| `http.route` | Route assigned to handler. Ex: `/users/:id` |
66+
| `http.method` / `http.request.method` | HTTP method. See "HTTP Semantic Convention migration" note below. |
67+
| `http.url` / `url.full` | Full request URL. See "HTTP Semantic Convention migration" note below. |
6868

6969
\* included in all of the spans.
7070

71+
### HTTP Semantic Convention migration
72+
73+
HTTP semantic conventions (semconv) were stabilized in v1.23.0, and a [migration process](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/non-normative/http-migration.md#http-semantic-convention-stability-migration)
74+
was defined. This instrumentations adds some minimal HTTP-related
75+
attributes on created spans. Starting with `instrumentation-nestjs-core` version
76+
0.52.0, the `OTEL_SEMCONV_STABILITY_OPT_IN` environment variable can be used to
77+
customize which HTTP semantic conventions are used for those HTTP-related
78+
attributes.
79+
80+
To select which semconv version(s) is emitted from this instrumentation, use the
81+
`OTEL_SEMCONV_STABILITY_OPT_IN` environment variable.
82+
83+
- `http`: emit the new (stable) v1.23.0+ semantics
84+
- `http/dup`: emit **both** the old v1.7.0 and the new (stable) v1.23.0+ semantics
85+
- By default, if `OTEL_SEMCONV_STABILITY_OPT_IN` includes neither of the above tokens, the old v1.7.0 semconv is used.
86+
87+
For this instrumentation, the only impacted attributes are as follows:
88+
89+
| v1.7.0 semconv | v1.23.0 semconv |
90+
| -------------- | --------------------- |
91+
| `http.method` | `http.request.method` |
92+
| `http.url` | `url.full` |
93+
94+
See the [HTTP semconv migration plan for OpenTelemetry JS instrumentations](https://github.com/open-telemetry/opentelemetry-js/issues/5646) for more details.
95+
7196
## Useful links
7297

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

packages/instrumentation-nestjs-core/src/instrumentation.ts

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@ import {
2121
InstrumentationNodeModuleDefinition,
2222
InstrumentationNodeModuleFile,
2323
isWrapped,
24+
SemconvStability,
25+
semconvStabilityFromStr,
2426
} from '@opentelemetry/instrumentation';
25-
import { ATTR_HTTP_ROUTE } from '@opentelemetry/semantic-conventions';
27+
import {
28+
ATTR_HTTP_REQUEST_METHOD,
29+
ATTR_HTTP_ROUTE,
30+
ATTR_URL_FULL,
31+
} from '@opentelemetry/semantic-conventions';
2632
import type { NestFactory } from '@nestjs/core/nest-factory.js';
2733
import type { RouterExecutionContext } from '@nestjs/core/router/router-execution-context.js';
2834
import type { Controller } from '@nestjs/common/interfaces';
@@ -39,8 +45,14 @@ export class NestInstrumentation extends InstrumentationBase {
3945
component: NestInstrumentation.COMPONENT,
4046
};
4147

48+
private _semconvStability: SemconvStability;
49+
4250
constructor(config: InstrumentationConfig = {}) {
4351
super(PACKAGE_NAME, PACKAGE_VERSION, config);
52+
this._semconvStability = semconvStabilityFromStr(
53+
'http',
54+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN
55+
);
4456
}
4557

4658
init() {
@@ -83,7 +95,11 @@ export class NestInstrumentation extends InstrumentationBase {
8395
this.ensureWrapped(
8496
RouterExecutionContext.RouterExecutionContext.prototype,
8597
'create',
86-
createWrapCreateHandler(this.tracer, moduleVersion)
98+
createWrapCreateHandler(
99+
this.tracer,
100+
moduleVersion,
101+
this._semconvStability
102+
)
87103
);
88104
return RouterExecutionContext;
89105
},
@@ -141,7 +157,11 @@ function createWrapNestFactoryCreate(
141157
};
142158
}
143159

144-
function createWrapCreateHandler(tracer: api.Tracer, moduleVersion?: string) {
160+
function createWrapCreateHandler(
161+
tracer: api.Tracer,
162+
moduleVersion: string | undefined,
163+
semconvStability: SemconvStability
164+
) {
145165
return function wrapCreateHandler(
146166
original: RouterExecutionContext['create']
147167
) {
@@ -167,19 +187,24 @@ function createWrapCreateHandler(tracer: api.Tracer, moduleVersion?: string) {
167187
res: any,
168188
next: (...args: any[]) => unknown
169189
) {
170-
const span = tracer.startSpan(spanName, {
171-
attributes: {
172-
...NestInstrumentation.COMMON_ATTRIBUTES,
173-
[AttributeNames.VERSION]: moduleVersion,
174-
[AttributeNames.TYPE]: NestType.REQUEST_CONTEXT,
175-
[ATTR_HTTP_METHOD]: req.method,
176-
[ATTR_HTTP_URL]: req.originalUrl || req.url,
177-
[ATTR_HTTP_ROUTE]:
178-
req.route?.path || req.routeOptions?.url || req.routerPath,
179-
[AttributeNames.CONTROLLER]: instanceName,
180-
[AttributeNames.CALLBACK]: callbackName,
181-
},
182-
});
190+
const attributes: api.Attributes = {
191+
...NestInstrumentation.COMMON_ATTRIBUTES,
192+
[AttributeNames.VERSION]: moduleVersion,
193+
[AttributeNames.TYPE]: NestType.REQUEST_CONTEXT,
194+
[ATTR_HTTP_ROUTE]:
195+
req.route?.path || req.routeOptions?.url || req.routerPath,
196+
[AttributeNames.CONTROLLER]: instanceName,
197+
[AttributeNames.CALLBACK]: callbackName,
198+
};
199+
if (semconvStability & SemconvStability.OLD) {
200+
attributes[ATTR_HTTP_METHOD] = req.method;
201+
attributes[ATTR_HTTP_URL] = req.originalUrl || req.url;
202+
}
203+
if (semconvStability & SemconvStability.STABLE) {
204+
attributes[ATTR_HTTP_REQUEST_METHOD] = req.method;
205+
attributes[ATTR_URL_FULL] = req.originalUrl || req.url;
206+
}
207+
const span = tracer.startSpan(spanName, { attributes });
183208
const spanContext = api.trace.setSpan(api.context.active(), span);
184209

185210
return api.context.with(spanContext, async () => {

packages/instrumentation-nestjs-core/test/index.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ import * as util from 'util';
3131

3232
const LIB_VERSION = require('@nestjs/core/package.json').version;
3333

34+
// This is a meagre testing of just a single value of
35+
// OTEL_SEMCONV_STABILITY_OPT_IN, because testing multiple configurations of
36+
// `NestInstrumentation` in this all-in-one-process is more trouble than it
37+
// it is worth for the ~6mo migration process.
38+
process.env.OTEL_SEMCONV_STABILITY_OPT_IN = 'http/dup';
39+
3440
const instrumentation = new NestInstrumentation();
3541
const memoryExporter = new InMemorySpanExporter();
3642

@@ -215,8 +221,17 @@ const assertSpans = (actualSpans: any[], expectedSpans: any[]) => {
215221

216222
assert.strictEqual(span.name, expected.name);
217223

224+
// Because OTEL_SEMCONV_STABILITY_OPT_IN=http/dup is being set for testing
225+
// we expect both the deprecated:
218226
assert.strictEqual(span.attributes['http.method'], expected.method);
219227
assert.strictEqual(span.attributes['http.url'], expected.url);
228+
// ... and stable HTTP semconv attributes:
229+
assert.strictEqual(
230+
span.attributes['http.request.method'],
231+
expected.method
232+
);
233+
assert.strictEqual(span.attributes['url.full'], expected.url);
234+
220235
assert.strictEqual(span.attributes['http.route'], expected.path);
221236
assert.strictEqual(span.attributes['nestjs.type'], expected.type);
222237
assert.strictEqual(span.attributes['nestjs.callback'], expected.callback);

0 commit comments

Comments
 (0)