Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
- uses: actions/setup-node@v7
with:
node-version: latest
- run: npm ci
- run: npm test

benchmark:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Attributes set on server spans:
| `url.path` | Request URL path (without query) | Always |
| `url.query` | Query string (without `?`) | When query string is present |
| `url.scheme` | `http` or `https` | Always |
| `server.address` | Host header value | When `Host` header is present |
| `server.address` | Requested host, without the port | When `Host` header is present |
| `network.protocol.version` | HTTP version (e.g. `1.1`) | Always |
| `http.response.status_code` | Response status code | When response finishes |
| `error.type` | HTTP status code as string | On 5xx responses |
Expand Down Expand Up @@ -136,7 +136,7 @@ Attributes set on undici/fetch client spans:
| --------------------------- | ------------------------- | ------------------------- |
| `http.request.method` | HTTP method | Always |
| `url.full` | Full request URL | Always |
| `server.address` | Target origin | Always |
| `server.address` | Target host | Always |
| `server.port` | Target port | Always |
| `http.response.status_code` | Response status code | When response is received |
| `error.type` | Status code or error name | On 4xx/5xx or errors |
Expand Down
4 changes: 2 additions & 2 deletions lib/instrumentations.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ function onHttpServerRequestStart({ request, response, socket }) {
span.setAttribute('network.protocol.version', request.httpVersion || '1.1');

const host = request.headers?.host;
if (host) span.setAttribute('server.address', host);
if (host) span.setAttribute('server.address', splitAuthority(host)[0]);

// The span is stashed on the request so the response handlers do not
// need a second AsyncLocalStorage lookup.
Expand Down Expand Up @@ -243,9 +243,9 @@ function onUndiciRequestCreate({ request }) {
const { origin } = request;
const path = request.path;
if (origin) {
span.setAttribute('server.address', origin);
// The origin omits the port when it is the scheme default.
const parsed = new URL(origin);
span.setAttribute('server.address', parsed.hostname);
span.setAttribute(
'server.port',
Number(parsed.port) || (parsed.protocol === 'https:' ? 443 : 80),
Expand Down
11 changes: 11 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"format:check": "prettier --check ."
},
"devDependencies": {
"@opentelemetry/semantic-conventions": "1.43.0",
"prettier": "3.9.10"
}
}
1 change: 1 addition & 0 deletions test/test-http-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('otel HTTP server spans', () => {

assert.partialDeepStrictEqual(spanAttrs(serverSpan), {
'http.request.method': 'GET',
'server.address': '127.0.0.1',
'url.path': '/test-path',
'http.response.status_code': '200',
});
Expand Down
80 changes: 80 additions & 0 deletions test/test-semconv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict';

const assert = require('node:assert');
const { after, before, describe, it } = require('node:test');
// The registry of defined attribute names: the package's exports are the
// attribute names the OpenTelemetry semantic conventions define.
const semconv = require('@opentelemetry/semantic-conventions');
const { flush } = require('../lib/flush');
const { startTracing, startHTTPServer, httpGet } = require('./common/otel');

const registry = new Set(
Object.values(semconv).filter((v) => typeof v === 'string'),
);

describe('otel semantic conventions', () => {
let collector;

before(async () => {
collector = await startTracing();
});

after(() => collector.close());

it('emits only attribute names the conventions define', async () => {
// One trace through every instrumentation shape: a server span with
// its query attribute, http and fetch client spans, a failed request
// for the error attributes, and a refused connection for the
// exception event attributes.
const ok = await startHTTPServer();
const failing = await startHTTPServer((req, res) => {
res.writeHead(500);
res.end();
});

await httpGet(ok.port, '/semconv?token=1');
await fetch(`http://127.0.0.1:${ok.port}/fetch`)
.then((res) => res.text())
.catch(() => {});
await httpGet(failing.port, '/fail');
await httpGet(1, '/refused').catch(() => {});

flush();
const payload = await collector.next();

await ok.close();
await failing.close();

const spans = payload.resourceSpans[0].scopeSpans[0].spans;
assert.ok(
spans.some((s) => s.kind === 2),
'Expected a server span',
);
assert.ok(
spans.some((s) => s.kind === 3),
'Expected client spans',
);

const assertDefined = (key, where) =>
assert.ok(
registry.has(key),
`${where} attribute "${key}" is not defined by the ` +
'semantic conventions',
);

for (const span of spans) {
for (const attr of span.attributes ?? []) {
assertDefined(attr.key, 'span');
}
for (const event of span.events ?? []) {
for (const attr of event.attributes ?? []) {
assertDefined(attr.key, 'event');
}
}
}

for (const attr of payload.resourceSpans[0].resource.attributes) {
assertDefined(attr.key, 'resource');
}
});
});
2 changes: 1 addition & 1 deletion test/test-undici.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ describe('otel undici spans', () => {

assert.partialDeepStrictEqual(spanAttrs(clientSpan), {
'http.request.method': 'GET',
'server.address': `http://127.0.0.1:${target.port}`,
'server.address': '127.0.0.1',
'server.port': String(target.port),
'url.full': `http://127.0.0.1:${target.port}/fetch-test`,
'http.response.status_code': '200',
Expand Down
Loading