Skip to content

Commit efaa257

Browse files
authored
Add net.peer.ip in requests & urllib3 instrumentations. (#661)
1 parent 201aa2b commit efaa257

File tree

11 files changed

+492
-4
lines changed

11 files changed

+492
-4
lines changed

.github/workflows/test.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
- 'release/*'
77
pull_request:
88
env:
9-
CORE_REPO_SHA: c49ad57bfe35cfc69bfa863d74058ca9bec55fc3
9+
CORE_REPO_SHA: d9c22a87b6bfc5ec332588c764f82c32f068b2c3
1010

1111
jobs:
1212
build:

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2828
- `opentelemetry-instrumentation-urllib3` Updated `_RequestHookT` with two additional fields - the request body and the request headers
2929
([#660](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/660))
3030

31+
### Added
32+
33+
- `opentelemetry-instrumentation-urllib3`, `opentelemetry-instrumentation-requests`
34+
The `net.peer.ip` attribute is set to the IP of the connected HTTP server or proxy
35+
using a new instrumentor in `opententelemetry-util-http`
36+
([#661](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/661))
37+
3138
## [1.5.0-0.24b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.5.0-0.24b0) - 2021-08-26
3239

3340
### Added

instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from opentelemetry.trace import SpanKind, get_tracer
5454
from opentelemetry.trace.status import Status
5555
from opentelemetry.util.http import remove_url_credentials
56+
from opentelemetry.util.http.httplib import set_ip_on_next_http_connection
5657

5758
# A key to a context variable to avoid creating duplicate spans when instrumenting
5859
# both, Session.request and Session.send, since Session.request calls into Session.send
@@ -133,7 +134,7 @@ def _instrumented_requests_call(
133134

134135
with tracer.start_as_current_span(
135136
span_name, kind=SpanKind.CLIENT
136-
) as span:
137+
) as span, set_ip_on_next_http_connection(span):
137138
exception = None
138139
if span.is_recording():
139140
span.set_attribute(SpanAttributes.HTTP_METHOD, method)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import requests
16+
17+
from opentelemetry import trace
18+
from opentelemetry.instrumentation.requests import RequestsInstrumentor
19+
from opentelemetry.test.httptest import HttpTestBase
20+
from opentelemetry.test.test_base import TestBase
21+
from opentelemetry.util.http.httplib import HttpClientInstrumentor
22+
23+
24+
class TestURLLib3InstrumentorWithRealSocket(HttpTestBase, TestBase):
25+
def setUp(self):
26+
super().setUp()
27+
self.assert_ip = self.server.server_address[0]
28+
self.http_host = ":".join(map(str, self.server.server_address[:2]))
29+
self.http_url_base = "http://" + self.http_host
30+
self.http_url = self.http_url_base + "/status/200"
31+
HttpClientInstrumentor().instrument()
32+
RequestsInstrumentor().instrument()
33+
34+
def tearDown(self):
35+
super().tearDown()
36+
HttpClientInstrumentor().uninstrument()
37+
RequestsInstrumentor().uninstrument()
38+
39+
@staticmethod
40+
def perform_request(url: str) -> requests.Response:
41+
return requests.get(url)
42+
43+
def test_basic_http_success(self):
44+
response = self.perform_request(self.http_url)
45+
self.assert_success_span(response)
46+
47+
def test_basic_http_success_using_connection_pool(self):
48+
with requests.Session() as session:
49+
response = session.get(self.http_url)
50+
51+
self.assert_success_span(response)
52+
53+
# Test that when re-using an existing connection, everything still works.
54+
# Especially relevant for IP capturing.
55+
response = session.get(self.http_url)
56+
57+
self.assert_success_span(response)
58+
59+
def assert_span(self, num_spans=1): # TODO: Move this to TestBase
60+
span_list = self.memory_exporter.get_finished_spans()
61+
self.assertEqual(num_spans, len(span_list))
62+
if num_spans == 0:
63+
return None
64+
self.memory_exporter.clear()
65+
if num_spans == 1:
66+
return span_list[0]
67+
return span_list
68+
69+
def assert_success_span(self, response: requests.Response):
70+
self.assertEqual("Hello!", response.text)
71+
72+
span = self.assert_span()
73+
self.assertIs(trace.SpanKind.CLIENT, span.kind)
74+
self.assertEqual("HTTP GET", span.name)
75+
76+
attributes = {
77+
"http.status_code": 200,
78+
"net.peer.ip": self.assert_ip,
79+
}
80+
self.assertGreaterEqual(span.attributes.items(), attributes.items())

instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ install_requires =
4141
opentelemetry-api ~= 1.3
4242
opentelemetry-semantic-conventions == 0.24b0
4343
opentelemetry-instrumentation == 0.24b0
44+
opentelemetry-util-http == 0.24b0
4445
wrapt >= 1.0.0, < 2.0.0
4546

4647
[options.extras_require]

instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ def response_hook(span, request, response):
8181
from opentelemetry.semconv.trace import SpanAttributes
8282
from opentelemetry.trace import Span, SpanKind, get_tracer
8383
from opentelemetry.trace.status import Status
84+
from opentelemetry.util.http.httplib import set_ip_on_next_http_connection
8485

8586
# A key to a context variable to avoid creating duplicate spans when instrumenting
8687
# both, Session.request and Session.send, since Session.request calls into Session.send
@@ -168,7 +169,7 @@ def instrumented_urlopen(wrapped, instance, args, kwargs):
168169

169170
with tracer.start_as_current_span(
170171
span_name, kind=SpanKind.CLIENT, attributes=span_attributes
171-
) as span:
172+
) as span, set_ip_on_next_http_connection(span):
172173
if callable(request_hook):
173174
request_hook(span, instance, headers, body)
174175
inject(headers)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import urllib3
16+
import urllib3.exceptions
17+
18+
from opentelemetry import trace
19+
from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor
20+
from opentelemetry.test.httptest import HttpTestBase
21+
from opentelemetry.test.test_base import TestBase
22+
from opentelemetry.util.http.httplib import HttpClientInstrumentor
23+
24+
25+
class TestURLLib3InstrumentorWithRealSocket(HttpTestBase, TestBase):
26+
def setUp(self):
27+
super().setUp()
28+
self.assert_ip = self.server.server_address[0]
29+
self.http_host = ":".join(map(str, self.server.server_address[:2]))
30+
self.http_url_base = "http://" + self.http_host
31+
self.http_url = self.http_url_base + "/status/200"
32+
HttpClientInstrumentor().instrument()
33+
URLLib3Instrumentor().instrument()
34+
35+
def tearDown(self):
36+
super().tearDown()
37+
HttpClientInstrumentor().uninstrument()
38+
URLLib3Instrumentor().uninstrument()
39+
40+
@staticmethod
41+
def perform_request(url: str) -> urllib3.response.HTTPResponse:
42+
with urllib3.PoolManager() as pool:
43+
resp = pool.request("GET", url)
44+
resp.close()
45+
return resp
46+
47+
def test_basic_http_success(self):
48+
response = self.perform_request(self.http_url)
49+
self.assert_success_span(response, self.http_url)
50+
51+
def test_basic_http_success_using_connection_pool(self):
52+
with urllib3.HTTPConnectionPool(self.http_host, timeout=3) as pool:
53+
response = pool.request("GET", "/status/200")
54+
55+
self.assert_success_span(response, self.http_url)
56+
57+
# Test that when re-using an existing connection, everything still works.
58+
# Especially relevant for IP capturing.
59+
response = pool.request("GET", "/status/200")
60+
61+
self.assert_success_span(response, self.http_url)
62+
63+
def assert_span(self, num_spans=1):
64+
span_list = self.memory_exporter.get_finished_spans()
65+
self.assertEqual(num_spans, len(span_list))
66+
if num_spans == 0:
67+
return None
68+
self.memory_exporter.clear()
69+
if num_spans == 1:
70+
return span_list[0]
71+
return span_list
72+
73+
def assert_success_span(
74+
self, response: urllib3.response.HTTPResponse, url: str
75+
):
76+
self.assertEqual(b"Hello!", response.data)
77+
78+
span = self.assert_span()
79+
self.assertIs(trace.SpanKind.CLIENT, span.kind)
80+
self.assertEqual("HTTP GET", span.name)
81+
82+
attributes = {
83+
"http.status_code": 200,
84+
"net.peer.ip": self.assert_ip,
85+
}
86+
self.assertGreaterEqual(span.attributes.items(), attributes.items())

tox.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ commands_pre =
236236

237237
grpc: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-grpc[test]
238238

239-
falcon,flask,django,pyramid,tornado,starlette,fastapi,aiohttp,asgi,requests,urllib,wsgi: pip install {toxinidir}/util/opentelemetry-util-http[test]
239+
falcon,flask,django,pyramid,tornado,starlette,fastapi,aiohttp,asgi,requests,urllib,urllib3,wsgi: pip install {toxinidir}/util/opentelemetry-util-http[test]
240240
wsgi,falcon,flask,django,pyramid: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-wsgi[test]
241241
asgi,starlette,fastapi: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi[test]
242242

util/opentelemetry-util-http/setup.cfg

+4
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,7 @@ packages=find_namespace:
4040

4141
[options.packages.find]
4242
where = src
43+
44+
[options.entry_points]
45+
opentelemetry_instrumentor =
46+
httplib = opentelemetry.util.http.httplib:HttpClientInstrumentor

0 commit comments

Comments
 (0)