Skip to content

Commit

Permalink
Added ability to extract span attributes from falcon request objects.
Browse files Browse the repository at this point in the history
OTEL_PYTHON_FALCON_TRACED_REQUEST_ATTRS env var can be set to a command
separated list of attributes names that will be extracted from Falcon's
request object and set as attributes on spans.
  • Loading branch information
owais committed Sep 24, 2020
1 parent 2b46d11 commit bd1eead
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions instrumentation/opentelemetry-instrumentation-falcon/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,20 @@ 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][ # pylint:disable=W0212
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")

0 comments on commit bd1eead

Please sign in to comment.