Skip to content

Commit 4a66652

Browse files
owaisNathanielRN
andauthored
Added ability to extract span attributes from tornado request objects (#1178)
OTEL_PYTHON_TONADO_TRACED_REQUEST_ATTRS env var can be set to a command separated list of attributes names that will be extracted from Tornado's request object and set as attributes on spans. Co-authored-by: (Eliseo) Nathaniel Ruiz Nowell <[email protected]>
1 parent 68b7cea commit 4a66652

File tree

7 files changed

+70
-10
lines changed

7 files changed

+70
-10
lines changed

instrumentation/opentelemetry-instrumentation-django/src/opentelemetry/instrumentation/django/middleware.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from opentelemetry.configuration import Configuration
1818
from opentelemetry.context import attach, detach
1919
from opentelemetry.instrumentation.django.version import __version__
20+
from opentelemetry.instrumentation.utils import extract_attributes_from_object
2021
from opentelemetry.instrumentation.wsgi import (
2122
add_response_attributes,
2223
collect_request_attributes,
@@ -111,10 +112,9 @@ def process_request(self, request):
111112

112113
if span.is_recording():
113114
attributes = collect_request_attributes(environ)
114-
for attr in self._traced_request_attrs:
115-
value = getattr(request, attr, None)
116-
if value is not None:
117-
attributes[attr] = str(value)
115+
attributes = extract_attributes_from_object(
116+
request, self._traced_request_attrs, attributes
117+
)
118118
for key, value in attributes.items():
119119
span.set_attribute(key, value)
120120

instrumentation/opentelemetry-instrumentation-falcon/src/opentelemetry/instrumentation/falcon/__init__.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,10 @@ def on_get(self, req, resp):
5353
from opentelemetry.configuration import Configuration
5454
from opentelemetry.instrumentation.falcon.version import __version__
5555
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
56-
from opentelemetry.instrumentation.utils import http_status_to_canonical_code
56+
from opentelemetry.instrumentation.utils import (
57+
extract_attributes_from_object,
58+
http_status_to_canonical_code,
59+
)
5760
from opentelemetry.trace.status import Status
5861
from opentelemetry.util import ExcludeList, time_ns
5962

@@ -162,10 +165,11 @@ def process_request(self, req, resp):
162165
if not span:
163166
return
164167

165-
for attr in self._traced_request_attrs:
166-
value = getattr(req, attr, None)
167-
if value is not None:
168-
span.set_attribute(attr, str(value))
168+
attributes = extract_attributes_from_object(
169+
req, self._traced_request_attrs
170+
)
171+
for key, value in attributes.items():
172+
span.set_attribute(key, value)
169173

170174
def process_resource(self, req, resp, resource, params):
171175
span = req.env.get(_ENVIRON_SPAN_KEY)

instrumentation/opentelemetry-instrumentation-tornado/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Added support for `OTEL_PYTHON_TORNADO_TRACED_REQUEST_ATTRS` ([#1178](https://github.com/open-telemetry/opentelemetry-python/pull/1178))
6+
57
## Version 0.13b0
68

79
Released 2020-09-17

instrumentation/opentelemetry-instrumentation-tornado/README.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ A comma separated list of paths that should not be automatically traced. For exa
3131

3232
Then any requests made to ``/healthz`` and ``/ping`` will not be automatically traced.
3333

34+
Request attributes
35+
********************
36+
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
37+
delimited list of request attribute names.
38+
39+
For example,
40+
41+
::
42+
43+
export OTEL_PYTHON_TORNADO_TRACED_REQUEST_ATTRS='uri,query'
44+
45+
will extract path_info and content_type attributes from every traced request and add them as span attributes.
3446

3547
References
3648
----------

instrumentation/opentelemetry-instrumentation-tornado/src/opentelemetry/instrumentation/tornado/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def get(self):
5050
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
5151
from opentelemetry.instrumentation.tornado.version import __version__
5252
from opentelemetry.instrumentation.utils import (
53+
extract_attributes_from_object,
5354
http_status_to_canonical_code,
5455
unwrap,
5556
)
@@ -71,7 +72,17 @@ def get_excluded_urls():
7172
return ExcludeList(urls)
7273

7374

75+
def get_traced_request_attrs():
76+
attrs = configuration.Configuration().TORNADO_TRACED_REQUEST_ATTRS or ""
77+
if attrs:
78+
attrs = [attr.strip() for attr in attrs.split(",")]
79+
else:
80+
attrs = []
81+
return attrs
82+
83+
7484
_excluded_urls = get_excluded_urls()
85+
_traced_attrs = get_traced_request_attrs()
7586

7687

7788
class TornadoInstrumentor(BaseInstrumentor):
@@ -196,7 +207,7 @@ def _get_attributes_from_request(request):
196207
if request.remote_ip:
197208
attrs["net.peer.ip"] = request.remote_ip
198209

199-
return attrs
210+
return extract_attributes_from_object(request, _traced_attrs, attrs)
200211

201212

202213
def _get_operation_name(handler, request):
@@ -211,6 +222,7 @@ def _start_span(tracer, handler, start_time) -> _TraceContext:
211222
_get_header_from_request_headers, handler.request.headers,
212223
)
213224
)
225+
214226
span = tracer.start_span(
215227
_get_operation_name(handler, handler.request),
216228
kind=trace.SpanKind.SERVER,

instrumentation/opentelemetry-instrumentation-tornado/tests/test_instrumentation.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,21 @@ def test_excluded(path):
354354
test_excluded("/healthz")
355355
test_excluded("/ping")
356356

357+
@patch(
358+
"opentelemetry.instrumentation.tornado._traced_attrs",
359+
["uri", "full_url", "query"],
360+
)
361+
def test_traced_attrs(self):
362+
self.fetch("/ping?q=abc&b=123")
363+
spans = self.sorted_spans(self.memory_exporter.get_finished_spans())
364+
self.assertEqual(len(spans), 2)
365+
server_span = spans[0]
366+
self.assertEqual(server_span.kind, SpanKind.SERVER)
367+
self.assert_span_has_attributes(
368+
server_span, {"uri": "/ping?q=abc&b=123", "query": "q=abc&b=123"}
369+
)
370+
self.memory_exporter.clear()
371+
357372

358373
class TestTornadoUninstrument(TornadoTest):
359374
def test_uninstrument(self):

opentelemetry-instrumentation/src/opentelemetry/instrumentation/utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,26 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from typing import Dict, Sequence
16+
1517
from wrapt import ObjectProxy
1618

1719
from opentelemetry.trace.status import StatusCanonicalCode
1820

1921

22+
def extract_attributes_from_object(
23+
obj: any, attributes: Sequence[str], existing: Dict[str, str] = None
24+
) -> Dict[str, str]:
25+
extracted = {}
26+
if existing:
27+
extracted.update(existing)
28+
for attr in attributes:
29+
value = getattr(obj, attr, None)
30+
if value is not None:
31+
extracted[attr] = str(value)
32+
return extracted
33+
34+
2035
def http_status_to_canonical_code(
2136
status: int, allow_redirect: bool = True
2237
) -> StatusCanonicalCode:

0 commit comments

Comments
 (0)