Skip to content

Missing HTTP response attributes in HTTPX instrumentation #3614

Open
@diogosilva30

Description

@diogosilva30

Describe your environment

OS: Ubuntu
Python version: Python 3.12.4
Package version: 0.55b1

What happened?

When using HTTPXClientInstrumentor some HTTP response attributes (response status code, http version) are only added to duration histogram if SPAN is recording (tracing enabled). This create a high coupling between two things that should be seperate. Metrics behaviour should be independent of tracing decisions.

Steps to Reproduce

  1. Run this code
import httpx
import asyncio
import time

from opentelemetry import trace, metrics
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor, ConsoleSpanExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricExporter, PeriodicExportingMetricReader
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
import os

# --- Tracing setup ---
trace.set_tracer_provider(TracerProvider(resource=Resource.create({SERVICE_NAME: "httpx-client-example"})))
trace.get_tracer_provider().add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter(out=open(os.devnull, "w"))))

# --- Metrics setup ---
metrics.set_meter_provider(
    MeterProvider(
        resource=Resource.create({SERVICE_NAME: "httpx-client-example"}),
        metric_readers=[PeriodicExportingMetricReader(ConsoleMetricExporter(), export_interval_millis=5000)],
    )
)

HTTPXClientInstrumentor().instrument()


# --- Use a tracer to ensure span recording ---
tracer = trace.get_tracer(__name__)
url = "https://example.com"

# --- Sync request inside a span ---
with tracer.start_as_current_span("sync-request") as span:
    with httpx.Client() as client:
        response = client.get(url)
        print(f"Sync response: {response.status_code}")


# Allow metrics to be exported
time.sleep(6)

Output:

{
    "resource_metrics": [
        {
            "resource": {
                "attributes": {
                    "telemetry.sdk.language": "python",
                    "telemetry.sdk.name": "opentelemetry",
                    "telemetry.sdk.version": "1.34.1",
                    "service.name": "httpx-client-example"
                },
                "schema_url": ""
            },
            "scope_metrics": [
                {
                    "scope": {
                        "name": "opentelemetry.instrumentation.httpx",
                        "version": "0.55b1",
                        "schema_url": "https://opentelemetry.io/schemas/1.11.0",
                        "attributes": null
                    },
                    "metrics": [
                        {
                            "name": "http.client.duration",
                            "description": "measures the duration of the outbound HTTP request",
                            "unit": "ms",
                            "data": {
                                "data_points": [
                                    {
                                        "attributes": {
                                            "http.method": "GET",
                                            "http.scheme": "https",
                                            "http.status_code": 200
                                        },
                                        "start_time_unix_nano": 1751648610830079386,
                                        "time_unix_nano": 1751648617428825542,
                                        "count": 2,
                                        "sum": 1119,
                                        "bucket_counts": [
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            2,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0
                                        ],
                                        "explicit_bounds": [
                                            0.0,
                                            5.0,
                                            10.0,
                                            25.0,
                                            50.0,
                                            75.0,
                                            100.0,
                                            250.0,
                                            500.0,
                                            750.0,
                                            1000.0,
                                            2500.0,
                                            5000.0,
                                            7500.0,
                                            10000.0
                                        ],
                                        "min": 543,
                                        "max": 576,
                                        "exemplars": []
                                    }
                                ],
                                "aggregation_temporality": 2
                            }
                        }
                    ],
                    "schema_url": "https://opentelemetry.io/schemas/1.11.0"
                }
            ],
            "schema_url": ""
        }
    ]
}

As you see we have "http.status_code": 200 in attributes.

Now if we run this code without tracing:

import asyncio
import os
import time

import httpx
from opentelemetry import metrics, trace
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import ConsoleMetricExporter, PeriodicExportingMetricReader
from opentelemetry.sdk.resources import SERVICE_NAME, Resource


# --- Metrics setup ---
metrics.set_meter_provider(
    MeterProvider(
        resource=Resource.create({SERVICE_NAME: "httpx-client-example"}),
        metric_readers=[PeriodicExportingMetricReader(ConsoleMetricExporter(), export_interval_millis=5000)],
    )
)


HTTPXClientInstrumentor().instrument()

url = "https://example.com"

with httpx.Client() as client:
    response = client.get(url)
    print(f"Sync response: {response.status_code}")


# Allow metrics to be exported
time.sleep(6)

We have the following output:

{
    "resource_metrics": [
        {
            "resource": {
                "attributes": {
                    "telemetry.sdk.language": "python",
                    "telemetry.sdk.name": "opentelemetry",
                    "telemetry.sdk.version": "1.34.1",
                    "service.name": "httpx-client-example"
                },
                "schema_url": ""
            },
            "scope_metrics": [
                {
                    "scope": {
                        "name": "opentelemetry.instrumentation.httpx",
                        "version": "0.55b1",
                        "schema_url": "https://opentelemetry.io/schemas/1.11.0",
                        "attributes": null
                    },
                    "metrics": [
                        {
                            "name": "http.client.duration",
                            "description": "measures the duration of the outbound HTTP request",
                            "unit": "ms",
                            "data": {
                                "data_points": [
                                    {
                                        "attributes": {
                                            "http.method": "GET",
                                            "http.scheme": "https"
                                        },
                                        "start_time_unix_nano": 1751648790000666511,
                                        "time_unix_nano": 1751648796712108958,
                                        "count": 2,
                                        "sum": 1253,
                                        "bucket_counts": [
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            2,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0,
                                            0
                                        ],
                                        "explicit_bounds": [
                                            0.0,
                                            5.0,
                                            10.0,
                                            25.0,
                                            50.0,
                                            75.0,
                                            100.0,
                                            250.0,
                                            500.0,
                                            750.0,
                                            1000.0,
                                            2500.0,
                                            5000.0,
                                            7500.0,
                                            10000.0
                                        ],
                                        "min": 600,
                                        "max": 653,
                                        "exemplars": []
                                    }
                                ],
                                "aggregation_temporality": 2
                            }
                        }
                    ],
                    "schema_url": "https://opentelemetry.io/schemas/1.11.0"
                }
            ],
            "schema_url": ""
        }
    ]
}

As noticeable now http.status_code is missing from attributes.

Expected Result

Response attributes in metrics should be set no matter the tracing decisions, as done in requests library instrumentation:

https://github.com/open-telemetry/opentelemetry-python-contrib/blob/main/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py#L175C5-L176C45

Actual Result

When running without tracing instrumentation HTTP response attributes are missing from metric attributes

Additional context

No response

Would you like to implement a fix?

Yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions