From e4366362a7efb0de87a3335ea52bd664269b2ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 30 Aug 2021 16:50:59 +0200 Subject: [PATCH 01/14] Add instrumentation-http-base. --- .../MANIFEST.in | 9 + .../README.rst | 25 +++ .../setup.cfg | 40 ++++ .../setup.py | 99 ++++++++++ .../instrumentation/http_base/__init__.py | 176 ++++++++++++++++++ .../instrumentation/http_base/package.py | 14 ++ .../instrumentation/http_base/version.py | 15 ++ .../tests/__init__.py | 0 .../tests/test_http_base.py | 133 +++++++++++++ tox.ini | 8 + 10 files changed, 519 insertions(+) create mode 100644 instrumentation/opentelemetry-instrumentation-http-base/MANIFEST.in create mode 100644 instrumentation/opentelemetry-instrumentation-http-base/README.rst create mode 100644 instrumentation/opentelemetry-instrumentation-http-base/setup.cfg create mode 100644 instrumentation/opentelemetry-instrumentation-http-base/setup.py create mode 100644 instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/__init__.py create mode 100644 instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/package.py create mode 100644 instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/version.py create mode 100644 instrumentation/opentelemetry-instrumentation-http-base/tests/__init__.py create mode 100644 instrumentation/opentelemetry-instrumentation-http-base/tests/test_http_base.py diff --git a/instrumentation/opentelemetry-instrumentation-http-base/MANIFEST.in b/instrumentation/opentelemetry-instrumentation-http-base/MANIFEST.in new file mode 100644 index 0000000000..aed3e33273 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-http-base/MANIFEST.in @@ -0,0 +1,9 @@ +graft src +graft tests +global-exclude *.pyc +global-exclude *.pyo +global-exclude __pycache__/* +include CHANGELOG.md +include MANIFEST.in +include README.rst +include LICENSE diff --git a/instrumentation/opentelemetry-instrumentation-http-base/README.rst b/instrumentation/opentelemetry-instrumentation-http-base/README.rst new file mode 100644 index 0000000000..fcd6c64a23 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-http-base/README.rst @@ -0,0 +1,25 @@ +http.client helper instrumentation +================================== + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-http-base.svg + :target: https://pypi.org/project/opentelemetry-instrumentation-http-base/ + + +This library provides functionality to enrich HTTP client spans with IPs. It does +not create spans on its own. + +Installation +------------ + +:: + + pip install opentelemetry-instrumentation-http-base + + +References +---------- + +* `OpenTelemetry Project `_ +* `OpenTelemetry Python Examples `_ diff --git a/instrumentation/opentelemetry-instrumentation-http-base/setup.cfg b/instrumentation/opentelemetry-instrumentation-http-base/setup.cfg new file mode 100644 index 0000000000..b00b1e4b5f --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-http-base/setup.cfg @@ -0,0 +1,40 @@ +[metadata] +name = opentelemetry-instrumentation-http-base +description = Basic HTTP instrumentation to get an IP address +long_description = file: README.rst +long_description_content_type = text/x-rst +author = OpenTelemetry Authors +author_email = cncf-opentelemetry-contributors@lists.cncf.io +url = https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-http-base +platforms = any +license = Apache-2.0 +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + License :: OSI Approved :: Apache Software License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.6 +package_dir= + =src +packages=find_namespace: +install_requires = + opentelemetry-api ~= 1.3 + opentelemetry-semantic-conventions == 0.24b0 + opentelemetry-instrumentation == 0.24b0 + +[options.extras_require] +test = + opentelemetry-test == 0.24b0 + +[options.packages.find] +where = src + +[options.entry_points] +opentelemetry_instrumentor = + http_client = opentelemetry.instrumentation.http_base:HttpClientInstrumentor diff --git a/instrumentation/opentelemetry-instrumentation-http-base/setup.py b/instrumentation/opentelemetry-instrumentation-http-base/setup.py new file mode 100644 index 0000000000..b9f3efba83 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-http-base/setup.py @@ -0,0 +1,99 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +# DO NOT EDIT. THIS FILE WAS AUTOGENERATED FROM templates/instrumentation_setup.py.txt. +# RUN `python scripts/generate_setup.py` TO REGENERATE. + + +import distutils.cmd +import json +import os +from configparser import ConfigParser + +import setuptools + +config = ConfigParser() +config.read("setup.cfg") + +# We provide extras_require parameter to setuptools.setup later which +# overwrites the extra_require section from setup.cfg. To support extra_require +# secion in setup.cfg, we load it here and merge it with the extra_require param. +extras_require = {} +if "options.extras_require" in config: + for key, value in config["options.extras_require"].items(): + extras_require[key] = [v for v in value.split("\n") if v.strip()] + +BASE_DIR = os.path.dirname(__file__) +PACKAGE_INFO = {} + +VERSION_FILENAME = os.path.join( + BASE_DIR, + "src", + "opentelemetry", + "instrumentation", + "http_base", + "version.py", +) +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +PACKAGE_FILENAME = os.path.join( + BASE_DIR, + "src", + "opentelemetry", + "instrumentation", + "http_base", + "package.py", +) +with open(PACKAGE_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +# Mark any instruments/runtime dependencies as test dependencies as well. +extras_require["instruments"] = PACKAGE_INFO["_instruments"] +test_deps = extras_require.get("test", []) +for dep in extras_require["instruments"]: + test_deps.append(dep) + +extras_require["test"] = test_deps + + +class JSONMetadataCommand(distutils.cmd.Command): + + description = ( + "print out package metadata as JSON. This is used by OpenTelemetry dev scripts to ", + "auto-generate code in other places", + ) + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + metadata = { + "name": config["metadata"]["name"], + "version": PACKAGE_INFO["__version__"], + "instruments": PACKAGE_INFO["_instruments"], + } + print(json.dumps(metadata)) + + +setuptools.setup( + cmdclass={"meta": JSONMetadataCommand}, + version=PACKAGE_INFO["__version__"], + extras_require=extras_require, +) diff --git a/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/__init__.py b/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/__init__.py new file mode 100644 index 0000000000..b7bcca216c --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/__init__.py @@ -0,0 +1,176 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This library provides functionality to enrich HTTP client spans with IPs. It does +not create spans on its own. +""" + +import contextlib +import http.client +import logging +import socket +import typing +from typing import Collection + +import wrapt + +from opentelemetry import context +from opentelemetry.instrumentation.http_base.package import _instruments +from opentelemetry.instrumentation.instrumentor import BaseInstrumentor +from opentelemetry.instrumentation.utils import unwrap +from opentelemetry.semconv.trace import SpanAttributes +from opentelemetry.trace.span import Span + +_STATE_KEY = "httpbase_instrumentation_state" + +logger = logging.getLogger(__name__) + + +class HttpClientInstrumentor(BaseInstrumentor): + def instrumentation_dependencies(self) -> Collection[str]: + return _instruments + + def _instrument(self, **kwargs): + """Instruments the http.client module (not creating spans on its own)""" + _instrument() + + def _uninstrument(self, **kwargs): + _uninstrument() + + +def _remove_nonrecording(spanlist: typing.List[Span]): + idx = len(spanlist) - 1 + while idx >= 0: + if not spanlist[idx].is_recording(): + logger.debug("Span is not recording: %s", spanlist[idx]) + islast = idx + 1 == len(spanlist) + if not islast: + spanlist[idx] = spanlist[len(spanlist) - 1] + spanlist.pop() + if islast: + if idx == 0: + return False # We removed everything + idx -= 1 + else: + idx -= 1 + return True + + +def trysetip(conn: http.client.HTTPConnection, loglevel=logging.DEBUG) -> bool: + """Tries to set the net.peer.ip semantic attribute on the current span from the given + HttpConnection. + + Returns False if the connection is not yet established, False if the IP was captured + or there is no need to capture it. + """ + + state = _getstate() + if not state: + return True + spanlist = state.get("need_ip") # type: typing.List[Span] + if not spanlist: + return True + + # Remove all non-recording spans from the list. + if not _remove_nonrecording(spanlist): + return True + + sock = "" + try: + sock = conn.sock # type: typing.Optional[socket.socket] + logger.debug("Got socket: %s", sock) + if sock is None: + return False + addr = sock.getpeername() + if addr and addr[0]: + ip = addr[0] + except Exception: # pylint:disable=broad-except + logger.log( + loglevel, + "Failed to get peer address from %s", + sock, + exc_info=True, + stack_info=True, + ) + else: + for span in spanlist: + span.set_attribute(SpanAttributes.NET_PEER_IP, ip) + return True + + +def _instrumented_connect( + wrapped, instance: http.client.HTTPConnection, args, kwargs +): + result = wrapped(*args, **kwargs) + trysetip(instance, loglevel=logging.WARNING) + return result + + +def instrument_connect(module, name="connect"): + """Instrument additional connect() methods, e.g. for derived classes.""" + + wrapt.wrap_function_wrapper( + module, name, _instrumented_connect, + ) + + +def _instrument(): + def instrumented_send( + wrapped, instance: http.client.HTTPConnection, args, kwargs + ): + done = trysetip(instance) + result = wrapped(*args, **kwargs) + if not done: + trysetip(instance, loglevel=logging.WARNING) + return result + + wrapt.wrap_function_wrapper( + http.client.HTTPConnection, "send", instrumented_send, + ) + + instrument_connect(http.client.HTTPConnection) + # No need to instrument HTTPSConnection, as it calls super().connect() + + +def _getstate() -> typing.Optional[dict]: + return context.get_value(_STATE_KEY) + + +@contextlib.contextmanager +def set_ip_on_next_http_connection(span: Span): + state = _getstate() + if not state: + token = context.attach( + context.set_value(_STATE_KEY, {"need_ip": [span]}) + ) + try: + yield + finally: + context.detach(token) + else: + spans = state["need_ip"] # type: typing.List[Span] + spans.append(span) + try: + yield + finally: + try: + spans.remove(span) + except ValueError: # Span might have become non-recording + pass + + +def _uninstrument(): + unwrap(http.client.HTTPConnection, "send") + unwrap(http.client.HTTPConnection, "connect") diff --git a/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/package.py b/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/package.py new file mode 100644 index 0000000000..5088c610ce --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/package.py @@ -0,0 +1,14 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +_instruments = tuple() diff --git a/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/version.py b/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/version.py new file mode 100644 index 0000000000..d33bd87ce4 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/version.py @@ -0,0 +1,15 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__version__ = "0.24b0" diff --git a/instrumentation/opentelemetry-instrumentation-http-base/tests/__init__.py b/instrumentation/opentelemetry-instrumentation-http-base/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/instrumentation/opentelemetry-instrumentation-http-base/tests/test_http_base.py b/instrumentation/opentelemetry-instrumentation-http-base/tests/test_http_base.py new file mode 100644 index 0000000000..cf2f08a4bb --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-http-base/tests/test_http_base.py @@ -0,0 +1,133 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from http.client import HTTPConnection, HTTPResponse, HTTPSConnection + +from opentelemetry import trace +from opentelemetry.instrumentation.http_base import ( + HttpClientInstrumentor, + set_ip_on_next_http_connection, +) +from opentelemetry.test.httptest import HttpTestBase +from opentelemetry.test.test_base import TestBase +from opentelemetry.trace.span import INVALID_SPAN + +# pylint: disable=too-many-public-methods + + +class TestHttpBase(TestBase, HttpTestBase): + def setUp(self): + super().setUp() + HttpClientInstrumentor().instrument() + self.server_thread, self.server = self.run_server() + + def tearDown(self): + HttpClientInstrumentor().uninstrument() + self.server.shutdown() + self.server_thread.join() + super().tearDown() + + def assert_span(self, exporter=None, num_spans=1): + if exporter is None: + exporter = self.memory_exporter + span_list = exporter.get_finished_spans() + self.assertEqual(num_spans, len(span_list)) + if num_spans == 0: + return None + if num_spans == 1: + return span_list[0] + return span_list + + def test_basic(self): + resp, body = self.perform_request() + assert resp.status == 200 + assert body == b"Hello!" + self.assert_span(num_spans=0) + + def test_basic_with_span(self): + tracer = trace.get_tracer(__name__) + with tracer.start_as_current_span( + "HTTP GET" + ) as span, set_ip_on_next_http_connection(span): + resp, body = self.perform_request() + assert resp.status == 200 + assert body == b"Hello!" + span = self.assert_span(num_spans=1) + self.assertEqual(span.attributes, {"net.peer.ip": "127.0.0.1"}) + + def test_with_nested_span(self): + tracer = trace.get_tracer(__name__) + with tracer.start_as_current_span( + "requests HTTP GET" + ) as span, set_ip_on_next_http_connection(span): + with tracer.start_as_current_span( + "urllib3 HTTP GET" + ) as span2, set_ip_on_next_http_connection(span2): + resp, body = self.perform_request() + assert resp.status == 200 + assert body == b"Hello!" + for span in self.assert_span(num_spans=2): + self.assertEqual(span.attributes, {"net.peer.ip": "127.0.0.1"}) + + def test_with_nested_nonrecording_span(self): + tracer = trace.get_tracer(__name__) + with tracer.start_as_current_span( + "requests HTTP GET" + ) as span, set_ip_on_next_http_connection(span): + with trace.use_span(INVALID_SPAN), set_ip_on_next_http_connection( + INVALID_SPAN + ): + resp, body = self.perform_request() + assert resp.status == 200 + assert body == b"Hello!" + span = self.assert_span(num_spans=1) + self.assertEqual(span.attributes, {"net.peer.ip": "127.0.0.1"}) + + def test_with_only_nonrecording_span(self): + with trace.use_span(INVALID_SPAN), set_ip_on_next_http_connection( + INVALID_SPAN + ): + resp, body = self.perform_request() + assert resp.status == 200 + assert body == b"Hello!" + self.assert_span(num_spans=0) + + def perform_request(self, secure=False) -> HTTPResponse: + conn_cls = HTTPSConnection if secure else HTTPConnection + conn = conn_cls(self.server.server_address[0], self.server.server_port) + resp = None + try: + conn.request("GET", "/", headers={"Connection": "close"}) + resp = conn.getresponse() + return resp, resp.read() + finally: + if resp: + resp.close() + conn.close() + + def test_uninstrument(self): + HttpClientInstrumentor().uninstrument() + + tracer = trace.get_tracer(__name__) + with tracer.start_as_current_span( + "HTTP GET" + ) as span, set_ip_on_next_http_connection(span): + body = self.perform_request()[1] + self.assertEqual(b"Hello!", body) + + # We should have a span, but it should have no attributes + self.assertFalse(self.assert_span(num_spans=1).attributes) + + # instrument again to avoid warning message on tearDown + HttpClientInstrumentor().instrument() diff --git a/tox.ini b/tox.ini index 0fab90b217..77225fce46 100644 --- a/tox.ini +++ b/tox.ini @@ -145,6 +145,10 @@ envlist = py3{6,7,8,9}-test-instrumentation-httpx pypy3-test-instrumentation-httpx + ; opentelemetry-instrumentation-http-base + py3{6,7,8,9}-test-instrumentation-http-base + pypy3-test-instrumentation-http-base + ; opentelemetry-util-http py3{6,7,8,9}-test-util-http pypy3-test-util-http @@ -216,6 +220,7 @@ changedir = test-instrumentation-tornado: instrumentation/opentelemetry-instrumentation-tornado/tests test-instrumentation-wsgi: instrumentation/opentelemetry-instrumentation-wsgi/tests test-instrumentation-httpx: instrumentation/opentelemetry-instrumentation-httpx/tests + test-instrumentation-http-base: instrumentation/opentelemetry-instrumentation-http-base/tests test-util-http: util/opentelemetry-util-http/tests test-sdkextension-aws: sdk-extension/opentelemetry-sdk-extension-aws/tests test-propagator-ot-trace: propagator/opentelemetry-propagator-ot-trace/tests @@ -301,6 +306,8 @@ commands_pre = httpx: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-httpx[test] + http-base: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-http-base[test] + aws: pip install requests {toxinidir}/sdk-extension/opentelemetry-sdk-extension-aws[test] http: pip install {toxinidir}/util/opentelemetry-util-http[test] @@ -376,6 +383,7 @@ commands_pre = python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlite3[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-pyramid[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-requests[test] + python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-http-base[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-urllib[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-urllib3[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-pymysql[test] From 8f551c97e4cbaca4fc4576a5a0f88e6e28c93aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 6 Sep 2021 15:29:20 +0200 Subject: [PATCH 02/14] Add net.peer.ip in requests & urllib3 instrumentations. --- .../setup.cfg | 1 + .../instrumentation/requests/__init__.py | 5 +- .../tests/test_requests_ip_support.py | 80 +++++++++++++++++ .../setup.cfg | 1 + .../instrumentation/urllib3/__init__.py | 5 +- .../tests/test_urllib3_ip_support.py | 86 +++++++++++++++++++ tox.ini | 1 + 7 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_ip_support.py create mode 100644 instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py diff --git a/instrumentation/opentelemetry-instrumentation-requests/setup.cfg b/instrumentation/opentelemetry-instrumentation-requests/setup.cfg index f947b6d830..e3031e358b 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-requests/setup.cfg @@ -42,6 +42,7 @@ install_requires = opentelemetry-semantic-conventions == 0.24b0 opentelemetry-instrumentation == 0.24b0 opentelemetry-util-http == 0.24b0 + opentelemetry-instrumentation-http-base == 0.24b0 [options.extras_require] test = diff --git a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py index 73c81e1de5..2e7242d862 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py @@ -41,6 +41,9 @@ from requests.structures import CaseInsensitiveDict from opentelemetry import context +from opentelemetry.instrumentation.http_base import ( + set_ip_on_next_http_connection, +) from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.requests.package import _instruments from opentelemetry.instrumentation.requests.version import __version__ @@ -133,7 +136,7 @@ def _instrumented_requests_call( with tracer.start_as_current_span( span_name, kind=SpanKind.CLIENT - ) as span: + ) as span, set_ip_on_next_http_connection(span): exception = None if span.is_recording(): span.set_attribute(SpanAttributes.HTTP_METHOD, method) diff --git a/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_ip_support.py b/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_ip_support.py new file mode 100644 index 0000000000..df23f86390 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_ip_support.py @@ -0,0 +1,80 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import requests + +from opentelemetry import trace +from opentelemetry.instrumentation.http_base import HttpClientInstrumentor +from opentelemetry.instrumentation.requests import RequestsInstrumentor +from opentelemetry.test.httptest import HttpTestBase +from opentelemetry.test.test_base import TestBase + + +class TestURLLib3InstrumentorWithRealSocket(HttpTestBase, TestBase): + def setUp(self): + super().setUp() + self.assert_ip = self.server.server_address[0] + self.http_host = ":".join(map(str, self.server.server_address[:2])) + self.http_url_base = "http://" + self.http_host + self.http_url = self.http_url_base + "/status/200" + HttpClientInstrumentor().instrument() + RequestsInstrumentor().instrument() + + def tearDown(self): + super().tearDown() + HttpClientInstrumentor().uninstrument() + RequestsInstrumentor().uninstrument() + + @staticmethod + def perform_request(url: str) -> requests.Response: + return requests.get(url) + + def test_basic_http_success(self): + response = self.perform_request(self.http_url) + self.assert_success_span(response) + + def test_basic_http_success_using_connection_pool(self): + with requests.Session() as session: + response = session.get(self.http_url) + + self.assert_success_span(response) + + # Test that when re-using an existing connection, everything still works. + # Especially relevant for IP capturing. + response = session.get(self.http_url) + + self.assert_success_span(response) + + def assert_span(self, num_spans=1): # TODO: Move this to TestBase + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(num_spans, len(span_list)) + if num_spans == 0: + return None + self.memory_exporter.clear() + if num_spans == 1: + return span_list[0] + return span_list + + def assert_success_span(self, response: requests.Response): + self.assertEqual("Hello!", response.text) + + span = self.assert_span() + self.assertIs(trace.SpanKind.CLIENT, span.kind) + self.assertEqual("HTTP GET", span.name) + + attributes = { + "http.status_code": 200, + "net.peer.ip": self.assert_ip, + } + self.assertGreaterEqual(span.attributes.items(), attributes.items()) diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg b/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg index 6bacfc08fe..725db50943 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg @@ -41,6 +41,7 @@ install_requires = opentelemetry-api ~= 1.3 opentelemetry-semantic-conventions == 0.24b0 opentelemetry-instrumentation == 0.24b0 + opentelemetry-instrumentation-http-base == 0.24b0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py index 240ab29477..1a9f51cf1b 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py @@ -69,6 +69,9 @@ def response_hook(span, request, response): import wrapt from opentelemetry import context +from opentelemetry.instrumentation.http_base import ( + set_ip_on_next_http_connection, +) from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.urllib3.package import _instruments from opentelemetry.instrumentation.urllib3.version import __version__ @@ -168,7 +171,7 @@ def instrumented_urlopen(wrapped, instance, args, kwargs): with tracer.start_as_current_span( span_name, kind=SpanKind.CLIENT, attributes=span_attributes - ) as span: + ) as span, set_ip_on_next_http_connection(span): if callable(request_hook): request_hook(span, instance, headers, body) inject(headers) diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py new file mode 100644 index 0000000000..8334f70308 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py @@ -0,0 +1,86 @@ +# Copyright The OpenTelemetry Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import urllib3 +import urllib3.exceptions + +from opentelemetry import trace +from opentelemetry.instrumentation.http_base import HttpClientInstrumentor +from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor +from opentelemetry.test.httptest import HttpTestBase +from opentelemetry.test.test_base import TestBase + + +class TestURLLib3InstrumentorWithRealSocket(HttpTestBase, TestBase): + def setUp(self): + super().setUp() + self.assert_ip = self.server.server_address[0] + self.http_host = ":".join(map(str, self.server.server_address[:2])) + self.http_url_base = "http://" + self.http_host + self.http_url = self.http_url_base + "/status/200" + HttpClientInstrumentor().instrument() + URLLib3Instrumentor().instrument() + + def tearDown(self): + super().tearDown() + HttpClientInstrumentor().uninstrument() + URLLib3Instrumentor().uninstrument() + + @staticmethod + def perform_request(url: str) -> urllib3.response.HTTPResponse: + with urllib3.PoolManager() as pool: + resp = pool.request("GET", url) + resp.close() + return resp + + def test_basic_http_success(self): + response = self.perform_request(self.http_url) + self.assert_success_span(response, self.http_url) + + def test_basic_http_success_using_connection_pool(self): + with urllib3.HTTPConnectionPool(self.http_host, timeout=3) as pool: + response = pool.request("GET", "/status/200") + + self.assert_success_span(response, self.http_url) + + # Test that when re-using an existing connection, everything still works. + # Especially relevant for IP capturing. + response = pool.request("GET", "/status/200") + + self.assert_success_span(response, self.http_url) + + def assert_span(self, num_spans=1): + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(num_spans, len(span_list)) + if num_spans == 0: + return None + self.memory_exporter.clear() + if num_spans == 1: + return span_list[0] + return span_list + + def assert_success_span( + self, response: urllib3.response.HTTPResponse, url: str + ): + self.assertEqual(b"Hello!", response.data) + + span = self.assert_span() + self.assertIs(trace.SpanKind.CLIENT, span.kind) + self.assertEqual("HTTP GET", span.name) + + attributes = { + "http.status_code": 200, + "net.peer.ip": self.assert_ip, + } + self.assertGreaterEqual(span.attributes.items(), attributes.items()) diff --git a/tox.ini b/tox.ini index 77225fce46..ff86cfda88 100644 --- a/tox.ini +++ b/tox.ini @@ -244,6 +244,7 @@ commands_pre = falcon,flask,django,pyramid,tornado,starlette,fastapi,aiohttp,asgi,requests,urllib,wsgi: pip install {toxinidir}/util/opentelemetry-util-http[test] wsgi,falcon,flask,django,pyramid: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-wsgi[test] asgi,starlette,fastapi: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi[test] + urllib3,requests: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-http-base asyncpg: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asyncpg[test] From f7cdf1c6384040e4d3f3386872bc68735911d533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 6 Sep 2021 15:33:52 +0200 Subject: [PATCH 03/14] Update core repo SHA (to PR branch). --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2926a019aa..d3884f5631 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ on: - 'release/*' pull_request: env: - CORE_REPO_SHA: c49ad57bfe35cfc69bfa863d74058ca9bec55fc3 + CORE_REPO_SHA: d31823d8c2b3ec0db55b181ea1f97aef3e89d37f # TODO: Update to main jobs: build: From ce61d454b48106f59bb95999fac14ead8991e275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 6 Sep 2021 15:42:50 +0200 Subject: [PATCH 04/14] Add CHANGELOG entry. --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42168a6ce7..44f8369988 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-urllib3` Updated `_RequestHookT` with two additional fields - the request body and the request headers ([#660](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/660)) +### Added + +- `opentelemetry-instrumentation-urllib3`, `opentelemetry-instrumentation-requests` + The `net.peer.ip` attribute is set to the IP of the connected HTTP server or proxy + using the new `opententelemetry-instrumentation-http-base` package + ([#661](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/661)) + ## [1.5.0-0.24b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.5.0-0.24b0) - 2021-08-26 ### Added From 1e37e5d6cf0318a86a2fd000bbd250eaea66172f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 6 Sep 2021 15:46:21 +0200 Subject: [PATCH 05/14] Fix tox -e lint installation order. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index ff86cfda88..82e9095dab 100644 --- a/tox.ini +++ b/tox.ini @@ -383,8 +383,8 @@ commands_pre = python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-aiopg[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlite3[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-pyramid[test] - python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-requests[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-http-base[test] + python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-requests[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-urllib[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-urllib3[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-pymysql[test] From d60536f115d5725dcf6b87c490b56ba5af4ab939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 6 Sep 2021 15:47:34 +0200 Subject: [PATCH 06/14] tox -e generate. --- instrumentation/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/instrumentation/README.md b/instrumentation/README.md index 8c59661d55..ceb71330bf 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -15,6 +15,7 @@ | [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | | [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0, < 3.0 | | [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio ~= 1.27 | +| [opentelemetry-instrumentation-http-base](./opentelemetry-instrumentation-http-base) | http_base | | [opentelemetry-instrumentation-httpx](./opentelemetry-instrumentation-httpx) | httpx >= 0.18.0, < 0.19.0 | | [opentelemetry-instrumentation-jinja2](./opentelemetry-instrumentation-jinja2) | jinja2~=2.7 | | [opentelemetry-instrumentation-logging](./opentelemetry-instrumentation-logging) | logging | From 9b4db03b9856186bc071214d6b0bb532c3507773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 6 Sep 2021 15:59:23 +0200 Subject: [PATCH 07/14] Update to newer core PR revision. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d3884f5631..caee2faa39 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ on: - 'release/*' pull_request: env: - CORE_REPO_SHA: d31823d8c2b3ec0db55b181ea1f97aef3e89d37f # TODO: Update to main + CORE_REPO_SHA: 62bb05935a165dc38e28ceaed10b39d79553d00e # TODO: Update to main jobs: build: From b02f1d3f98433a004ecb5f2a9732d2e048339722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Tue, 14 Sep 2021 14:47:14 +0200 Subject: [PATCH 08/14] Merge instrumentation-http-base into util-http. --- instrumentation/README.md | 3 +- .../MANIFEST.in | 9 -- .../README.rst | 25 ----- .../setup.cfg | 40 -------- .../setup.py | 99 ------------------- .../instrumentation/http_base/package.py | 14 --- .../instrumentation/http_base/version.py | 15 --- .../tests/__init__.py | 0 .../setup.cfg | 1 - .../instrumentation/requests/__init__.py | 4 +- .../tests/test_requests_ip_support.py | 2 +- .../setup.cfg | 2 +- .../instrumentation/urllib3/__init__.py | 4 +- .../tests/test_urllib3_ip_support.py | 2 +- tox.ini | 11 +-- .../src/opentelemetry/util/http/httplib.py | 3 +- .../tests/test_http_base.py | 8 +- 17 files changed, 12 insertions(+), 230 deletions(-) delete mode 100644 instrumentation/opentelemetry-instrumentation-http-base/MANIFEST.in delete mode 100644 instrumentation/opentelemetry-instrumentation-http-base/README.rst delete mode 100644 instrumentation/opentelemetry-instrumentation-http-base/setup.cfg delete mode 100644 instrumentation/opentelemetry-instrumentation-http-base/setup.py delete mode 100644 instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/package.py delete mode 100644 instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/version.py delete mode 100644 instrumentation/opentelemetry-instrumentation-http-base/tests/__init__.py rename instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/__init__.py => util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py (98%) rename {instrumentation/opentelemetry-instrumentation-http-base => util/opentelemetry-util-http}/tests/test_http_base.py (98%) diff --git a/instrumentation/README.md b/instrumentation/README.md index ceb71330bf..bb426c3bc3 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -15,7 +15,6 @@ | [opentelemetry-instrumentation-fastapi](./opentelemetry-instrumentation-fastapi) | fastapi ~= 0.58 | | [opentelemetry-instrumentation-flask](./opentelemetry-instrumentation-flask) | flask >= 1.0, < 3.0 | | [opentelemetry-instrumentation-grpc](./opentelemetry-instrumentation-grpc) | grpcio ~= 1.27 | -| [opentelemetry-instrumentation-http-base](./opentelemetry-instrumentation-http-base) | http_base | | [opentelemetry-instrumentation-httpx](./opentelemetry-instrumentation-httpx) | httpx >= 0.18.0, < 0.19.0 | | [opentelemetry-instrumentation-jinja2](./opentelemetry-instrumentation-jinja2) | jinja2~=2.7 | | [opentelemetry-instrumentation-logging](./opentelemetry-instrumentation-logging) | logging | @@ -34,4 +33,4 @@ | [opentelemetry-instrumentation-tornado](./opentelemetry-instrumentation-tornado) | tornado >= 6.0 | | [opentelemetry-instrumentation-urllib](./opentelemetry-instrumentation-urllib) | urllib | | [opentelemetry-instrumentation-urllib3](./opentelemetry-instrumentation-urllib3) | urllib3 >= 1.0.0, < 2.0.0 | -| [opentelemetry-instrumentation-wsgi](./opentelemetry-instrumentation-wsgi) | wsgi | \ No newline at end of file +| [opentelemetry-instrumentation-wsgi](./opentelemetry-instrumentation-wsgi) | wsgi | diff --git a/instrumentation/opentelemetry-instrumentation-http-base/MANIFEST.in b/instrumentation/opentelemetry-instrumentation-http-base/MANIFEST.in deleted file mode 100644 index aed3e33273..0000000000 --- a/instrumentation/opentelemetry-instrumentation-http-base/MANIFEST.in +++ /dev/null @@ -1,9 +0,0 @@ -graft src -graft tests -global-exclude *.pyc -global-exclude *.pyo -global-exclude __pycache__/* -include CHANGELOG.md -include MANIFEST.in -include README.rst -include LICENSE diff --git a/instrumentation/opentelemetry-instrumentation-http-base/README.rst b/instrumentation/opentelemetry-instrumentation-http-base/README.rst deleted file mode 100644 index fcd6c64a23..0000000000 --- a/instrumentation/opentelemetry-instrumentation-http-base/README.rst +++ /dev/null @@ -1,25 +0,0 @@ -http.client helper instrumentation -================================== - -|pypi| - -.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-http-base.svg - :target: https://pypi.org/project/opentelemetry-instrumentation-http-base/ - - -This library provides functionality to enrich HTTP client spans with IPs. It does -not create spans on its own. - -Installation ------------- - -:: - - pip install opentelemetry-instrumentation-http-base - - -References ----------- - -* `OpenTelemetry Project `_ -* `OpenTelemetry Python Examples `_ diff --git a/instrumentation/opentelemetry-instrumentation-http-base/setup.cfg b/instrumentation/opentelemetry-instrumentation-http-base/setup.cfg deleted file mode 100644 index b00b1e4b5f..0000000000 --- a/instrumentation/opentelemetry-instrumentation-http-base/setup.cfg +++ /dev/null @@ -1,40 +0,0 @@ -[metadata] -name = opentelemetry-instrumentation-http-base -description = Basic HTTP instrumentation to get an IP address -long_description = file: README.rst -long_description_content_type = text/x-rst -author = OpenTelemetry Authors -author_email = cncf-opentelemetry-contributors@lists.cncf.io -url = https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-http-base -platforms = any -license = Apache-2.0 -classifiers = - Development Status :: 4 - Beta - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - -[options] -python_requires = >=3.6 -package_dir= - =src -packages=find_namespace: -install_requires = - opentelemetry-api ~= 1.3 - opentelemetry-semantic-conventions == 0.24b0 - opentelemetry-instrumentation == 0.24b0 - -[options.extras_require] -test = - opentelemetry-test == 0.24b0 - -[options.packages.find] -where = src - -[options.entry_points] -opentelemetry_instrumentor = - http_client = opentelemetry.instrumentation.http_base:HttpClientInstrumentor diff --git a/instrumentation/opentelemetry-instrumentation-http-base/setup.py b/instrumentation/opentelemetry-instrumentation-http-base/setup.py deleted file mode 100644 index b9f3efba83..0000000000 --- a/instrumentation/opentelemetry-instrumentation-http-base/setup.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright The OpenTelemetry Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - - -# DO NOT EDIT. THIS FILE WAS AUTOGENERATED FROM templates/instrumentation_setup.py.txt. -# RUN `python scripts/generate_setup.py` TO REGENERATE. - - -import distutils.cmd -import json -import os -from configparser import ConfigParser - -import setuptools - -config = ConfigParser() -config.read("setup.cfg") - -# We provide extras_require parameter to setuptools.setup later which -# overwrites the extra_require section from setup.cfg. To support extra_require -# secion in setup.cfg, we load it here and merge it with the extra_require param. -extras_require = {} -if "options.extras_require" in config: - for key, value in config["options.extras_require"].items(): - extras_require[key] = [v for v in value.split("\n") if v.strip()] - -BASE_DIR = os.path.dirname(__file__) -PACKAGE_INFO = {} - -VERSION_FILENAME = os.path.join( - BASE_DIR, - "src", - "opentelemetry", - "instrumentation", - "http_base", - "version.py", -) -with open(VERSION_FILENAME) as f: - exec(f.read(), PACKAGE_INFO) - -PACKAGE_FILENAME = os.path.join( - BASE_DIR, - "src", - "opentelemetry", - "instrumentation", - "http_base", - "package.py", -) -with open(PACKAGE_FILENAME) as f: - exec(f.read(), PACKAGE_INFO) - -# Mark any instruments/runtime dependencies as test dependencies as well. -extras_require["instruments"] = PACKAGE_INFO["_instruments"] -test_deps = extras_require.get("test", []) -for dep in extras_require["instruments"]: - test_deps.append(dep) - -extras_require["test"] = test_deps - - -class JSONMetadataCommand(distutils.cmd.Command): - - description = ( - "print out package metadata as JSON. This is used by OpenTelemetry dev scripts to ", - "auto-generate code in other places", - ) - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - metadata = { - "name": config["metadata"]["name"], - "version": PACKAGE_INFO["__version__"], - "instruments": PACKAGE_INFO["_instruments"], - } - print(json.dumps(metadata)) - - -setuptools.setup( - cmdclass={"meta": JSONMetadataCommand}, - version=PACKAGE_INFO["__version__"], - extras_require=extras_require, -) diff --git a/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/package.py b/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/package.py deleted file mode 100644 index 5088c610ce..0000000000 --- a/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/package.py +++ /dev/null @@ -1,14 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -_instruments = tuple() diff --git a/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/version.py b/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/version.py deleted file mode 100644 index d33bd87ce4..0000000000 --- a/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/version.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright The OpenTelemetry Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -__version__ = "0.24b0" diff --git a/instrumentation/opentelemetry-instrumentation-http-base/tests/__init__.py b/instrumentation/opentelemetry-instrumentation-http-base/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/instrumentation/opentelemetry-instrumentation-requests/setup.cfg b/instrumentation/opentelemetry-instrumentation-requests/setup.cfg index e3031e358b..f947b6d830 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-requests/setup.cfg @@ -42,7 +42,6 @@ install_requires = opentelemetry-semantic-conventions == 0.24b0 opentelemetry-instrumentation == 0.24b0 opentelemetry-util-http == 0.24b0 - opentelemetry-instrumentation-http-base == 0.24b0 [options.extras_require] test = diff --git a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py index 2e7242d862..e3ba20a6a0 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-requests/src/opentelemetry/instrumentation/requests/__init__.py @@ -41,9 +41,6 @@ from requests.structures import CaseInsensitiveDict from opentelemetry import context -from opentelemetry.instrumentation.http_base import ( - set_ip_on_next_http_connection, -) from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.requests.package import _instruments from opentelemetry.instrumentation.requests.version import __version__ @@ -56,6 +53,7 @@ from opentelemetry.trace import SpanKind, get_tracer from opentelemetry.trace.status import Status from opentelemetry.util.http import remove_url_credentials +from opentelemetry.util.http.httplib import set_ip_on_next_http_connection # A key to a context variable to avoid creating duplicate spans when instrumenting # both, Session.request and Session.send, since Session.request calls into Session.send diff --git a/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_ip_support.py b/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_ip_support.py index df23f86390..593ed92fe9 100644 --- a/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_ip_support.py +++ b/instrumentation/opentelemetry-instrumentation-requests/tests/test_requests_ip_support.py @@ -15,10 +15,10 @@ import requests from opentelemetry import trace -from opentelemetry.instrumentation.http_base import HttpClientInstrumentor from opentelemetry.instrumentation.requests import RequestsInstrumentor from opentelemetry.test.httptest import HttpTestBase from opentelemetry.test.test_base import TestBase +from opentelemetry.util.http.httplib import HttpClientInstrumentor class TestURLLib3InstrumentorWithRealSocket(HttpTestBase, TestBase): diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg b/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg index 725db50943..7916cbe701 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-urllib3/setup.cfg @@ -41,7 +41,7 @@ install_requires = opentelemetry-api ~= 1.3 opentelemetry-semantic-conventions == 0.24b0 opentelemetry-instrumentation == 0.24b0 - opentelemetry-instrumentation-http-base == 0.24b0 + opentelemetry-util-http == 0.24b0 wrapt >= 1.0.0, < 2.0.0 [options.extras_require] diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py index 1a9f51cf1b..d0bb977f74 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/src/opentelemetry/instrumentation/urllib3/__init__.py @@ -69,9 +69,6 @@ def response_hook(span, request, response): import wrapt from opentelemetry import context -from opentelemetry.instrumentation.http_base import ( - set_ip_on_next_http_connection, -) from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.urllib3.package import _instruments from opentelemetry.instrumentation.urllib3.version import __version__ @@ -84,6 +81,7 @@ def response_hook(span, request, response): from opentelemetry.semconv.trace import SpanAttributes from opentelemetry.trace import Span, SpanKind, get_tracer from opentelemetry.trace.status import Status +from opentelemetry.util.http.httplib import set_ip_on_next_http_connection # A key to a context variable to avoid creating duplicate spans when instrumenting # both, Session.request and Session.send, since Session.request calls into Session.send diff --git a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py index 8334f70308..5baddee516 100644 --- a/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py +++ b/instrumentation/opentelemetry-instrumentation-urllib3/tests/test_urllib3_ip_support.py @@ -16,10 +16,10 @@ import urllib3.exceptions from opentelemetry import trace -from opentelemetry.instrumentation.http_base import HttpClientInstrumentor from opentelemetry.instrumentation.urllib3 import URLLib3Instrumentor from opentelemetry.test.httptest import HttpTestBase from opentelemetry.test.test_base import TestBase +from opentelemetry.util.http.httplib import HttpClientInstrumentor class TestURLLib3InstrumentorWithRealSocket(HttpTestBase, TestBase): diff --git a/tox.ini b/tox.ini index 82e9095dab..b608b99aec 100644 --- a/tox.ini +++ b/tox.ini @@ -145,10 +145,6 @@ envlist = py3{6,7,8,9}-test-instrumentation-httpx pypy3-test-instrumentation-httpx - ; opentelemetry-instrumentation-http-base - py3{6,7,8,9}-test-instrumentation-http-base - pypy3-test-instrumentation-http-base - ; opentelemetry-util-http py3{6,7,8,9}-test-util-http pypy3-test-util-http @@ -220,7 +216,6 @@ changedir = test-instrumentation-tornado: instrumentation/opentelemetry-instrumentation-tornado/tests test-instrumentation-wsgi: instrumentation/opentelemetry-instrumentation-wsgi/tests test-instrumentation-httpx: instrumentation/opentelemetry-instrumentation-httpx/tests - test-instrumentation-http-base: instrumentation/opentelemetry-instrumentation-http-base/tests test-util-http: util/opentelemetry-util-http/tests test-sdkextension-aws: sdk-extension/opentelemetry-sdk-extension-aws/tests test-propagator-ot-trace: propagator/opentelemetry-propagator-ot-trace/tests @@ -241,10 +236,9 @@ commands_pre = grpc: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-grpc[test] - falcon,flask,django,pyramid,tornado,starlette,fastapi,aiohttp,asgi,requests,urllib,wsgi: pip install {toxinidir}/util/opentelemetry-util-http[test] + falcon,flask,django,pyramid,tornado,starlette,fastapi,aiohttp,asgi,requests,urllib,urllib3,wsgi: pip install {toxinidir}/util/opentelemetry-util-http[test] wsgi,falcon,flask,django,pyramid: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-wsgi[test] asgi,starlette,fastapi: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asgi[test] - urllib3,requests: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-http-base asyncpg: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-asyncpg[test] @@ -307,8 +301,6 @@ commands_pre = httpx: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-httpx[test] - http-base: pip install {toxinidir}/instrumentation/opentelemetry-instrumentation-http-base[test] - aws: pip install requests {toxinidir}/sdk-extension/opentelemetry-sdk-extension-aws[test] http: pip install {toxinidir}/util/opentelemetry-util-http[test] @@ -383,7 +375,6 @@ commands_pre = python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-aiopg[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-sqlite3[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-pyramid[test] - python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-http-base[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-requests[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-urllib[test] python -m pip install -e {toxinidir}/instrumentation/opentelemetry-instrumentation-urllib3[test] diff --git a/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/__init__.py b/util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py similarity index 98% rename from instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/__init__.py rename to util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py index b7bcca216c..11ed256882 100644 --- a/instrumentation/opentelemetry-instrumentation-http-base/src/opentelemetry/instrumentation/http_base/__init__.py +++ b/util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py @@ -27,7 +27,6 @@ import wrapt from opentelemetry import context -from opentelemetry.instrumentation.http_base.package import _instruments from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.utils import unwrap from opentelemetry.semconv.trace import SpanAttributes @@ -40,7 +39,7 @@ class HttpClientInstrumentor(BaseInstrumentor): def instrumentation_dependencies(self) -> Collection[str]: - return _instruments + return () def _instrument(self, **kwargs): """Instruments the http.client module (not creating spans on its own)""" diff --git a/instrumentation/opentelemetry-instrumentation-http-base/tests/test_http_base.py b/util/opentelemetry-util-http/tests/test_http_base.py similarity index 98% rename from instrumentation/opentelemetry-instrumentation-http-base/tests/test_http_base.py rename to util/opentelemetry-util-http/tests/test_http_base.py index cf2f08a4bb..13f87a8f93 100644 --- a/instrumentation/opentelemetry-instrumentation-http-base/tests/test_http_base.py +++ b/util/opentelemetry-util-http/tests/test_http_base.py @@ -15,13 +15,13 @@ from http.client import HTTPConnection, HTTPResponse, HTTPSConnection from opentelemetry import trace -from opentelemetry.instrumentation.http_base import ( - HttpClientInstrumentor, - set_ip_on_next_http_connection, -) from opentelemetry.test.httptest import HttpTestBase from opentelemetry.test.test_base import TestBase from opentelemetry.trace.span import INVALID_SPAN +from opentelemetry.util.http.httplib import ( + HttpClientInstrumentor, + set_ip_on_next_http_connection, +) # pylint: disable=too-many-public-methods From b0437e3802417a1a07ea067ed883e6e46672d1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Tue, 14 Sep 2021 14:49:21 +0200 Subject: [PATCH 09/14] Bump core version. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index caee2faa39..c0bc4cb7b2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ on: - 'release/*' pull_request: env: - CORE_REPO_SHA: 62bb05935a165dc38e28ceaed10b39d79553d00e # TODO: Update to main + CORE_REPO_SHA: f4b9ac20023395e077f47831a04a1a6b473c3406 # TODO: Update to main jobs: build: From 11b4a32270dbfb610f76f4c3a52061663aacb761 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Tue, 14 Sep 2021 14:52:35 +0200 Subject: [PATCH 10/14] Remove final newline as generate wants it. --- instrumentation/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instrumentation/README.md b/instrumentation/README.md index bb426c3bc3..8c59661d55 100644 --- a/instrumentation/README.md +++ b/instrumentation/README.md @@ -33,4 +33,4 @@ | [opentelemetry-instrumentation-tornado](./opentelemetry-instrumentation-tornado) | tornado >= 6.0 | | [opentelemetry-instrumentation-urllib](./opentelemetry-instrumentation-urllib) | urllib | | [opentelemetry-instrumentation-urllib3](./opentelemetry-instrumentation-urllib3) | urllib3 >= 1.0.0, < 2.0.0 | -| [opentelemetry-instrumentation-wsgi](./opentelemetry-instrumentation-wsgi) | wsgi | +| [opentelemetry-instrumentation-wsgi](./opentelemetry-instrumentation-wsgi) | wsgi | \ No newline at end of file From daf3c944403edefdd1a551cfe7a0f26633a8dc77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Tue, 14 Sep 2021 15:20:14 +0200 Subject: [PATCH 11/14] Pylint doesn't notice #type: comment. --- .../src/opentelemetry/util/http/httplib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py b/util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py index 11ed256882..9b14a8ca76 100644 --- a/util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py +++ b/util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py @@ -20,7 +20,7 @@ import contextlib import http.client import logging -import socket +import socket # pylint:disable=unused-import # Used for typing import typing from typing import Collection From d1d334251278e3b40cbb2446dfb729e4a933fa77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Wed, 15 Sep 2021 10:45:04 +0200 Subject: [PATCH 12/14] Fix CHANGELOG. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0392a5a98..a388ae49c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `opentelemetry-instrumentation-urllib3`, `opentelemetry-instrumentation-requests` The `net.peer.ip` attribute is set to the IP of the connected HTTP server or proxy - using the new `opententelemetry-instrumentation-http-base` package + using a new instrumentor in `opententelemetry-util-http` ([#661](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/661)) ## [1.5.0-0.24b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.5.0-0.24b0) - 2021-08-26 From bd83dc6a0735e3b7ba6c9a07daeeb4957381f7ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Wed, 15 Sep 2021 10:47:11 +0200 Subject: [PATCH 13/14] Add entrypoint. --- util/opentelemetry-util-http/setup.cfg | 4 ++++ .../src/opentelemetry/util/http/httplib.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/util/opentelemetry-util-http/setup.cfg b/util/opentelemetry-util-http/setup.cfg index e866a771b6..aca43df5dd 100644 --- a/util/opentelemetry-util-http/setup.cfg +++ b/util/opentelemetry-util-http/setup.cfg @@ -40,3 +40,7 @@ packages=find_namespace: [options.packages.find] where = src + +[options.entry_points] +opentelemetry_instrumentor = + httplib = opentelemetry.util.http.httplib:HttpClientInstrumentor diff --git a/util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py b/util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py index 9b14a8ca76..b4aac01124 100644 --- a/util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py +++ b/util/opentelemetry-util-http/src/opentelemetry/util/http/httplib.py @@ -39,7 +39,7 @@ class HttpClientInstrumentor(BaseInstrumentor): def instrumentation_dependencies(self) -> Collection[str]: - return () + return () # This instruments http.client from stdlib; no extra deps. def _instrument(self, **kwargs): """Instruments the http.client module (not creating spans on its own)""" From 5532a3ad783888642085dec11194f32ff8df4af9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Neum=C3=BCller?= Date: Mon, 20 Sep 2021 19:01:30 +0200 Subject: [PATCH 14/14] Update to main branch of core repo (from PR branch) --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c0bc4cb7b2..e0d1ea9b4d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,7 +6,7 @@ on: - 'release/*' pull_request: env: - CORE_REPO_SHA: f4b9ac20023395e077f47831a04a1a6b473c3406 # TODO: Update to main + CORE_REPO_SHA: d9c22a87b6bfc5ec332588c764f82c32f068b2c3 jobs: build: