From d19b464a0a23a4a66d5df635634e7d6315cb54db Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Mon, 3 Aug 2020 10:10:45 -0700 Subject: [PATCH 1/8] Rename web framework packages from "ext" to "instrumentation" (#961) --- .../CHANGELOG.md | 40 +++ .../LICENSE | 201 +++++++++++ .../MANIFEST.in | 9 + .../README.rst | 26 ++ .../setup.cfg | 51 +++ .../setup.py | 26 ++ .../instrumentation/wsgi/__init__.py | 233 ++++++++++++ .../instrumentation/wsgi/version.py | 15 + .../tests/__init__.py | 0 .../tests/test_wsgi_middleware.py | 334 ++++++++++++++++++ 10 files changed, 935 insertions(+) create mode 100644 instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md create mode 100644 instrumentation/opentelemetry-instrumentation-wsgi/LICENSE create mode 100644 instrumentation/opentelemetry-instrumentation-wsgi/MANIFEST.in create mode 100644 instrumentation/opentelemetry-instrumentation-wsgi/README.rst create mode 100644 instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg create mode 100644 instrumentation/opentelemetry-instrumentation-wsgi/setup.py create mode 100644 instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py create mode 100644 instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py create mode 100644 instrumentation/opentelemetry-instrumentation-wsgi/tests/__init__.py create mode 100644 instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md b/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md new file mode 100644 index 000000000000..6f1085b8bb44 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md @@ -0,0 +1,40 @@ +# Changelog + +## Unreleased + +- Change package name to opentelemetry-instrumentation-wsgi + ([#961](https://github.com/open-telemetry/opentelemetry-python/pull/961)) + +## Version 0.11b0 + +Released 2020-07-28 + +- Set span status on wsgi errors + ([#864](https://github.com/open-telemetry/opentelemetry-python/pull/864)) + +## 0.4a0 + +Released 2020-02-21 + +- Updating network connection attribute names + ([#350](https://github.com/open-telemetry/opentelemetry-python/pull/350)) + +## 0.3a0 + +Released 2019-12-11 + +- Support new semantic conventions + ([#299](https://github.com/open-telemetry/opentelemetry-python/pull/299)) +- Updates for core library changes + +## 0.2a0 + +Released 2019-10-29 + +- Updates for core library changes + +## 0.1a0 + +Released 2019-09-30 + +- Initial release diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/LICENSE b/instrumentation/opentelemetry-instrumentation-wsgi/LICENSE new file mode 100644 index 000000000000..261eeb9e9f8b --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-wsgi/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/MANIFEST.in b/instrumentation/opentelemetry-instrumentation-wsgi/MANIFEST.in new file mode 100644 index 000000000000..aed3e33273b8 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-wsgi/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-wsgi/README.rst b/instrumentation/opentelemetry-instrumentation-wsgi/README.rst new file mode 100644 index 000000000000..ac39dac0c7c0 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-wsgi/README.rst @@ -0,0 +1,26 @@ +OpenTelemetry WSGI Middleware +============================= + +|pypi| + +.. |pypi| image:: https://badge.fury.io/py/opentelemetry-instrumentation-wsgi.svg + :target: https://pypi.org/project/opentelemetry-instrumentation-wsgi/ + + +This library provides a WSGI middleware that can be used on any WSGI framework +(such as Django / Flask) to track requests timing through OpenTelemetry. + +Installation +------------ + +:: + + pip install opentelemetry-instrumentation-wsgi + + +References +---------- + +* `OpenTelemetry WSGI Middleware `_ +* `OpenTelemetry Project `_ +* `WSGI `_ diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg new file mode 100644 index 000000000000..b48d61dc849f --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg @@ -0,0 +1,51 @@ +# 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. +# +[metadata] +name = opentelemetry-instrumentation-wsgi +description = WSGI Middleware for OpenTelemetry +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/tree/master/instrumentation/opentelemetry-instrumentation-wsgi +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.4 + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +python_requires = >=3.4 +package_dir= + =src +packages=find_namespace: +install_requires = + opentelemetry-api == 0.12.dev0 + opentelemetry-instrumentation == 0.12.dev0 + +[options.extras_require] +test = + opentelemetry-test == 0.12.dev0 + +[options.packages.find] +where = src diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/setup.py b/instrumentation/opentelemetry-instrumentation-wsgi/setup.py new file mode 100644 index 000000000000..fb4ffa7437bf --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-wsgi/setup.py @@ -0,0 +1,26 @@ +# 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 os + +import setuptools + +BASE_DIR = os.path.dirname(__file__) +VERSION_FILENAME = os.path.join( + BASE_DIR, "src", "opentelemetry", "instrumentation", "wsgi", "version.py" +) +PACKAGE_INFO = {} +with open(VERSION_FILENAME) as f: + exec(f.read(), PACKAGE_INFO) + +setuptools.setup(version=PACKAGE_INFO["__version__"]) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py new file mode 100644 index 000000000000..8a2d0bec0855 --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py @@ -0,0 +1,233 @@ +# 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 a WSGI middleware that can be used on any WSGI framework +(such as Django / Flask) to track requests timing through OpenTelemetry. + +Usage (Flask) +------------- + +.. code-block:: python + + from flask import Flask + from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware + + app = Flask(__name__) + app.wsgi_app = OpenTelemetryMiddleware(app.wsgi_app) + + @app.route("/") + def hello(): + return "Hello!" + + if __name__ == "__main__": + app.run(debug=True) + + +Usage (Django) +-------------- + +Modify the application's ``wsgi.py`` file as shown below. + +.. code-block:: python + + import os + from opentelemetry.instrumentation.wsgi import OpenTelemetryMiddleware + from django.core.wsgi import get_wsgi_application + + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') + + application = get_wsgi_application() + application = OpenTelemetryMiddleware(application) + +API +--- +""" + +import functools +import typing +import wsgiref.util as wsgiref_util + +from opentelemetry import context, propagators, trace +from opentelemetry.instrumentation.utils import http_status_to_canonical_code +from opentelemetry.instrumentation.wsgi.version import __version__ +from opentelemetry.trace.status import Status, StatusCanonicalCode + +_HTTP_VERSION_PREFIX = "HTTP/" + + +def get_header_from_environ( + environ: dict, header_name: str +) -> typing.List[str]: + """Retrieve a HTTP header value from the PEP3333-conforming WSGI environ. + + Returns: + A list with a single string with the header value if it exists, else an empty list. + """ + environ_key = "HTTP_" + header_name.upper().replace("-", "_") + value = environ.get(environ_key) + if value is not None: + return [value] + return [] + + +def setifnotnone(dic, key, value): + if value is not None: + dic[key] = value + + +def collect_request_attributes(environ): + """Collects HTTP request attributes from the PEP3333-conforming + WSGI environ and returns a dictionary to be used as span creation attributes.""" + + result = { + "component": "http", + "http.method": environ.get("REQUEST_METHOD"), + "http.server_name": environ.get("SERVER_NAME"), + "http.scheme": environ.get("wsgi.url_scheme"), + } + + host_port = environ.get("SERVER_PORT") + if host_port is not None: + result.update({"host.port": int(host_port)}) + + setifnotnone(result, "http.host", environ.get("HTTP_HOST")) + target = environ.get("RAW_URI") + if target is None: # Note: `"" or None is None` + target = environ.get("REQUEST_URI") + if target is not None: + result["http.target"] = target + else: + result["http.url"] = wsgiref_util.request_uri(environ) + + remote_addr = environ.get("REMOTE_ADDR") + if remote_addr: + result["net.peer.ip"] = remote_addr + remote_host = environ.get("REMOTE_HOST") + if remote_host and remote_host != remote_addr: + result["net.peer.name"] = remote_host + + setifnotnone(result, "net.peer.port", environ.get("REMOTE_PORT")) + flavor = environ.get("SERVER_PROTOCOL", "") + if flavor.upper().startswith(_HTTP_VERSION_PREFIX): + flavor = flavor[len(_HTTP_VERSION_PREFIX) :] + if flavor: + result["http.flavor"] = flavor + + return result + + +def add_response_attributes( + span, start_response_status, response_headers +): # pylint: disable=unused-argument + """Adds HTTP response attributes to span using the arguments + passed to a PEP3333-conforming start_response callable.""" + + status_code, status_text = start_response_status.split(" ", 1) + span.set_attribute("http.status_text", status_text) + + try: + status_code = int(status_code) + except ValueError: + span.set_status( + Status( + StatusCanonicalCode.UNKNOWN, + "Non-integer HTTP status: " + repr(status_code), + ) + ) + else: + span.set_attribute("http.status_code", status_code) + span.set_status(Status(http_status_to_canonical_code(status_code))) + + +def get_default_span_name(environ): + """Default implementation for name_callback, returns HTTP {METHOD_NAME}.""" + return "HTTP {}".format(environ.get("REQUEST_METHOD", "")).strip() + + +class OpenTelemetryMiddleware: + """The WSGI application middleware. + + This class is a PEP 3333 conforming WSGI middleware that starts and + annotates spans for any requests it is invoked with. + + Args: + wsgi: The WSGI application callable to forward requests to. + name_callback: Callback which calculates a generic span name for an + incoming HTTP request based on the PEP3333 WSGI environ. + Optional: Defaults to get_default_span_name. + """ + + def __init__(self, wsgi, name_callback=get_default_span_name): + self.wsgi = wsgi + self.tracer = trace.get_tracer(__name__, __version__) + self.name_callback = name_callback + + @staticmethod + def _create_start_response(span, start_response): + @functools.wraps(start_response) + def _start_response(status, response_headers, *args, **kwargs): + add_response_attributes(span, status, response_headers) + return start_response(status, response_headers, *args, **kwargs) + + return _start_response + + def __call__(self, environ, start_response): + """The WSGI application + + Args: + environ: A WSGI environment. + start_response: The WSGI start_response callable. + """ + + token = context.attach( + propagators.extract(get_header_from_environ, environ) + ) + span_name = self.name_callback(environ) + + span = self.tracer.start_span( + span_name, + kind=trace.SpanKind.SERVER, + attributes=collect_request_attributes(environ), + ) + + try: + with self.tracer.use_span(span): + start_response = self._create_start_response( + span, start_response + ) + iterable = self.wsgi(environ, start_response) + return _end_span_after_iterating( + iterable, span, self.tracer, token + ) + except Exception as ex: + span.set_status(Status(StatusCanonicalCode.INTERNAL, str(ex))) + span.end() + context.detach(token) + raise + + +# Put this in a subfunction to not delay the call to the wrapped +# WSGI application (instrumentation should change the application +# behavior as little as possible). +def _end_span_after_iterating(iterable, span, tracer, token): + try: + with tracer.use_span(span): + for yielded in iterable: + yield yielded + finally: + close = getattr(iterable, "close", None) + if close: + close() + span.end() + context.detach(token) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py new file mode 100644 index 000000000000..780a92b6a1bb --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/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.12.dev0" diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/tests/__init__.py b/instrumentation/opentelemetry-instrumentation-wsgi/tests/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py new file mode 100644 index 000000000000..5734027b0aae --- /dev/null +++ b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py @@ -0,0 +1,334 @@ +# 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 sys +import unittest +import unittest.mock as mock +import wsgiref.util as wsgiref_util +from urllib.parse import urlsplit + +import opentelemetry.instrumentation.wsgi as otel_wsgi +from opentelemetry import trace as trace_api +from opentelemetry.test.wsgitestutil import WsgiTestBase +from opentelemetry.trace.status import StatusCanonicalCode + + +class Response: + def __init__(self): + self.iter = iter([b"*"]) + self.close_calls = 0 + + def __iter__(self): + return self + + def __next__(self): + return next(self.iter) + + def close(self): + self.close_calls += 1 + + +def simple_wsgi(environ, start_response): + assert isinstance(environ, dict) + start_response("200 OK", [("Content-Type", "text/plain")]) + return [b"*"] + + +def create_iter_wsgi(response): + def iter_wsgi(environ, start_response): + assert isinstance(environ, dict) + start_response("200 OK", [("Content-Type", "text/plain")]) + return response + + return iter_wsgi + + +def create_gen_wsgi(response): + def gen_wsgi(environ, start_response): + result = create_iter_wsgi(response)(environ, start_response) + yield from result + getattr(result, "close", lambda: None)() + + return gen_wsgi + + +def error_wsgi(environ, start_response): + assert isinstance(environ, dict) + try: + raise ValueError + except ValueError: + exc_info = sys.exc_info() + start_response("200 OK", [("Content-Type", "text/plain")], exc_info) + exc_info = None + return [b"*"] + + +def error_wsgi_unhandled(environ, start_response): + assert isinstance(environ, dict) + raise ValueError + + +class TestWsgiApplication(WsgiTestBase): + def validate_response( + self, response, error=None, span_name="HTTP GET", http_method="GET" + ): + while True: + try: + value = next(response) + self.assertEqual(value, b"*") + except StopIteration: + break + + self.assertEqual(self.status, "200 OK") + self.assertEqual( + self.response_headers, [("Content-Type", "text/plain")] + ) + if error: + self.assertIs(self.exc_info[0], error) + self.assertIsInstance(self.exc_info[1], error) + self.assertIsNotNone(self.exc_info[2]) + else: + self.assertIsNone(self.exc_info) + + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + self.assertEqual(span_list[0].name, span_name) + self.assertEqual(span_list[0].kind, trace_api.SpanKind.SERVER) + expected_attributes = { + "component": "http", + "http.server_name": "127.0.0.1", + "http.scheme": "http", + "host.port": 80, + "http.host": "127.0.0.1", + "http.flavor": "1.0", + "http.url": "http://127.0.0.1/", + "http.status_text": "OK", + "http.status_code": 200, + } + if http_method is not None: + expected_attributes["http.method"] = http_method + self.assertEqual(span_list[0].attributes, expected_attributes) + + def test_basic_wsgi_call(self): + app = otel_wsgi.OpenTelemetryMiddleware(simple_wsgi) + response = app(self.environ, self.start_response) + self.validate_response(response) + + def test_wsgi_iterable(self): + original_response = Response() + iter_wsgi = create_iter_wsgi(original_response) + app = otel_wsgi.OpenTelemetryMiddleware(iter_wsgi) + response = app(self.environ, self.start_response) + # Verify that start_response has been called + self.assertTrue(self.status) + self.validate_response(response) + + # Verify that close has been called exactly once + self.assertEqual(1, original_response.close_calls) + + def test_wsgi_generator(self): + original_response = Response() + gen_wsgi = create_gen_wsgi(original_response) + app = otel_wsgi.OpenTelemetryMiddleware(gen_wsgi) + response = app(self.environ, self.start_response) + # Verify that start_response has not been called + self.assertIsNone(self.status) + self.validate_response(response) + + # Verify that close has been called exactly once + self.assertEqual(original_response.close_calls, 1) + + def test_wsgi_exc_info(self): + app = otel_wsgi.OpenTelemetryMiddleware(error_wsgi) + response = app(self.environ, self.start_response) + self.validate_response(response, error=ValueError) + + def test_wsgi_internal_error(self): + app = otel_wsgi.OpenTelemetryMiddleware(error_wsgi_unhandled) + self.assertRaises(ValueError, app, self.environ, self.start_response) + span_list = self.memory_exporter.get_finished_spans() + self.assertEqual(len(span_list), 1) + self.assertEqual( + span_list[0].status.canonical_code, StatusCanonicalCode.INTERNAL, + ) + + def test_override_span_name(self): + """Test that span_names can be overwritten by our callback function.""" + span_name = "Dymaxion" + + def get_predefined_span_name(scope): + # pylint: disable=unused-argument + return span_name + + app = otel_wsgi.OpenTelemetryMiddleware( + simple_wsgi, name_callback=get_predefined_span_name + ) + response = app(self.environ, self.start_response) + self.validate_response(response, span_name=span_name) + + def test_default_span_name_missing_request_method(self): + """Test that default span_names with missing request method.""" + self.environ.pop("REQUEST_METHOD") + app = otel_wsgi.OpenTelemetryMiddleware(simple_wsgi) + response = app(self.environ, self.start_response) + self.validate_response(response, span_name="HTTP", http_method=None) + + +class TestWsgiAttributes(unittest.TestCase): + def setUp(self): + self.environ = {} + wsgiref_util.setup_testing_defaults(self.environ) + self.span = mock.create_autospec(trace_api.Span, spec_set=True) + + def test_request_attributes(self): + self.environ["QUERY_STRING"] = "foo=bar" + + attrs = otel_wsgi.collect_request_attributes(self.environ) + self.assertDictEqual( + attrs, + { + "component": "http", + "http.method": "GET", + "http.host": "127.0.0.1", + "http.url": "http://127.0.0.1/?foo=bar", + "host.port": 80, + "http.scheme": "http", + "http.server_name": "127.0.0.1", + "http.flavor": "1.0", + }, + ) + + def validate_url(self, expected_url, raw=False, has_host=True): + parts = urlsplit(expected_url) + expected = { + "http.scheme": parts.scheme, + "host.port": parts.port or (80 if parts.scheme == "http" else 443), + "http.server_name": parts.hostname, # Not true in the general case, but for all tests. + } + if raw: + expected["http.target"] = expected_url.split(parts.netloc, 1)[1] + else: + expected["http.url"] = expected_url + if has_host: + expected["http.host"] = parts.hostname + + attrs = otel_wsgi.collect_request_attributes(self.environ) + self.assertGreaterEqual( + attrs.items(), expected.items(), expected_url + " expected." + ) + + def test_request_attributes_with_partial_raw_uri(self): + self.environ["RAW_URI"] = "/#top" + self.validate_url("http://127.0.0.1/#top", raw=True) + + def test_request_attributes_with_partial_raw_uri_and_nonstandard_port( + self, + ): + self.environ["RAW_URI"] = "/?" + del self.environ["HTTP_HOST"] + self.environ["SERVER_PORT"] = "8080" + self.validate_url("http://127.0.0.1:8080/?", raw=True, has_host=False) + + def test_https_uri_port(self): + del self.environ["HTTP_HOST"] + self.environ["SERVER_PORT"] = "443" + self.environ["wsgi.url_scheme"] = "https" + self.validate_url("https://127.0.0.1/", has_host=False) + + self.environ["SERVER_PORT"] = "8080" + self.validate_url("https://127.0.0.1:8080/", has_host=False) + + self.environ["SERVER_PORT"] = "80" + self.validate_url("https://127.0.0.1:80/", has_host=False) + + def test_http_uri_port(self): + del self.environ["HTTP_HOST"] + self.environ["SERVER_PORT"] = "80" + self.environ["wsgi.url_scheme"] = "http" + self.validate_url("http://127.0.0.1/", has_host=False) + + self.environ["SERVER_PORT"] = "8080" + self.validate_url("http://127.0.0.1:8080/", has_host=False) + + self.environ["SERVER_PORT"] = "443" + self.validate_url("http://127.0.0.1:443/", has_host=False) + + def test_request_attributes_with_nonstandard_port_and_no_host(self): + del self.environ["HTTP_HOST"] + self.environ["SERVER_PORT"] = "8080" + self.validate_url("http://127.0.0.1:8080/", has_host=False) + + self.environ["SERVER_PORT"] = "443" + self.validate_url("http://127.0.0.1:443/", has_host=False) + + def test_request_attributes_with_conflicting_nonstandard_port(self): + self.environ[ + "HTTP_HOST" + ] += ":8080" # Note that we do not correct SERVER_PORT + expected = { + "http.host": "127.0.0.1:8080", + "http.url": "http://127.0.0.1:8080/", + "host.port": 80, + } + self.assertGreaterEqual( + otel_wsgi.collect_request_attributes(self.environ).items(), + expected.items(), + ) + + def test_request_attributes_with_faux_scheme_relative_raw_uri(self): + self.environ["RAW_URI"] = "//127.0.0.1/?" + self.validate_url("http://127.0.0.1//127.0.0.1/?", raw=True) + + def test_request_attributes_pathless(self): + self.environ["RAW_URI"] = "" + expected = {"http.target": ""} + self.assertGreaterEqual( + otel_wsgi.collect_request_attributes(self.environ).items(), + expected.items(), + ) + + def test_request_attributes_with_full_request_uri(self): + self.environ["HTTP_HOST"] = "127.0.0.1:8080" + self.environ["REQUEST_METHOD"] = "CONNECT" + self.environ[ + "REQUEST_URI" + ] = "127.0.0.1:8080" # Might happen in a CONNECT request + expected = { + "http.host": "127.0.0.1:8080", + "http.target": "127.0.0.1:8080", + } + self.assertGreaterEqual( + otel_wsgi.collect_request_attributes(self.environ).items(), + expected.items(), + ) + + def test_response_attributes(self): + otel_wsgi.add_response_attributes(self.span, "404 Not Found", {}) + expected = ( + mock.call("http.status_code", 404), + mock.call("http.status_text", "Not Found"), + ) + self.assertEqual(self.span.set_attribute.call_count, len(expected)) + self.span.set_attribute.assert_has_calls(expected, any_order=True) + + def test_response_attributes_invalid_status_code(self): + otel_wsgi.add_response_attributes(self.span, "Invalid Status Code", {}) + self.assertEqual(self.span.set_attribute.call_count, 1) + self.span.set_attribute.assert_called_with( + "http.status_text", "Status Code" + ) + + +if __name__ == "__main__": + unittest.main() From 59f7e94644bb0191d7db8ce9c6f8532041df00aa Mon Sep 17 00:00:00 2001 From: Abhilash Gnan Date: Tue, 4 Aug 2020 18:22:22 +0200 Subject: [PATCH 2/8] Add HTTP user-agent in WSGI instrumentation (#964) --- .../src/opentelemetry/instrumentation/wsgi/__init__.py | 4 ++++ .../tests/test_wsgi_middleware.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py index 8a2d0bec0855..289016fe5298 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py @@ -117,6 +117,10 @@ def collect_request_attributes(environ): if remote_host and remote_host != remote_addr: result["net.peer.name"] = remote_host + user_agent = environ.get("HTTP_USER_AGENT") + if user_agent is not None and len(user_agent) > 0: + result["http.user_agent"] = user_agent + setifnotnone(result, "net.peer.port", environ.get("REMOTE_PORT")) flavor = environ.get("SERVER_PROTOCOL", "") if flavor.upper().startswith(_HTTP_VERSION_PREFIX): diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py index 5734027b0aae..b06d915b5324 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py @@ -313,6 +313,14 @@ def test_request_attributes_with_full_request_uri(self): expected.items(), ) + def test_http_user_agent_attribute(self): + self.environ["HTTP_USER_AGENT"] = "test-useragent" + expected = {"http.user_agent": "test-useragent"} + self.assertGreaterEqual( + otel_wsgi.collect_request_attributes(self.environ).items(), + expected.items(), + ) + def test_response_attributes(self): otel_wsgi.add_response_attributes(self.span, "404 Not Found", {}) expected = ( From d1332d2c36056acc111b6147e62963a2e348884c Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Sat, 15 Aug 2020 18:06:27 -0700 Subject: [PATCH 3/8] chore: 0.13.dev0 version update (#991) --- .../opentelemetry-instrumentation-wsgi/CHANGELOG.md | 4 ++++ .../opentelemetry-instrumentation-wsgi/setup.cfg | 6 +++--- .../src/opentelemetry/instrumentation/wsgi/version.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md b/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md index 6f1085b8bb44..50b3afedfdf2 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md +++ b/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## Version 0.12b0 + +Released 2020-08-14 + - Change package name to opentelemetry-instrumentation-wsgi ([#961](https://github.com/open-telemetry/opentelemetry-python/pull/961)) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg index b48d61dc849f..789f81ba8dce 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg @@ -40,12 +40,12 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.12.dev0 - opentelemetry-instrumentation == 0.12.dev0 + opentelemetry-api == 0.13dev0 + opentelemetry-instrumentation == 0.13dev0 [options.extras_require] test = - opentelemetry-test == 0.12.dev0 + opentelemetry-test == 0.13dev0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py index 780a92b6a1bb..9cc445d09e13 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.12.dev0" +__version__ = "0.13dev0" From daf9b134b62ea410e3f44e9caac9e1ddf3026588 Mon Sep 17 00:00:00 2001 From: alrex Date: Mon, 14 Sep 2020 15:11:56 -0700 Subject: [PATCH 4/8] dropping support for python 3.4 (#1099) * dropping support for python 3.4 --- .../opentelemetry-instrumentation-wsgi/CHANGELOG.md | 3 +++ instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md b/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md index 50b3afedfdf2..0bde2bb14487 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md +++ b/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Drop support for Python 3.4 + ([#1099](https://github.com/open-telemetry/opentelemetry-python/pull/1099)) + ## Version 0.12b0 Released 2020-08-14 diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg index 789f81ba8dce..5c4cae94d328 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg @@ -28,14 +28,13 @@ classifiers = License :: OSI Approved :: Apache Software License Programming Language :: Python Programming Language :: Python :: 3 - Programming Language :: Python :: 3.4 Programming Language :: Python :: 3.5 Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 [options] -python_requires = >=3.4 +python_requires = >=3.5 package_dir= =src packages=find_namespace: From 8ee2ff9daca1eaab2075369c4d860aab6e3dd8f1 Mon Sep 17 00:00:00 2001 From: alrex Date: Thu, 17 Sep 2020 08:23:52 -0700 Subject: [PATCH 5/8] release: updating changelogs and version to 0.13b0 (#1129) * updating changelogs and version to 0.13b0 --- .../opentelemetry-instrumentation-wsgi/CHANGELOG.md | 4 ++++ .../opentelemetry-instrumentation-wsgi/setup.cfg | 6 +++--- .../src/opentelemetry/instrumentation/wsgi/version.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md b/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md index 0bde2bb14487..68bd25237c8b 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md +++ b/instrumentation/opentelemetry-instrumentation-wsgi/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +## Version 0.13b0 + +Released 2020-09-17 + - Drop support for Python 3.4 ([#1099](https://github.com/open-telemetry/opentelemetry-python/pull/1099)) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg index 5c4cae94d328..bce6df7522d3 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg @@ -39,12 +39,12 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.13dev0 - opentelemetry-instrumentation == 0.13dev0 + opentelemetry-api == 0.13b0 + opentelemetry-instrumentation == 0.13b0 [options.extras_require] test = - opentelemetry-test == 0.13dev0 + opentelemetry-test == 0.13b0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py index 9cc445d09e13..2015e87c705f 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.13dev0" +__version__ = "0.13b0" From 04316cad0b8fa611ea0538795ac1f664ebd08d90 Mon Sep 17 00:00:00 2001 From: alrex Date: Thu, 17 Sep 2020 12:21:39 -0700 Subject: [PATCH 6/8] chore: bump dev version (#1131) --- .../opentelemetry-instrumentation-wsgi/setup.cfg | 6 +++--- .../src/opentelemetry/instrumentation/wsgi/version.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg index bce6df7522d3..2de0e784bf08 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg @@ -39,12 +39,12 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.13b0 - opentelemetry-instrumentation == 0.13b0 + opentelemetry-api == 0.14.dev0 + opentelemetry-instrumentation == 0.14.dev0 [options.extras_require] test = - opentelemetry-test == 0.13b0 + opentelemetry-test == 0.14.dev0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py index 2015e87c705f..0f990278982e 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.13b0" +__version__ = "0.14.dev0" From 7addc1e40348f28e87deda1ae97fc7f4b01806b3 Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Mon, 21 Sep 2020 08:38:58 -0700 Subject: [PATCH 7/8] instrumentation/wsgi: Use is_recording flag in wsgi instrumentation (#1122) --- .../instrumentation/wsgi/__init__.py | 6 ++++-- .../tests/test_wsgi_middleware.py | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py index 289016fe5298..56c8b755c5cd 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/__init__.py @@ -136,7 +136,8 @@ def add_response_attributes( ): # pylint: disable=unused-argument """Adds HTTP response attributes to span using the arguments passed to a PEP3333-conforming start_response callable.""" - + if not span.is_recording(): + return status_code, status_text = start_response_status.split(" ", 1) span.set_attribute("http.status_text", status_text) @@ -215,7 +216,8 @@ def __call__(self, environ, start_response): iterable, span, self.tracer, token ) except Exception as ex: - span.set_status(Status(StatusCanonicalCode.INTERNAL, str(ex))) + if span.is_recording(): + span.set_status(Status(StatusCanonicalCode.INTERNAL, str(ex))) span.end() context.detach(token) raise diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py index b06d915b5324..baab50d96bb4 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/tests/test_wsgi_middleware.py @@ -125,6 +125,23 @@ def test_basic_wsgi_call(self): response = app(self.environ, self.start_response) self.validate_response(response) + def test_wsgi_not_recording(self): + mock_tracer = mock.Mock() + mock_span = mock.Mock() + mock_span.is_recording.return_value = False + mock_tracer.start_span.return_value = mock_span + mock_tracer.use_span.return_value.__enter__ = mock_span + mock_tracer.use_span.return_value.__exit__ = mock_span + with mock.patch("opentelemetry.trace.get_tracer") as tracer: + tracer.return_value = mock_tracer + app = otel_wsgi.OpenTelemetryMiddleware(simple_wsgi) + # pylint: disable=W0612 + response = app(self.environ, self.start_response) # noqa: F841 + self.assertFalse(mock_span.is_recording()) + self.assertTrue(mock_span.is_recording.called) + self.assertFalse(mock_span.set_attribute.called) + self.assertFalse(mock_span.set_status.called) + def test_wsgi_iterable(self): original_response = Response() iter_wsgi = create_iter_wsgi(original_response) From c9a3a121a557f5bea2e5dd8b057a45d8e983ad6d Mon Sep 17 00:00:00 2001 From: Leighton Chen Date: Tue, 13 Oct 2020 14:38:09 -0400 Subject: [PATCH 8/8] chore: bump dev version (#1235) --- .../opentelemetry-instrumentation-wsgi/setup.cfg | 6 +++--- .../src/opentelemetry/instrumentation/wsgi/version.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg index 2de0e784bf08..1570fd5d0e75 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg +++ b/instrumentation/opentelemetry-instrumentation-wsgi/setup.cfg @@ -39,12 +39,12 @@ package_dir= =src packages=find_namespace: install_requires = - opentelemetry-api == 0.14.dev0 - opentelemetry-instrumentation == 0.14.dev0 + opentelemetry-api == 0.15.dev0 + opentelemetry-instrumentation == 0.15.dev0 [options.extras_require] test = - opentelemetry-test == 0.14.dev0 + opentelemetry-test == 0.15.dev0 [options.packages.find] where = src diff --git a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py index 0f990278982e..e7b342d644e0 100644 --- a/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py +++ b/instrumentation/opentelemetry-instrumentation-wsgi/src/opentelemetry/instrumentation/wsgi/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.14.dev0" +__version__ = "0.15.dev0"