Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 8ae35d4

Browse files
bplotnickliyanhui1228
authored andcommitted
Pyramid support (#121)
1 parent acab734 commit 8ae35d4

10 files changed

Lines changed: 667 additions & 1 deletion

File tree

README.rst

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ Framework Integration
183183
---------------------
184184

185185
Census supports integration with popular web frameworks including
186-
Django, Flask, and Webapp2. When the application receives a HTTP request,
186+
Django, Flask, Pyramid, and Webapp2. When the application receives a HTTP request,
187187
the tracer will automatically generate a span context using the trace
188188
information extracted from the request headers, and propagated to the
189189
child spans.
@@ -253,6 +253,39 @@ setting in ``settings.py``:
253253
'ZIPKIN_EXPORTER_PORT': 9411,
254254
}
255255
256+
257+
Pyramid
258+
~~~~~~~
259+
260+
In your application, add the pyramid tween and your requests will be
261+
traced.
262+
263+
.. code:: python
264+
265+
def main(global_config, **settings):
266+
config = Configurator(settings=settings)
267+
268+
config.add_tween('opencensus.trace.ext.pyramid'
269+
'.pyramid_middleware.OpenCensusTweenFactory')
270+
271+
To configure the sampler, exporter, and propagator, pass the instances
272+
into the pyramid settings
273+
274+
.. code:: python
275+
276+
from opencensus.trace.exporters import print_exporter
277+
from opencensus.trace.propagation import google_cloud_format
278+
from opencensus.trace.samplers import probability
279+
280+
settings = {}
281+
settings['OPENCENSUS_TRACE'] = {
282+
'EXPORTER': print_exporter.PrintExporter(),
283+
'SAMPLER': probability.ProbabilitySampler(rate=0.5),
284+
'PROPAGATOR': google_cloud_format.GoogleCloudFormatPropagator(),
285+
}
286+
287+
config = Configurator(settings=settings)
288+
256289
gRPC Integration
257290
----------------
258291

examples/trace/helloworld/pyramid/__init__.py

Whitespace-only changes.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import requests
16+
17+
from pyramid.config import Configurator
18+
from pyramid.response import Response
19+
from pyramid.tweens import MAIN
20+
from pyramid.view import view_config
21+
22+
23+
@view_config(route_name='hello')
24+
def hello(request):
25+
return Response('Hello world!')
26+
27+
28+
@view_config(route_name='trace_requests')
29+
def trace_requests(request):
30+
response = requests.get('http://www.google.com')
31+
return Response(str(response.status_code))
32+
33+
34+
def main(global_config, **settings):
35+
config = Configurator(settings=settings)
36+
37+
config.add_route('hello', '/')
38+
config.add_route('trace_requests', '/requests')
39+
40+
config.add_tween('opencensus.trace.ext.pyramid'
41+
'.pyramid_middleware.OpenCensusTweenFactory',
42+
over=MAIN)
43+
44+
config.scan()
45+
46+
return config.make_wsgi_app()
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from wsgiref.simple_server import make_server
16+
17+
from opencensus.trace import config_integration
18+
from opencensus.trace.exporters import print_exporter
19+
from opencensus.trace.samplers import probability
20+
21+
from app import main
22+
23+
24+
INTEGRATIONS = ['requests']
25+
26+
27+
config_integration.trace_integrations(INTEGRATIONS)
28+
29+
30+
def run_app():
31+
settings = {}
32+
33+
exporter = print_exporter.PrintExporter()
34+
sampler = probability.ProbabilitySampler(rate=1)
35+
36+
settings['OPENCENSUS_TRACE'] = {
37+
'EXPORTER': exporter,
38+
'SAMPLER': sampler,
39+
}
40+
41+
app = main({}, **settings)
42+
server = make_server('localhost', 8080, app)
43+
server.serve_forever()
44+
45+
46+
if __name__ == '__main__':
47+
run_app()

opencensus/trace/ext/pyramid/__init__.py

Whitespace-only changes.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from opencensus.trace.samplers import always_on
16+
from opencensus.trace.exporters import print_exporter
17+
from opencensus.trace.propagation import google_cloud_format
18+
19+
DEFAULT_PYRAMID_TRACER_CONFIG = {
20+
'SAMPLER': always_on.AlwaysOnSampler(),
21+
'EXPORTER': print_exporter.PrintExporter(),
22+
'PROPAGATOR': google_cloud_format.GoogleCloudFormatPropagator()
23+
}
24+
25+
26+
DEFAULT_PYRAMID_TRACER_PARAMS = {
27+
# https://cloud.google.com/appengine/docs/flexible/python/
28+
# how-instances-are-managed#health_checking
29+
'BLACKLIST_PATHS': ['_ah/health'],
30+
}
31+
32+
33+
class PyramidTraceSettings(object):
34+
def __init__(self, registry):
35+
self.settings = registry.settings.get(
36+
'OPENCENSUS_TRACE',
37+
DEFAULT_PYRAMID_TRACER_CONFIG)
38+
39+
self.params = registry.settings.get(
40+
'OPENCENSUS_TRACE_PARAMS',
41+
DEFAULT_PYRAMID_TRACER_PARAMS)
42+
43+
_set_default_configs(self.settings, DEFAULT_PYRAMID_TRACER_CONFIG)
44+
45+
_set_default_configs(self.params, DEFAULT_PYRAMID_TRACER_PARAMS)
46+
47+
def __getattr__(self, attr):
48+
# If not in defaults, it is something we cannot parse.
49+
if attr not in DEFAULT_PYRAMID_TRACER_CONFIG:
50+
raise AttributeError('Attribute {} does not exist.'.format(attr))
51+
52+
return self.settings[attr]
53+
54+
55+
def _set_default_configs(user_settings, default):
56+
"""Set the default value to user settings if user not specified
57+
the value.
58+
"""
59+
for key in default:
60+
if key not in user_settings:
61+
user_settings[key] = default[key]
62+
63+
return user_settings
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
17+
from opencensus.trace.ext import utils
18+
from opencensus.trace.ext.pyramid.config import PyramidTraceSettings
19+
20+
from opencensus.trace import attributes_helper
21+
from opencensus.trace import execution_context
22+
from opencensus.trace import tracer as tracer_module
23+
24+
25+
HTTP_METHOD = attributes_helper.COMMON_ATTRIBUTES['HTTP_METHOD']
26+
HTTP_URL = attributes_helper.COMMON_ATTRIBUTES['HTTP_URL']
27+
HTTP_STATUS_CODE = attributes_helper.COMMON_ATTRIBUTES['HTTP_STATUS_CODE']
28+
29+
_PYRAMID_TRACE_HEADER = 'X_CLOUD_TRACE_CONTEXT'
30+
31+
BLACKLIST_PATHS = 'BLACKLIST_PATHS'
32+
33+
34+
log = logging.getLogger(__name__)
35+
36+
37+
class OpenCensusTweenFactory(object):
38+
"""Pyramid tweens are like wsgi middleware, but have access to things
39+
like the request, response, and application registry.
40+
41+
The tween factory is a globally importable callable whose
42+
constructor takes a request handler and application registry. It
43+
will be called with a pyramid request object.
44+
45+
For details on pyramid tweens, see
46+
https://docs.pylonsproject.org/projects/pyramid/en/latest/narr/hooks.html#creating-a-tween
47+
"""
48+
def __init__(self, handler, registry):
49+
"""Constructor for the pyramid tween
50+
51+
:param handler: Either the main Pyramid request handling
52+
function or another tween
53+
:type handler: function
54+
:param registry: The pyramid application registry
55+
:type registry: :class:`pyramid.registry.Registry`
56+
"""
57+
self.handler = handler
58+
self.registry = registry
59+
settings = PyramidTraceSettings(registry)
60+
61+
self.sampler = settings.SAMPLER
62+
self.exporter = settings.EXPORTER
63+
self.propagator = settings.PROPAGATOR
64+
65+
self._blacklist_paths = settings.params.get(BLACKLIST_PATHS)
66+
67+
def __call__(self, request):
68+
self._before_request(request)
69+
70+
response = self.handler(request)
71+
72+
self._after_request(request, response)
73+
74+
return response
75+
76+
def _before_request(self, request):
77+
if utils.disable_tracing_url(request.path, self._blacklist_paths):
78+
return
79+
80+
try:
81+
header = get_context_header(request)
82+
span_context = self.propagator.from_header(header)
83+
84+
tracer = tracer_module.Tracer(
85+
span_context=span_context,
86+
sampler=self.sampler,
87+
exporter=self.exporter,
88+
propagator=self.propagator)
89+
90+
span = tracer.start_span()
91+
92+
# Set the span name as the name of the current module name
93+
span.name = '[{}]{}'.format(
94+
request.method,
95+
request.path)
96+
97+
tracer.add_attribute_to_current_span(
98+
attribute_key=HTTP_METHOD,
99+
attribute_value=request.method)
100+
tracer.add_attribute_to_current_span(
101+
attribute_key=HTTP_URL,
102+
attribute_value=request.path)
103+
except Exception: # pragma: NO COVER
104+
log.error('Failed to trace request', exc_info=True)
105+
106+
def _after_request(self, request, response):
107+
if utils.disable_tracing_url(request.path, self._blacklist_paths):
108+
return
109+
110+
try:
111+
tracer = execution_context.get_opencensus_tracer()
112+
tracer.add_attribute_to_current_span(
113+
HTTP_STATUS_CODE,
114+
str(response.status_code))
115+
116+
tracer.end_span()
117+
tracer.finish()
118+
except Exception: # pragma: NO COVER
119+
log.error('Failed to trace request', exc_info=True)
120+
121+
122+
def get_context_header(request):
123+
"""Get trace context header from pyramid request headers."""
124+
return request.headers.get(_PYRAMID_TRACE_HEADER)

requirements-test.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ mock==2.0.0
66
mysql-connector==2.1.6
77
psycopg2==2.7.3.1
88
pymysql==0.7.11
9+
pyramid==1.9.1
910
pytest==3.2.2
1011
pytest-cov==2.5.1
1112
retrying==1.3.3

0 commit comments

Comments
 (0)