Skip to content

Commit 794402b

Browse files
committed
semantic convention for httplib
1 parent 58a6d3a commit 794402b

File tree

5 files changed

+180
-15
lines changed

5 files changed

+180
-15
lines changed

util/opentelemetry-util-http/src/opentelemetry/util/http/__init__.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@
1919
from re import IGNORECASE as RE_IGNORECASE
2020
from re import compile as re_compile
2121
from re import search
22-
from typing import Callable, Iterable, Optional
22+
from typing import Callable, Dict, Iterable, Optional
2323
from urllib.parse import urlparse, urlunparse
2424

25+
from opentelemetry.instrumentation._semconv import (
26+
_HTTPStabilityMode,
27+
_OpenTelemetrySemanticConventionStability,
28+
_OpenTelemetryStabilitySignalType,
29+
_server_active_requests_count_attrs_new,
30+
_server_duration_attrs_new,
31+
)
2532
from opentelemetry.semconv.trace import SpanAttributes
2633

2734
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS = (
@@ -228,20 +235,44 @@ def get_custom_headers(env_var: str) -> list[str]:
228235
return []
229236

230237

231-
def _parse_active_request_count_attrs(req_attrs):
232-
active_requests_count_attrs = {
233-
key: req_attrs[key]
234-
for key in _active_requests_count_attrs.intersection(req_attrs.keys())
238+
def _parse_active_request_count_attrs(
239+
req_attrs: Dict[str, str]
240+
) -> Dict[str, str]:
241+
_OpenTelemetrySemanticConventionStability._initialize()
242+
stability_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
243+
_OpenTelemetryStabilitySignalType.HTTP
244+
)
245+
246+
if stability_mode == _HTTPStabilityMode.DEFAULT:
247+
attrs_set = _active_requests_count_attrs
248+
elif stability_mode == _HTTPStabilityMode.HTTP:
249+
attrs_set = _server_active_requests_count_attrs_new
250+
else: # HTTP_DUP
251+
attrs_set = _active_requests_count_attrs.union(
252+
_server_active_requests_count_attrs_new
253+
)
254+
255+
return {
256+
key: req_attrs[key] for key in attrs_set.intersection(req_attrs.keys())
235257
}
236-
return active_requests_count_attrs
237258

238259

239-
def _parse_duration_attrs(req_attrs):
240-
duration_attrs = {
241-
key: req_attrs[key]
242-
for key in _duration_attrs.intersection(req_attrs.keys())
260+
def _parse_duration_attrs(req_attrs: Dict[str, str]) -> Dict[str, str]:
261+
_OpenTelemetrySemanticConventionStability._initialize()
262+
stability_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
263+
_OpenTelemetryStabilitySignalType.HTTP
264+
)
265+
266+
if stability_mode == _HTTPStabilityMode.DEFAULT:
267+
attrs_set = _duration_attrs
268+
elif stability_mode == _HTTPStabilityMode.HTTP:
269+
attrs_set = _server_duration_attrs_new
270+
else: # HTTP_DUP
271+
attrs_set = _duration_attrs.union(_server_duration_attrs_new)
272+
273+
return {
274+
key: req_attrs[key] for key in attrs_set.intersection(req_attrs.keys())
243275
}
244-
return duration_attrs
245276

246277

247278
def _parse_url_query(url: str):

util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
import wrapt
2828

2929
from opentelemetry import context
30+
from opentelemetry.instrumentation._semconv import (
31+
_OpenTelemetrySemanticConventionStability,
32+
_OpenTelemetryStabilitySignalType,
33+
_report_new,
34+
_report_old,
35+
)
3036
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
3137
from opentelemetry.instrumentation.utils import unwrap
3238
from opentelemetry.semconv.trace import SpanAttributes
@@ -104,8 +110,15 @@ def trysetip(conn: http.client.HTTPConnection, loglevel=logging.DEBUG) -> bool:
104110
stack_info=True,
105111
)
106112
else:
113+
_OpenTelemetrySemanticConventionStability._initialize()
114+
stability_mode = _OpenTelemetrySemanticConventionStability._get_opentelemetry_stability_opt_in_mode(
115+
_OpenTelemetryStabilitySignalType.HTTP
116+
)
107117
for span in spanlist:
108-
span.set_attribute(SpanAttributes.NET_PEER_IP, ip)
118+
if _report_old(stability_mode):
119+
span.set_attribute(SpanAttributes.NET_PEER_IP, ip)
120+
if _report_new(stability_mode):
121+
span.set_attribute(SpanAttributes.CLIENT_ADDRESS, ip)
109122
return True
110123

111124

util/opentelemetry-util-http/tests/test_http_base.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
from http.client import HTTPConnection, HTTPResponse, HTTPSConnection
16+
from typing import Tuple
1617

1718
from opentelemetry import trace
1819
from opentelemetry.test.httptest import HttpTestBase
@@ -103,7 +104,7 @@ def test_with_only_nonrecording_span(self):
103104
assert body == b"Hello!"
104105
self.assert_span(num_spans=0)
105106

106-
def perform_request(self, secure=False) -> HTTPResponse:
107+
def perform_request(self, secure=False) -> Tuple[HTTPResponse, bytes]:
107108
conn_cls = HTTPSConnection if secure else HTTPConnection
108109
conn = conn_cls(self.server.server_address[0], self.server.server_port)
109110
resp = None
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import os
2+
from http.client import HTTPConnection, HTTPResponse, HTTPSConnection
3+
from typing import Tuple
4+
5+
from opentelemetry import trace
6+
from opentelemetry.semconv.trace import SpanAttributes
7+
from opentelemetry.test.httptest import HttpTestBase
8+
from opentelemetry.test.test_base import TestBase
9+
from opentelemetry.util.http.httplib import (
10+
HttpClientInstrumentor,
11+
set_ip_on_next_http_connection,
12+
)
13+
14+
15+
class TestHttpBase(TestBase, HttpTestBase):
16+
def setUp(self):
17+
super().setUp()
18+
self.server_thread, self.server = self.run_server()
19+
self.http_instrumentor = HttpClientInstrumentor()
20+
21+
def tearDown(self):
22+
self.server.shutdown()
23+
self.server_thread.join()
24+
super().tearDown()
25+
26+
def instrument_http(self, semconv_mode):
27+
original = os.environ.get("OTEL_SEMCONV_STABILITY_OPT_IN")
28+
os.environ["OTEL_SEMCONV_STABILITY_OPT_IN"] = semconv_mode
29+
self.http_instrumentor.instrument()
30+
return original
31+
32+
def uninstrument_http(self, original_value):
33+
self.http_instrumentor.uninstrument()
34+
if original_value is None:
35+
del os.environ["OTEL_SEMCONV_STABILITY_OPT_IN"]
36+
else:
37+
os.environ["OTEL_SEMCONV_STABILITY_OPT_IN"] = original_value
38+
39+
def assert_span(self, exporter=None, num_spans=1):
40+
if exporter is None:
41+
exporter = self.memory_exporter
42+
span_list = exporter.get_finished_spans()
43+
self.assertEqual(num_spans, len(span_list))
44+
if num_spans == 0:
45+
return None
46+
if num_spans == 1:
47+
return span_list[0]
48+
return span_list
49+
50+
def test_basic_with_span_default(self):
51+
original = self.instrument_http("")
52+
try:
53+
tracer = trace.get_tracer(__name__)
54+
with tracer.start_as_current_span(
55+
"HTTP GET"
56+
) as span, set_ip_on_next_http_connection(span):
57+
resp, body = self.perform_request()
58+
assert resp.status == 200
59+
assert body == b"Hello!"
60+
span = self.assert_span(num_spans=1)
61+
self.assertEqual(
62+
span.attributes, {SpanAttributes.NET_PEER_IP: "127.0.0.1"}
63+
)
64+
finally:
65+
self.uninstrument_http(original)
66+
67+
def test_basic_with_span_new(self):
68+
original = self.instrument_http("http")
69+
try:
70+
tracer = trace.get_tracer(__name__)
71+
with tracer.start_as_current_span(
72+
"HTTP GET"
73+
) as span, set_ip_on_next_http_connection(span):
74+
resp, body = self.perform_request()
75+
assert resp.status == 200
76+
assert body == b"Hello!"
77+
span = self.assert_span(num_spans=1)
78+
self.assertEqual(
79+
span.attributes, {SpanAttributes.CLIENT_ADDRESS: "127.0.0.1"}
80+
)
81+
finally:
82+
self.uninstrument_http(original)
83+
84+
def test_basic_with_span_both(self):
85+
original = self.instrument_http("http/dup")
86+
try:
87+
tracer = trace.get_tracer(__name__)
88+
with tracer.start_as_current_span(
89+
"HTTP GET"
90+
) as span, set_ip_on_next_http_connection(span):
91+
resp, body = self.perform_request()
92+
assert resp.status == 200
93+
assert body == b"Hello!"
94+
span = self.assert_span(num_spans=1)
95+
self.assertEqual(
96+
span.attributes,
97+
{
98+
SpanAttributes.NET_PEER_IP: "127.0.0.1",
99+
SpanAttributes.CLIENT_ADDRESS: "127.0.0.1",
100+
},
101+
)
102+
finally:
103+
self.uninstrument_http(original)
104+
105+
def perform_request(self, secure=False) -> Tuple[HTTPResponse, bytes]:
106+
conn_cls = HTTPSConnection if secure else HTTPConnection
107+
conn = conn_cls(self.server.server_address[0], self.server.server_port)
108+
resp = None
109+
try:
110+
conn.request("GET", "/", headers={"Connection": "close"})
111+
resp = conn.getresponse()
112+
return resp, resp.read()
113+
finally:
114+
if resp:
115+
resp.close()
116+
conn.close()

util/opentelemetry-util-http/tests/test_remove_credentials.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ def test_remove_credentials(self):
1919
def test_remove_credentials_ipv4_literal(self):
2020
url = "http://someuser:somepass@127.0.0.1:8080/test/path?query=value"
2121
cleaned_url = remove_url_credentials(url)
22-
self.assertEqual(cleaned_url, "http://127.0.0.1:8080/test/path?query=value")
22+
self.assertEqual(
23+
cleaned_url, "http://127.0.0.1:8080/test/path?query=value"
24+
)
2325

2426
def test_remove_credentials_ipv6_literal(self):
2527
url = "http://someuser:somepass@[::1]:8080/test/path?query=value"
2628
cleaned_url = remove_url_credentials(url)
27-
self.assertEqual(cleaned_url, "http://[::1]:8080/test/path?query=value")
29+
self.assertEqual(
30+
cleaned_url, "http://[::1]:8080/test/path?query=value"
31+
)

0 commit comments

Comments
 (0)