Skip to content

Commit 7aab076

Browse files
committed
Initial code import.
1 parent e685f0e commit 7aab076

File tree

8 files changed

+367
-0
lines changed

8 files changed

+367
-0
lines changed

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.pyc
2+
dist
3+
bin
4+
eggs
5+
lib
6+
*.egg-info
7+
build
8+
env/

README.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
##################
2+
Django Opentracing
3+
##################
4+
5+
This package enables distributed tracing in Pyramid projects via `The OpenTracing Project`_. Once a production system contends with real concurrency or splits into many services, crucial (and formerly easy) tasks become difficult: user-facing latency optimization, root-cause analysis of backend errors, communication about distinct pieces of a now-distributed system, etc. Distributed tracing follows a request on its journey from inception to completion from mobile/browser all the way to the microservices.
6+
7+
Setting up Tracing
8+
==================
9+
10+
In order to implement tracing in your system (for all the requests), add the following lines of code to your site's Configuration section to enable the tracing tweed:
11+
12+
.. code-block:: python
13+
14+
# OpenTracing settings
15+
16+
# if not included, defaults to False
17+
config.add_attributes(opentracing_trace_all=True)
18+
19+
# defaults to []
20+
# only valid if 'opentracing_trace_all' == True
21+
config.add_attributes(opentracing_trace_attributes=['host', 'method', ...])
22+
23+
# can be any valid underlying OpenTracing tracer implementation
24+
config.add_attributes(opentracing_base_tracer=...)
25+
26+
# enable the tween
27+
config.include('pyramid_opentracing')
28+

example/simple.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from wsgiref.simple_server import make_server
2+
from pyramid.config import Configurator
3+
from pyramid.view import view_config
4+
5+
import opentracing
6+
from pyramid_opentracing import PyramidTracer
7+
8+
# Pass a specific Tracer instance to PyramidTracer()
9+
tracer = PyramidTracer(opentracing.Tracer())
10+
11+
@view_config(route_name='root', renderer='json')
12+
def server_index(request):
13+
return { 'message': 'Hello world!' }
14+
15+
@tracer.trace('method')
16+
@view_config(route_name='simple', renderer='json')
17+
def server_simple(request):
18+
return { 'message': 'This is a simple traced request.' }
19+
20+
@tracer.trace()
21+
@view_config(route_name='log', renderer='json')
22+
def server_log(request):
23+
if tracer.get_span(request) is not None:
24+
span.log_event('Hello, World!')
25+
return { 'message': 'Something was logged' }
26+
27+
@tracer.trace()
28+
@view_config(route_name='childspan', renderer='json')
29+
def server_child_span(request):
30+
if tracer.get_span(request) is not None:
31+
child_span = tracer._tracer.start_span('child_span', child_of=span.context)
32+
child_span.finish()
33+
return { 'message': 'A child span was created' }
34+
35+
36+
if __name__ == '__main__':
37+
config = Configurator()
38+
config.add_route('root', '/')
39+
config.add_route('simple', '/simple')
40+
config.add_route('log', '/log')
41+
config.add_route('childspan', '/childspan')
42+
config.scan()
43+
44+
app = config.make_wsgi_app()
45+
server = make_server('127.0.0.1', 8080, app)
46+
server.serve_forever()
47+

example/tween.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from wsgiref.simple_server import make_server
2+
from pyramid.config import Configurator
3+
from pyramid.view import view_config
4+
5+
import opentracing
6+
from pyramid_opentracing import PyramidTracer
7+
8+
# Pass a specific Tracer instance
9+
base_tracer = opentracing.Tracer()
10+
11+
@view_config(route_name='root', renderer='json')
12+
def server_index(request):
13+
return { 'message': 'Hello world!' }
14+
15+
@view_config(route_name='simple', renderer='json')
16+
def server_simple(request):
17+
return { 'message': 'This is a simple traced request.' }
18+
19+
@view_config(route_name='log', renderer='json')
20+
def server_log(request):
21+
return { 'message': 'Something was logged' }
22+
23+
24+
if __name__ == '__main__':
25+
config = Configurator()
26+
config.add_route('root', '')
27+
config.add_route('simple', '/simple')
28+
config.add_route('log', '/log')
29+
config.scan()
30+
31+
# Tween setup
32+
config.add_settings(opentracing_traced_attributes=['host', 'method'])
33+
config.add_settings(opentracing_traced_all=True)
34+
config.add_settings(opentracing_base_tracer=base_tracer)
35+
config.include('pyramid_opentracing')
36+
37+
app = config.make_wsgi_app()
38+
server = make_server('127.0.0.1', 8080, app)
39+
server.serve_forever()
40+

pyramid_opentracing/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .tracer import PyramidTracer
2+
from .tween_factory import includeme, opentracing_tween_factory

pyramid_opentracing/tests.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import unittest
2+
from pyramid import testing
3+
import opentracing
4+
5+
from tracer import PyramidTracer
6+
from tween_factory import includeme, opentracing_tween_factory
7+
8+
class TestPyramidTracer(unittest.TestCase):
9+
def test_ctor_default(self):
10+
tracer = PyramidTracer(DummyTracer())
11+
self.assertIsNotNone(tracer._tracer, '#A0')
12+
self.assertFalse(tracer._trace_all, '#A1')
13+
self.assertEqual({}, tracer._current_spans, '#A2')
14+
15+
def test_ctor(self):
16+
tracer = PyramidTracer(DummyTracer(), trace_all=True)
17+
self.assertTrue(tracer._trace_all, '#A0')
18+
19+
tracer = PyramidTracer(DummyTracer(), trace_all=False)
20+
self.assertFalse(tracer._trace_all, '#B0')
21+
22+
def test_get_span_none(self):
23+
tracer = PyramidTracer(DummyTracer())
24+
self.assertIsNone(tracer.get_span(DummyRequest()), '#A0')
25+
26+
def test_get_span(self):
27+
tracer = PyramidTracer(DummyTracer())
28+
req = DummyRequest()
29+
tracer._apply_tracing(req, [])
30+
self.assertIsNotNone(tracer.get_span(req), '#B0')
31+
self.assertIsNone(tracer.get_span(DummyRequest()), '#B1')
32+
self.assertEqual(1, len(tracer._current_spans), '#B2')
33+
34+
def test_apply_tracing_invalid(self):
35+
tracer = PyramidTracer(DummyTracer(opentracing.InvalidCarrierException()))
36+
tracer._apply_tracing(DummyRequest(), [])
37+
38+
def test_apply_tracing_corrupted(self):
39+
tracer = PyramidTracer(DummyTracer(opentracing.SpanContextCorruptedException()))
40+
tracer._apply_tracing(DummyRequest(), [])
41+
42+
def test_apply_tracing_operation(self):
43+
tracer = PyramidTracer(DummyTracer(opentracing.SpanContextCorruptedException()))
44+
span = tracer._apply_tracing(DummyRequest(), [])
45+
self.assertEqual('/', span.operation_name)
46+
47+
def test_apply_tracing_attrs(self):
48+
tracer = PyramidTracer(DummyTracer())
49+
req = DummyRequest()
50+
51+
span = tracer._apply_tracing(req, [])
52+
self.assertEqual({}, span._tags, '#A0')
53+
54+
span = tracer._apply_tracing(req, ['dont', 'exist'])
55+
self.assertEqual({}, span._tags, '#B0')
56+
57+
span = tracer._apply_tracing(req, ['host', 'path'])
58+
self.assertEqual({'host': 'example.com:80', 'path': '/'}, span._tags, '#C0')
59+
60+
def test_finish_none(self):
61+
tracer = PyramidTracer(DummyTracer())
62+
tracer._finish_tracing(DummyRequest())
63+
64+
def test_finish(self):
65+
tracer = PyramidTracer(DummyTracer())
66+
req = DummyRequest()
67+
span = tracer._apply_tracing(req, [])
68+
tracer._finish_tracing(req)
69+
self.assertTrue(span._is_finished)
70+
71+
class TestTweenFactory(unittest.TestCase):
72+
73+
def setUp(self):
74+
self.request = DummyRequest()
75+
self.response = DummyResponse()
76+
self.registry = DummyRegistry()
77+
78+
def test_it(self):
79+
handler = lambda x: self.response
80+
factory = opentracing_tween_factory(handler, self.registry)
81+
82+
res = factory(self.request)
83+
self.assertIsNotNone(self.registry.settings.get('opentracing_tracer'))
84+
85+
class TestIncludeme(unittest.TestCase):
86+
87+
def test_it(self):
88+
config = DummyConfig()
89+
includeme(config)
90+
self.assertEqual([('pyramid_opentracing.opentracing_tween_factory', None, None)], config.tweens, '#A0')
91+
92+
class DummyTracer(object):
93+
def __init__(self, excToThrow=None):
94+
super(DummyTracer, self).__init__()
95+
self.excToThrow = excToThrow
96+
97+
def extract(self, f, headers):
98+
if self.excToThrow:
99+
raise self.excToThrow
100+
101+
return DummyContext()
102+
103+
def start_span(self, operation_name, child_of=None):
104+
return DummySpan(operation_name, child_of=child_of)
105+
106+
class DummyRegistry(object):
107+
def __init__(self, settings={}):
108+
self.settings = settings
109+
110+
class DummyConfig(object):
111+
def __init__(self):
112+
self.tweens = []
113+
114+
def add_tween(self, x, under=None, over=None):
115+
self.tweens.append((x, under, over))
116+
117+
class DummyRequest(testing.DummyRequest):
118+
def __init__(self, *args, **kwargs):
119+
super(DummyRequest, self).__init__(*args, **kwargs)
120+
121+
class DummyResponse(object):
122+
def __init__(self, headers={}):
123+
self.headers = headers
124+
125+
class DummyContext(object):
126+
pass
127+
128+
class DummySpan(object):
129+
def __init__(self, operation_name, child_of):
130+
super(DummySpan, self).__init__()
131+
self.operation_name = operation_name
132+
self.child_of = child_of
133+
self._tags = {}
134+
135+
def set_tag(self, name, value):
136+
self._tags[name] = value
137+
138+
def finish(self):
139+
self._is_finished = True
140+

pyramid_opentracing/tracer.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import opentracing
2+
3+
# Ported from the Django library:
4+
# https://github.com/opentracing-contrib/python-django
5+
class PyramidTracer(object):
6+
'''
7+
@param tracer the OpenTracing tracer to be used
8+
to trace requests using this PyramidTracer
9+
'''
10+
def __init__(self, tracer, trace_all=False):
11+
self._tracer = tracer
12+
self._trace_all = trace_all
13+
self._current_spans = {}
14+
15+
def get_span(self, request):
16+
'''
17+
@param request
18+
Returns the span tracing this request
19+
'''
20+
return self._current_spans.get(request, None)
21+
22+
def trace(self, *attributes):
23+
'''
24+
Function decorator that traces functions
25+
'''
26+
def decorator(view_func):
27+
if self._trace_all:
28+
return view_func
29+
30+
# otherwise, execute the decorator
31+
def wrapper(request):
32+
span = self._apply_tracing(request, list(attributes))
33+
r = view_func(request)
34+
self._finish_tracing(request)
35+
return r
36+
37+
return wrapper
38+
return decorator
39+
40+
def _apply_tracing(self, request, attributes):
41+
'''
42+
Helper function to avoid rewriting for middleware and decorator.
43+
Returns a new span from the request with logged attributes and
44+
correct operation name from the view_func.
45+
'''
46+
headers = request.headers
47+
48+
# start new span from trace info
49+
span = None
50+
operation_name = request.path # (xxx) fix this thing
51+
try:
52+
span_ctx = self._tracer.extract(opentracing.Format.HTTP_HEADERS, headers)
53+
span = self._tracer.start_span(operation_name=operation_name, child_of=span_ctx)
54+
except (opentracing.InvalidCarrierException, opentracing.SpanContextCorruptedException) as e:
55+
span = self._tracer.start_span(operation_name=operation_name)
56+
if span is None:
57+
span = self._tracer.start_span(operation_name=operation_name)
58+
59+
# add span to current spans
60+
self._current_spans[request] = span
61+
62+
# log any traced attributes
63+
for attr in attributes:
64+
if hasattr(request, attr):
65+
payload = str(getattr(request, attr))
66+
if payload:
67+
span.set_tag(attr, payload)
68+
69+
return span
70+
71+
def _finish_tracing(self, request):
72+
span = self._current_spans.pop(request, None)
73+
if span is not None:
74+
span.finish()
75+

pyramid_opentracing/tween_factory.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import opentracing
2+
3+
from .tracer import PyramidTracer
4+
5+
def opentracing_tween_factory(handler, registry):
6+
base_tracer = registry.settings.get('opentracing_base_tracer', opentracing.Tracer())
7+
traced_attrs = registry.settings.get('opentracing_traced_attributes', [])
8+
trace_all = registry.settings.get('opentracing_trace_all', False)
9+
10+
tracer = PyramidTracer(base_tracer, trace_all)
11+
registry.settings ['opentracing_tracer'] = tracer
12+
13+
def opentracing_tween(req):
14+
if not trace_all:
15+
return handler(req)
16+
17+
span = tracer._apply_tracing(req, traced_attrs)
18+
res = handler(req)
19+
tracer._finish_tracing(req)
20+
21+
return res
22+
23+
return opentracing_tween
24+
25+
def includeme(config):
26+
config.add_tween('pyramid_opentracing.opentracing_tween_factory')
27+

0 commit comments

Comments
 (0)