diff --git a/instrumentation/opentelemetry-instrumentation-falcon/CHANGELOG.md b/instrumentation/opentelemetry-instrumentation-falcon/CHANGELOG.md index e14c730f74b..f398b7460da 100644 --- a/instrumentation/opentelemetry-instrumentation-falcon/CHANGELOG.md +++ b/instrumentation/opentelemetry-instrumentation-falcon/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Added support for `OTEL_PYTHON_FALCON_TRACED_REQUEST_ATTRS` ([#1158](https://github.com/open-telemetry/opentelemetry-python/pull/1158)) + ## Version 0.13b0 Released 2020-09-17 diff --git a/instrumentation/opentelemetry-instrumentation-falcon/README.rst b/instrumentation/opentelemetry-instrumentation-falcon/README.rst index f7d5a99d951..8230deaf764 100644 --- a/instrumentation/opentelemetry-instrumentation-falcon/README.rst +++ b/instrumentation/opentelemetry-instrumentation-falcon/README.rst @@ -31,6 +31,21 @@ For example, will exclude requests such as ``https://site/client/123/info`` and ``https://site/xyz/healthcheck``. +Request attributes +******************** +To extract certain attributes from Falcon's request object and use them as span attributes, set the environment variable ``OTEL_PYTHON_FALCON_TRACED_REQUEST_ATTRS`` to a comma +delimited list of request attribute names. + +For example, + +:: + + export OTEL_PYTHON_FALCON_TRACED_REQUEST_ATTRS='query_string,uri_template' + +will extract path_info and content_type attributes from every traced request and add them as span attritbues. + +Falcon Request object reference: https://falcon.readthedocs.io/en/stable/api/request_and_response.html#id1 + References ---------- 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 1e67d6101bd..660fc23063c 100644 --- a/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py +++ b/instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py @@ -50,6 +50,7 @@ def on_get(self, req, resp): import opentelemetry.instrumentation.wsgi as otel_wsgi from opentelemetry import configuration, context, propagators, trace +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 @@ -92,13 +93,16 @@ def _uninstrument(self, **kwargs): class _InstrumentedFalconAPI(falcon.API): def __init__(self, *args, **kwargs): - mw = kwargs.pop("middleware", []) - if not isinstance(mw, (list, tuple)): - mw = [mw] + middlewares = kwargs.pop("middleware", []) + if not isinstance(middlewares, (list, tuple)): + middlewares = [middlewares] self._tracer = trace.get_tracer(__name__, __version__) - mw.insert(0, _TraceMiddleware(self._tracer)) - kwargs["middleware"] = mw + trace_middleware = _TraceMiddleware( + self._tracer, kwargs.get("traced_request_attributes") + ) + middlewares.insert(0, trace_middleware) + kwargs["middleware"] = middlewares super().__init__(*args, **kwargs) def __call__(self, env, start_response): @@ -144,8 +148,24 @@ def _start_response(status, response_headers, *args, **kwargs): class _TraceMiddleware: # pylint:disable=R0201,W0613 - def __init__(self, tracer=None): + def __init__(self, tracer=None, traced_request_attrs=None): self.tracer = tracer + self._traced_request_attrs = traced_request_attrs or [ + attr.strip() + for attr in ( + Configuration().FALCON_TRACED_REQUEST_ATTRS or "" + ).split(",") + ] + + def process_request(self, req, resp): + span = req.env.get(_ENVIRON_SPAN_KEY) + 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)) def process_resource(self, req, resp, resource, params): span = req.env.get(_ENVIRON_SPAN_KEY) diff --git a/instrumentation/opentelemetry-instrumentation-falcon/tests/test_falcon.py b/instrumentation/opentelemetry-instrumentation-falcon/tests/test_falcon.py index a3d2c5d8d81..81092a938ce 100644 --- a/instrumentation/opentelemetry-instrumentation-falcon/tests/test_falcon.py +++ b/instrumentation/opentelemetry-instrumentation-falcon/tests/test_falcon.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import os from unittest.mock import patch from falcon import testing @@ -171,3 +172,18 @@ def test_exclude_lists(self): self.client().simulate_get(path="/hello") span_list = self.memory_exporter.get_finished_spans() self.assertEqual(len(span_list), 1) + + def test_traced_request_attributes(self): + self.client().simulate_get(path="/hello?q=abc") + span = self.memory_exporter.get_finished_spans()[0] + self.assertNotIn("query_string", span.attributes) + self.memory_exporter.clear() + + middleware = self.app._middleware[0][0].__self__ + with patch.object( + middleware, "_traced_request_attrs", ["query_string"] + ): + self.client().simulate_get(path="/hello?q=abc") + span = self.memory_exporter.get_finished_spans()[0] + self.assertIn("query_string", span.attributes) + self.assertEqual(span.attributes["query_string"], "q=abc")