opentelemetry-js icon indicating copy to clipboard operation
opentelemetry-js copied to clipboard

instrumentation-http: ESM instrumentation does not work on outgoing requests

Open constb opened this issue 4 months ago • 7 comments

What happened?

Steps to Reproduce

with node v20.17.0 or v18.20.4, install latest (0.53.0) opentelemetry sdk-node and instrumentation-http enable ESM (set type: module in package.json)

a client makes a http request in context with trace id using http.request()

Expected Result

server receives traceparent header and runs in a context with same traceId.

Actual Result

server does not receive traceparent header and runs handler in a separate trace context.

Additional Details

might be related to #5001

OpenTelemetry Setup Code

// register.js

import { register } from 'node:module';
import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
import { node, NodeSDK } from '@opentelemetry/sdk-node';

const OPENTELEMETRY_SDK = Symbol('openTelemetrySdk');

register('@opentelemetry/instrumentation/hook.mjs', import.meta.url);

globalThis[OPENTELEMETRY_SDK] = new NodeSDK({
  serviceName: 'test',
  instrumentations: [new HttpInstrumentation({ enabled: true })],
});
globalThis[OPENTELEMETRY_SDK].start();

const shutdownFn = async () => globalThis[OPENTELEMETRY_SDK].shutdown().then(() => console.log('shutdown')).catch((error) => console.error(error));
let shutdownStarted = false;
const signals = ['SIGHUP', 'SIGINT', 'SIGQUIT', 'SIGILL', 'SIGTRAP', 'SIGABRT', 'SIGBUS', 'SIGFPE', 'SIGSEGV', 'SIGUSR2', 'SIGTERM'];
const signalHandler = (signal) => {
  if (shutdownStarted) return;
  shutdownStarted = true;
  shutdownFn().then(() => {
    signals.forEach((s) => process.removeListener(s, signalHandler));
    process.kill(process.pid, signal);
  });
};
signals.forEach((s) => process.on(s, signalHandler));

// index.js

import { api } from '@opentelemetry/sdk-node';
import http from 'node:http';

const span = api.trace.getTracer().startSpan('test');
const ctx = api.trace.setSpan(api.context.active(), span);

const server = http.Server((req, res) => {
  console.log('server', { heades: req.headers, span: api.trace.getActiveSpan().spanContext() });
  res.writeHead(204);
  res.end();
});
await new Promise(res => server.listen(8080).once('listening', res));

await api.context.with(ctx, async () => {
  console.log('client', { span: api.trace.getActiveSpan().spanContext() });
  await new Promise(resolve => {
    const req = http.request('http://localhost:8080/ping', (res) => {
      res.on('data', () => {});
      res.on('end', resolve);
    });
    req.end();
  });
});

process.kill(process.pid, 'SIGTERM');

package.json

{
  "name": "otel-reproduce",
  "version": "0.0.1",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "start": "cross-env OTEL_PROPAGATORS=tracecontext OTEL_TRACES_EXPORTER=console OTEL_NODE_RESOURCE_DETECTORS=none node --import ./register.js ./index.js"
  },
  "author": "",
  "license": "UNLICENSED",
  "description": "",
  "dependencies": {
    "@opentelemetry/instrumentation": "0.53.0",
    "@opentelemetry/instrumentation-http": "0.53.0",
    "@opentelemetry/sdk-node": "0.53.0",
    "cross-env": "7.0.3"
  }
}

Relevant log output

client {
  span: {
    traceId: '92a751c98e9a4c3d782cc6c877fd8502',
    spanId: '74ae11ca029180a3',
    traceFlags: 1,
    traceState: undefined
  }
}
server {
  heades: { host: 'localhost:8080', connection: 'close' },
  span: {
    traceId: 'c94c82377ce8920746fc79e664303ebc',
    spanId: '64f65ad6b638654a',
    traceFlags: 1,
    traceState: undefined
  }
}

constb avatar Sep 27 '24 16:09 constb