Description
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
- 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:
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