Skip to content

@opentelemetry/instrumentation-express - Bare middleware can't resolve value for http.route attribute #3236

@vitorvasc

Description

@vitorvasc

What version of OpenTelemetry are you using?

"dependencies": {
    "@opentelemetry/api": "^1.9.0",
    "@opentelemetry/instrumentation-express": "^0.57.0",
    "@opentelemetry/instrumentation-http": "^0.208.0",
    "@opentelemetry/sdk-metrics": "^2.2.0",
    "@opentelemetry/sdk-node": "^0.208.0",
    "@opentelemetry/sdk-trace-node": "^2.2.0",
    "express": "^5.1.0"
}

What version of Node are you using?

v24.11.0

This behavior was also reproduced in other applications using v18 and above.

What did you do?

When an Express application includes a bare middleware that returns a response before the route handler runs — such as an ETag cache check returning 304 Not Modified — the http.route attribute isn't populated with the full matched route.

The snippet below is a minimal example. Full code is available at: https://github.com/vitorvasc/js-otel-instrumentation-express.

main.js

// Bare Middleware that may return early (e.g., cache hit with 304)
apiRouter.use([(req, res, next) => {
  const etag = req.headers['if-none-match'];
  const currentETag = '"12345-abcdef"';

  if (etag && etag === currentETag) {
    res.status(304).end();  // Early return
    return;
  }
  next();
}]);

// Define routes
apiRouter.get('/users', handler);
apiRouter.get('/products', handler);

// Mount router
app.use('/', apiRouter);

cURL

curl http://localhost:${port}/api/users -H 'If-None-Match: "12345-abcdef"'

What did you expect to see?

Even if a middleware responds early, the http.route attribute should reflect the full matched route.

Expected behavior:

  • Normal request (200): http.route = "/api/users"
  • Bare middleware (304): http.route = "/api/users"

What did you see instead?

When a bare middleware finishes the request, the instrumentation captures only the router's mount path and loses the specific route information:

Actual behavior:

  • Normal request (200): http.route = "/api/users"
  • Bare middleware (304): http.route = "/api"

In this scenario, only /api is recorded instead of /api/users.

Metric output: `http.server.duration`
{
  descriptor: {
    name: 'http.server.duration',
    type: 'HISTOGRAM',
    description: 'Measures the duration of inbound HTTP requests.',
    unit: 'ms',
    valueType: 1,
    advice: {}
  },
  dataPointType: 0,
  dataPoints: [
    {
      attributes: {
        'http.scheme': 'http',
        'http.method': 'GET',
        'net.host.name': 'localhost',
        'http.flavor': '1.1',
        'http.status_code': 304,
        'net.host.port': 3000,
        'http.route': '/'
      },
      startTime: [ 1763685623, 177000000 ],
      endTime: [ 1763685624, 307000000 ],
      value: {
        min: 12.619138,
        max: 12.619138,
        sum: 12.619138,
        buckets: {
          boundaries: [
               0,    5,    10,   25,
              50,   75,   100,  250,
             500,  750,  1000, 2500,
            5000, 7500, 10000
          ],
          counts: [
            0, 0, 0, 1, 0, 0,
            0, 0, 0, 0, 0, 0,
            0, 0, 0, 0
          ]
        },
        count: 1
      }
    }
  ]
}

Additional context

I'm not sure whether this could be related to #1947.

Although the example uses an ETag scenario, this behavior appears in any application where bare middleware finishes the request before reaching an actual router — such as cached endpoints (HTTP 304), authentication (HTTP 401/403), rate limiting (HTTP 429), or request validation errors (HTTP 400).

If this is confirmed to be a valid issue from @opentelemetry/instrumentation-express, I'm happy to open a PR and contribute with a fix.

Tip: React with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding +1 or me too, to help us triage it. Learn more here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingpkg:instrumentation-expresspriority:p2Bugs and spec inconsistencies which cause telemetry to be incomplete or incorrect

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions