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

Commit 26374d4

Browse files
authored
Add tracing blacklist (#71)
1 parent 00763b3 commit 26374d4

9 files changed

Lines changed: 243 additions & 10 deletions

File tree

README.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,34 @@ SpanContext and headers. Currently support
135135
# Serialize
136136
header = propagator.to_header(span_context)
137137
138+
Blacklist Paths
139+
~~~~~~~~~~~~~~~
140+
141+
You can specify which paths you do not want to trace by configuring the
142+
blacklist paths. By default the health check path in GAE Flex is not traced,
143+
but you can turn it on in the setting.
144+
145+
Here is the sample code for configuring the blacklist:
146+
147+
For Flask:
148+
149+
.. code:: python
150+
151+
from opencensus.trace.ext.flask.flask_middleware import FlaskMiddleware
152+
153+
app = flask.Flask(__name__)
154+
155+
blacklist_paths = ['_ah/health']
156+
middleware = FlaskMiddleware(app, blacklist_paths=blacklist_paths)
157+
158+
For Django:
159+
160+
Add this line in the OPENCENSUS_PARAMS:
161+
162+
::
163+
164+
'BLACKLIST_PATHS': ['_ah/health',]
165+
138166
Framework Integration
139167
---------------------
140168

@@ -188,6 +216,20 @@ Customize the sampler, exporter, propagator in the ``settings.py`` file:
188216
'GoogleCloudFormatPropagator',
189217
}
190218

219+
Configure the sampling rate and other params:
220+
221+
::
222+
223+
OPENCENSUS_TRACE_PARAMS = {
224+
'BLACKLIST_PATHS': ['/_ah/health'],
225+
'GCP_EXPORTER_PROJECT': None,
226+
'SAMPLING_RATE': 0.5,
227+
'ZIPKIN_EXPORTER_SERVICE_NAME': 'my_service',
228+
'ZIPKIN_EXPORTER_HOST_NAME': 'localhost',
229+
'ZIPKIN_EXPORTER_PORT': 9411,
230+
231+
}
232+
191233
Then the requests will be automatically traced.
192234

193235
Webapp2

trace/opencensus/trace/ext/django/config.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,15 @@
2626
}
2727

2828
DEFAULT_DJANGO_TRACER_PARAMS = {
29-
'SAMPLING_RATE': 0.5,
29+
# https://cloud.google.com/appengine/docs/flexible/python/
30+
# how-instances-are-managed#health_checking
31+
'BLACKLIST_PATHS': ['/_ah/health'],
3032
'GCP_EXPORTER_PROJECT': None,
33+
'SAMPLING_RATE': 0.5,
3134
'ZIPKIN_EXPORTER_SERVICE_NAME': 'my_service',
3235
'ZIPKIN_EXPORTER_HOST_NAME': 'localhost',
3336
'ZIPKIN_EXPORTER_PORT': 9411,
37+
3438
}
3539

3640

trace/opencensus/trace/ext/django/middleware.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@
3030

3131
_DJANGO_TRACE_HEADER = 'HTTP_X_CLOUD_TRACE_CONTEXT'
3232

33+
BLACKLIST_PATHS = 'BLACKLIST_PATHS'
3334
GCP_EXPORTER_PROJECT = 'GCP_EXPORTER_PROJECT'
3435
SAMPLING_RATE = 'SAMPLING_RATE'
3536
ZIPKIN_EXPORTER_SERVICE_NAME = 'ZIPKIN_EXPORTER_SERVICE_NAME'
3637
ZIPKIN_EXPORTER_HOST_NAME = 'ZIPKIN_EXPORTER_HOST_NAME'
3738
ZIPKIN_EXPORTER_PORT = 'ZIPKIN_EXPORTER_PORT'
3839

40+
3941
log = logging.getLogger(__name__)
4042

4143

@@ -94,6 +96,8 @@ def __init__(self, get_response=None):
9496
self._exporter = settings.EXPORTER
9597
self._propagator = settings.PROPAGATOR
9698

99+
self._blacklist_paths = settings.params.get(BLACKLIST_PATHS)
100+
97101
# Initialize the sampler
98102
if self._sampler.__name__ == 'ProbabilitySampler':
99103
_rate = settings.params.get(
@@ -129,6 +133,10 @@ def process_request(self, request):
129133
:type request: :class:`~django.http.request.HttpRequest`
130134
:param request: Django http request.
131135
"""
136+
# Do not trace if the url is blacklisted
137+
if utils.disable_tracing_url(request.path, self._blacklist_paths):
138+
return
139+
132140
# Add the request to thread local
133141
execution_context.set_opencensus_attr(
134142
REQUEST_THREAD_LOCAL_KEY,
@@ -161,6 +169,10 @@ def process_view(self, request, view_func, *args, **kwargs):
161169
"""Process view is executed before the view function, here we get the
162170
function name add set it as the span name.
163171
"""
172+
# Do not trace if the url is blacklisted
173+
if utils.disable_tracing_url(request.path, self._blacklist_paths):
174+
return
175+
164176
try:
165177
# Get the current span and set the span name to the current
166178
# function name of the request.
@@ -171,6 +183,10 @@ def process_view(self, request, view_func, *args, **kwargs):
171183
log.error('Failed to trace request', exc_info=True)
172184

173185
def process_response(self, request, response):
186+
# Do not trace if the url is blacklisted
187+
if utils.disable_tracing_url(request.path, self._blacklist_paths):
188+
return response
189+
174190
try:
175191
tracer = _get_current_request_tracer()
176192
tracer.add_label_to_current_span(

trace/opencensus/trace/ext/flask/flask_middleware.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from opencensus.trace import execution_context
2020
from opencensus.trace.propagation import google_cloud_format
2121
from opencensus.trace.exporters import print_exporter
22+
from opencensus.trace.ext import utils
2223
from opencensus.trace.samplers import always_on
2324
from opencensus.trace import request_tracer
2425

@@ -37,6 +38,9 @@ class FlaskMiddleware(object):
3738
:type app: :class: `~flask.Flask`
3839
:param app: A flask application.
3940
41+
:type blacklist_paths: list
42+
:param blacklist_paths: Paths that do not trace.
43+
4044
:type sampler: :class: `type`
4145
:param sampler: Class for creating new Sampler objects. It should extend
4246
from the base :class:`.Sampler` type and implement
@@ -48,8 +52,16 @@ class FlaskMiddleware(object):
4852
:param exporter: Class for creating new exporter objects. Default to
4953
:class:`.PrintExporter`. The rest option is
5054
:class:`.FileExporter`.
55+
56+
:type propagator: :class: 'type'
57+
:param propagator: Class for creating new propagator objects. Default to
58+
:class:`.GoogleCloudFormatPropagator`. The rest option
59+
are :class:`.BinaryFormatPropagator`,
60+
:class:`.TextFormatPropagator` and
61+
:class:`.TraceContextPropagator`.
5162
"""
52-
def __init__(self, app, sampler=None, exporter=None, propagator=None):
63+
def __init__(self, app, blacklist_paths=None, sampler=None, exporter=None,
64+
propagator=None):
5365
if sampler is None:
5466
sampler = always_on.AlwaysOnSampler()
5567

@@ -60,6 +72,7 @@ def __init__(self, app, sampler=None, exporter=None, propagator=None):
6072
propagator = google_cloud_format.GoogleCloudFormatPropagator()
6173

6274
self.app = app
75+
self.blacklist_paths = blacklist_paths
6376
self.sampler = sampler
6477
self.exporter = exporter
6578
self.propagator = propagator
@@ -74,6 +87,10 @@ def _before_request(self):
7487
7588
See: http://flask.pocoo.org/docs/0.12/api/#flask.Flask.before_request
7689
"""
90+
# Do not trace if the url is blacklisted
91+
if utils.disable_tracing_url(flask.request.url, self.blacklist_paths):
92+
return
93+
7794
try:
7895
header = get_flask_header()
7996
span_context = self.propagator.from_header(header)
@@ -100,6 +117,10 @@ def _after_request(self, response):
100117
101118
See: http://flask.pocoo.org/docs/0.12/api/#flask.Flask.after_request
102119
"""
120+
# Do not trace if the url is blacklisted
121+
if utils.disable_tracing_url(flask.request.url, self.blacklist_paths):
122+
return response
123+
103124
try:
104125
tracer = execution_context.get_opencensus_tracer()
105126
tracer.add_label_to_current_span(

trace/opencensus/trace/ext/utils.py

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

15+
import re
16+
17+
# By default the blacklist urls are not tracing, currently just include the
18+
# health check url. The paths are literal string matched instead of regular
19+
# expressions. Do not include the '/' at the beginning of the path.
20+
DEFAULT_BLACKLIST_PATHS = [
21+
'_ah/health',
22+
]
23+
24+
# Pattern for matching the 'https://', 'http://', 'ftp://' part.
25+
URL_PATTERN = '^(https?|ftp):\\/\\/'
26+
1527

1628
def get_func_name(func):
1729
"""Return a name which includes the module name and function name."""
@@ -23,3 +35,31 @@ def get_func_name(func):
2335
return '{}.{}'.format(module_name, func_name)
2436

2537
return func_name
38+
39+
40+
def disable_tracing_url(url, blacklist_paths=None):
41+
"""Disable tracing on the provided blacklist paths, by default not tracing
42+
the health check request.
43+
44+
If the url path starts with the blacklisted path, return True.
45+
46+
:type blacklist_paths: list
47+
:param blacklist_paths: Paths that not tracing.
48+
49+
:rtype: bool
50+
:returns: True if not tracing, False if tracing.
51+
"""
52+
if blacklist_paths is None:
53+
blacklist_paths = DEFAULT_BLACKLIST_PATHS
54+
55+
# Remove the 'https?|ftp://' if exists
56+
url = re.sub(URL_PATTERN, '', url)
57+
58+
# Split the url by the first '/' and get the path part
59+
url_path = url.split('/', 1)[1]
60+
61+
for path in blacklist_paths:
62+
if url_path.startswith(path):
63+
return True
64+
65+
return False

trace/tests/system/django/app/settings.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,9 @@
7272
'GoogleCloudFormatPropagator',
7373
}
7474

75-
DEFAULT_DJANGO_TRACER_PARAMS = {
76-
'SAMPLING_RATE': 0.5,
77-
'GCP_REPORTER_PROJECT': os.environ.get('GCLOUD_PROJECT_PYTHON'),
78-
'ZIPKIN_REPORTER_SERVICE_NAME': 'my_service',
79-
'ZIPKIN_REPORTER_HOST_NAME': 'localhost',
80-
'ZIPKIN_REPORTER_PORT': 9411,
81-
}
82-
8375
OPENCENSUS_TRACE_PARAMS = {
8476
'SAMPLING_RATE': 0.5,
77+
'BLACKLIST_PATHS': ['_ah/health',]
8578
}
8679

8780
# Internationalization

trace/tests/unit/ext/django/test_middleware.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,57 @@ def test_process_request(self):
198198

199199
self.assertEqual(span.name, 'mock.mock.Mock')
200200

201+
def test_blacklist_path(self):
202+
from django.test import RequestFactory
203+
204+
from opencensus.trace.ext.django import middleware
205+
from opencensus.trace.tracer import base
206+
from opencensus.trace.tracer import noop_tracer
207+
from opencensus.trace.ext import utils
208+
from opencensus.trace import execution_context
209+
210+
execution_context.clear()
211+
212+
blacklist_paths = ['test_blacklist_path',]
213+
params = {'BLACKLIST_PATHS': ['test_blacklist_path',]}
214+
patch_params = mock.patch(
215+
'opencensus.trace.ext.django.middleware.settings.params',
216+
params)
217+
218+
with patch_params:
219+
middleware_obj = middleware.OpencensusMiddleware()
220+
221+
django_request = RequestFactory().get('/test_blacklist_path')
222+
disabled = utils.disable_tracing_url(django_request.path,
223+
blacklist_paths)
224+
self.assertTrue(disabled)
225+
self.assertEqual(middleware_obj._blacklist_paths, blacklist_paths)
226+
227+
# test process_request
228+
middleware_obj.process_request(django_request)
229+
230+
tracer = middleware._get_current_request_tracer()
231+
span = tracer.current_span()
232+
233+
# process view
234+
view_func = mock.Mock()
235+
middleware_obj.process_view(django_request, view_func)
236+
237+
tracer = middleware._get_current_request_tracer()
238+
span = tracer.current_span()
239+
240+
assert isinstance(span, base.NullContextManager)
241+
242+
# process response
243+
django_response = mock.Mock()
244+
django_response.status_code = 200
245+
246+
middleware_obj.process_response(django_request, django_response)
247+
248+
tracer = middleware._get_current_request_tracer()
249+
span = tracer.current_span()
250+
assert isinstance(span, base.NullContextManager)
251+
201252
def test_process_response(self):
202253
from django.test import RequestFactory
203254

trace/tests/unit/ext/flask/test_flask_middleware.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ def create_app():
3434
def index():
3535
return 'test flask trace' # pragma: NO COVER
3636

37+
@app.route('/_ah/health')
38+
def health_check():
39+
return 'test health check' # pragma: NO COVER
40+
3741
return app
3842

3943
def tearDown(self):
@@ -109,6 +113,31 @@ def test__before_request(self):
109113
span_context = tracer.span_context
110114
self.assertEqual(span_context.trace_id, trace_id)
111115

116+
def test__before_request_blacklist(self):
117+
from opencensus.trace import execution_context
118+
from opencensus.trace.tracer import base
119+
from opencensus.trace.tracer import noop_tracer
120+
121+
flask_trace_header = 'X_CLOUD_TRACE_CONTEXT'
122+
trace_id = '2dd43a1d6b2549c6bc2a1a54c2fc0b05'
123+
span_id = 1234
124+
flask_trace_id = '{}/{}'.format(trace_id, span_id)
125+
126+
app = self.create_app()
127+
flask_middleware.FlaskMiddleware(app=app)
128+
context = app.test_request_context(
129+
path='/_ah/health',
130+
headers={flask_trace_header: flask_trace_id})
131+
132+
with context:
133+
app.preprocess_request()
134+
tracer = execution_context.get_opencensus_tracer()
135+
assert isinstance(tracer, noop_tracer.NoopTracer)
136+
137+
span = tracer.current_span()
138+
139+
assert isinstance(span, base.NullContextManager)
140+
112141
def test_header_encoding(self):
113142
# The test is for detecting the encoding compatibility issue in
114143
# Python2 and Python3 and what flask does for encoding the headers.
@@ -203,3 +232,24 @@ def test__after_request_sampled(self):
203232
headers={flask_trace_header: flask_trace_id})
204233

205234
self.assertEqual(response.status_code, 200)
235+
236+
def test__after_request_blacklist(self):
237+
from opencensus.trace import execution_context
238+
from opencensus.trace.tracer import noop_tracer
239+
240+
flask_trace_header = 'X_CLOUD_TRACE_CONTEXT'
241+
trace_id = '2dd43a1d6b2549c6bc2a1a54c2fc0b05'
242+
span_id = 1234
243+
flask_trace_id = '{}/{}'.format(trace_id, span_id)
244+
245+
app = self.create_app()
246+
flask_middleware.FlaskMiddleware(app=app)
247+
248+
response = app.test_client().get(
249+
'/_ah/health',
250+
headers={flask_trace_header: flask_trace_id})
251+
252+
tracer = execution_context.get_opencensus_tracer()
253+
254+
self.assertEqual(response.status_code, 200)
255+
assert isinstance(tracer, noop_tracer.NoopTracer)

0 commit comments

Comments
 (0)