diff --git a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware.py b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware.py index 07e3eb710b8..11991413eb8 100644 --- a/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware.py +++ b/instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware.py @@ -17,6 +17,7 @@ from opentelemetry.configuration import Configuration from opentelemetry.context import attach, detach from opentelemetry.instrumentation.django.version import __version__ +from opentelemetry.instrumentation.utils import extract_attributes_from_object from opentelemetry.instrumentation.wsgi import ( add_response_attributes, collect_request_attributes, @@ -111,10 +112,9 @@ def process_request(self, request): if span.is_recording(): attributes = collect_request_attributes(environ) - for attr in self._traced_request_attrs: - value = getattr(request, attr, None) - if value is not None: - attributes[attr] = str(value) + attributes = extract_attributes_from_object( + request, self._traced_request_attrs, attributes + ) for key, value in attributes.items(): span.set_attribute(key, value) diff --git a/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py b/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py index 660fc23063c..bfcd45a8b58 100644 --- a/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py @@ -53,7 +53,10 @@ def on_get(self, req, resp): from opentelemetry.configuration import Configuration from opentelemetry.instrumentation.falcon.version import __version__ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor -from opentelemetry.instrumentation.utils import http_status_to_canonical_code +from opentelemetry.instrumentation.utils import ( + extract_attributes_from_object, + http_status_to_canonical_code, +) from opentelemetry.trace.status import Status from opentelemetry.util import ExcludeList, time_ns @@ -162,10 +165,11 @@ def process_request(self, req, resp): if not span: return - for attr in self._traced_request_attrs: - value = getattr(req, attr, None) - if value is not None: - span.set_attribute(attr, str(value)) + attributes = extract_attributes_from_object( + req, self._traced_request_attrs + ) + for key, value in attributes.items(): + span.set_attribute(key, value) def process_resource(self, req, resp, resource, params): span = req.env.get(_ENVIRON_SPAN_KEY) diff --git a/instrumentation/opentelemetry-instrumentation-tornado/CHANGELOG.md b/instrumentation/opentelemetry-instrumentation-tornado/CHANGELOG.md index 7cc628718de..e82446d66d7 100644 --- a/instrumentation/opentelemetry-instrumentation-tornado/CHANGELOG.md +++ b/instrumentation/opentelemetry-instrumentation-tornado/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Added support for `OTEL_PYTHON_TORNADO_TRACED_REQUEST_ATTRS` ([#1178](https://github.com/open-telemetry/opentelemetry-python/pull/1178)) + ## Version 0.13b0 Released 2020-09-17 diff --git a/instrumentation/opentelemetry-instrumentation-tornado/README.rst b/instrumentation/opentelemetry-instrumentation-tornado/README.rst index d84fbd0412c..088c7f0e85d 100644 --- a/instrumentation/opentelemetry-instrumentation-tornado/README.rst +++ b/instrumentation/opentelemetry-instrumentation-tornado/README.rst @@ -31,6 +31,18 @@ A comma separated list of paths that should not be automatically traced. For exa Then any requests made to ``/healthz`` and ``/ping`` will not be automatically traced. +Request attributes +******************** +To extract certain attributes from Tornado's request object and use them as span attributes, set the environment variable ``OTEL_PYTHON_TORNADO_TRACED_REQUEST_ATTRS`` to a comma +delimited list of request attribute names. + +For example, + +:: + + export OTEL_PYTHON_TORNADO_TRACED_REQUEST_ATTRS='uri,query' + +will extract path_info and content_type attributes from every traced request and add them as span attributes. References ---------- diff --git a/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/__init__.py b/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/__init__.py index 6379d841a03..5357be6d0fc 100644 --- a/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/__init__.py @@ -50,6 +50,7 @@ def get(self): from opentelemetry.instrumentation.instrumentor import BaseInstrumentor from opentelemetry.instrumentation.tornado.version import __version__ from opentelemetry.instrumentation.utils import ( + extract_attributes_from_object, http_status_to_canonical_code, unwrap, ) @@ -71,7 +72,17 @@ def get_excluded_urls(): return ExcludeList(urls) +def get_traced_request_attrs(): + attrs = configuration.Configuration().TORNADO_TRACED_REQUEST_ATTRS or "" + if attrs: + attrs = [attr.strip() for attr in attrs.split(",")] + else: + attrs = [] + return attrs + + _excluded_urls = get_excluded_urls() +_traced_attrs = get_traced_request_attrs() class TornadoInstrumentor(BaseInstrumentor): @@ -196,7 +207,7 @@ def _get_attributes_from_request(request): if request.remote_ip: attrs["net.peer.ip"] = request.remote_ip - return attrs + return extract_attributes_from_object(request, _traced_attrs, attrs) def _get_operation_name(handler, request): @@ -211,6 +222,7 @@ def _start_span(tracer, handler, start_time) -> _TraceContext: _get_header_from_request_headers, handler.request.headers, ) ) + span = tracer.start_span( _get_operation_name(handler, handler.request), kind=trace.SpanKind.SERVER, diff --git a/instrumentation/opentelemetry-instrumentation-tornado/tests/test_instrumentation.py b/instrumentation/opentelemetry-instrumentation-tornado/tests/test_instrumentation.py index d900b5d360e..eb2852f1123 100644 --- a/instrumentation/opentelemetry-instrumentation-tornado/tests/test_instrumentation.py +++ b/instrumentation/opentelemetry-instrumentation-tornado/tests/test_instrumentation.py @@ -354,6 +354,21 @@ def test_excluded(path): test_excluded("/healthz") test_excluded("/ping") + @patch( + "opentelemetry.instrumentation.tornado._traced_attrs", + ["uri", "full_url", "query"], + ) + def test_traced_attrs(self): + self.fetch("/ping?q=abc&b=123") + spans = self.sorted_spans(self.memory_exporter.get_finished_spans()) + self.assertEqual(len(spans), 2) + server_span = spans[0] + self.assertEqual(server_span.kind, SpanKind.SERVER) + self.assert_span_has_attributes( + server_span, {"uri": "/ping?q=abc&b=123", "query": "q=abc&b=123"} + ) + self.memory_exporter.clear() + class TestTornadoUninstrument(TornadoTest): def test_uninstrument(self): diff --git a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py index 8553b1bc63a..6220854ad59 100644 --- a/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py +++ b/opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py @@ -12,11 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Dict, Sequence + from wrapt import ObjectProxy from opentelemetry.trace.status import StatusCanonicalCode +def extract_attributes_from_object( + obj: any, attributes: Sequence[str], existing: Dict[str, str] = None +) -> Dict[str, str]: + extracted = {} + if existing: + extracted.update(existing) + for attr in attributes: + value = getattr(obj, attr, None) + if value is not None: + extracted[attr] = str(value) + return extracted + + def http_status_to_canonical_code( status: int, allow_redirect: bool = True ) -> StatusCanonicalCode: