def _handle_response_or_error(span, response_or_error): # response_of_error should be a grpc.Future and so we expect to have # exception() and traceback() methods if a computation has resulted in # an exception being raised if ( not callable(getattr(response_or_error, 'exception', None)) and not callable(getattr(response_or_error, 'traceback', None)) ): return exception = response_or_error.exception() traceback = response_or_error.traceback() # pull out status code from gRPC response to use both for `grpc.status.code` # tag and the error type tag if the response is an exception status_code = to_unicode(response_or_error.code()) if exception is not None and traceback is not None: if isinstance(exception, grpc.RpcError): # handle internal gRPC exceptions separately to get status code and # details as tags properly exc_val = to_unicode(response_or_error.details()) span.set_tag(errors.ERROR_MSG, exc_val) span.set_tag(errors.ERROR_TYPE, status_code) span.set_tag(errors.ERROR_STACK, traceback) else: exc_type = type(exception) span.set_exc_info(exc_type, exception, traceback) status_code = to_unicode(response_or_error.code()) span.set_tag(constants.GRPC_STATUS_CODE_KEY, status_code)
def _handle_server_exception(server_context, span): if server_context is not None and \ hasattr(server_context, '_state') and \ server_context._state is not None: code = to_unicode(server_context._state.code) details = to_unicode(server_context._state.details) span.error = 1 span.set_tag(errors.ERROR_MSG, details) span.set_tag(errors.ERROR_TYPE, code)
def test_to_unicode_bytearray_double_decode(self): # Calling `compat.to_unicode` with an already decoded `bytearray` # This represents the double-decode issue, which can cause a `UnicodeEncodeError` # `bytearray('\xc3\xbf').decode('utf-8').decode('utf-8')` res = to_unicode(bytearray(b'\xc3\xbf').decode('utf-8')) assert type(res) == unicode assert res == u'ÿ'
def test_analytics_global_off_integration_default(self): """ When making a request When an integration trace search is not set and sample rate is set and globally trace search is disabled We expect the root span to not include tag """ # setup our test app @self.app.route('/hi/<name>') def hi(name): return 'hi %s' % name self._trace_app(self.tracer) with self.override_global_config(dict(analytics_enabled=False)): resp = self.app.get('/hi/dougie') assert resp.status_int == 200 assert compat.to_unicode(resp.body) == u'hi dougie' root = self.get_root_span() self.assertIsNone(root.get_metric(ANALYTICS_SAMPLE_RATE_KEY)) for span in self.spans: if span == root: continue self.assertIsNone(span.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
def test_analytics_global_off_integration_on(self): """ When making a request When an integration trace search is enabled and sample rate is set and globally trace search is disabled We expect the root span to have the appropriate tag """ # setup our test app @self.app.route('/hi/<name>') def hi(name): return 'hi %s' % name self._trace_app(self.tracer) with self.override_global_config(dict(analytics_enabled=False)): with self.override_config( 'bottle', dict(analytics_enabled=True, analytics_sample_rate=0.5)): resp = self.app.get('/hi/dougie') assert resp.status_int == 200 assert compat.to_unicode(resp.body) == u'hi dougie' root = self.get_root_span() root.assert_matches( name='bottle.request', metrics={ ANALYTICS_SAMPLE_RATE_KEY: 0.5, }, ) for span in self.spans: if span == root: continue self.assertIsNone(span.get_metric(ANALYTICS_SAMPLE_RATE_KEY))
def test_to_unicode_unicode_double_decode(self): # Calling `compat.to_unicode` on a unicode decoded string # This represents the double-decode issue, which can cause a `UnicodeEncodeError` # `'\xc3\xbf'.decode('utf-8').decode('utf-8')` res = to_unicode(b'\xc3\xbf'.decode('utf-8')) assert type(res) == unicode assert res == u'ÿ'
def test_200(self, query_string=''): if query_string: fqs = '?' + query_string else: fqs = '' # setup our test app @self.app.route('/hi/<name>') def hi(name): return 'hi %s' % name self._trace_app(self.tracer) # make a request resp = self.app.get('/hi/dougie' + fqs) assert resp.status_int == 200 assert compat.to_unicode(resp.body) == u'hi dougie' # validate it's traced spans = self.tracer.writer.pop() assert len(spans) == 1 s = spans[0] assert s.name == 'bottle.request' assert s.service == 'bottle-app' assert s.span_type == 'web' assert s.resource == 'GET /hi/<name>' assert s.get_tag('http.status_code') == '200' assert s.get_tag('http.method') == 'GET' assert s.get_tag(http.URL) == 'http://localhost:80/hi/dougie' if oteltrace.config.bottle.trace_query_string: assert s.get_tag(http.QUERY_STRING) == query_string else: assert http.QUERY_STRING not in s.meta services = self.tracer.writer.pop_services() assert services == {}
def test_not_distributed(self): # setup our test app @self.app.route('/hi/<name>') def hi(name): return 'hi %s' % name self._trace_app_not_distributed(self.tracer) # make a request headers = {'x-datadog-trace-id': '123', 'x-datadog-parent-id': '456'} resp = self.app.get('/hi/dougie', headers=headers) assert resp.status_int == 200 assert compat.to_unicode(resp.body) == u'hi dougie' # validate it's traced spans = self.tracer.writer.pop() assert len(spans) == 1 s = spans[0] assert s.name == 'bottle.request' assert s.service == 'bottle-app' assert s.resource == 'GET /hi/<name>' assert s.get_tag('http.status_code') == '200' assert s.get_tag('http.method') == 'GET' # check distributed headers assert 123 != s.trace_id assert 456 != s.parent_id
def test_redis_legacy(): # ensure the old interface isn't broken, but doesn't trace tracer = get_dummy_tracer() TracedRedisCache = get_traced_redis(tracer, 'foo') r = TracedRedisCache(port=REDIS_CONFIG['port']) r.set('a', 'b') got = r.get('a') assert compat.to_unicode(got) == 'b' assert not tracer.writer.pop()
def test_render(self): # render t = Template('Hello ${name}!') self.assertEqual(t.render(name='mako'), 'Hello mako!') spans = self.tracer.writer.pop() self.assertEqual(len(spans), 1) self.assertEqual(spans[0].service, 'mako') self.assertEqual(spans[0].span_type, 'template') self.assertEqual(spans[0].get_tag('mako.template_name'), '<memory>') self.assertEqual(spans[0].name, 'mako.template.render') self.assertEqual(spans[0].resource, '<memory>') # render_unicode t = Template('Hello ${name}!') self.assertEqual(t.render_unicode(name='mako'), to_unicode('Hello mako!')) spans = self.tracer.writer.pop() self.assertEqual(len(spans), 1) self.assertEqual(spans[0].service, 'mako') self.assertEqual(spans[0].span_type, 'template') self.assertEqual(spans[0].get_tag('mako.template_name'), '<memory>') self.assertEqual(spans[0].name, 'mako.template.render_unicode') self.assertEqual(spans[0].resource, '<memory>') # render_context t = Template('Hello ${name}!') buf = StringIO() c = Context(buf, name='mako') t.render_context(c) self.assertEqual(buf.getvalue(), 'Hello mako!') spans = self.tracer.writer.pop() self.assertEqual(len(spans), 1) self.assertEqual(spans[0].service, 'mako') self.assertEqual(spans[0].span_type, 'template') self.assertEqual(spans[0].get_tag('mako.template_name'), '<memory>') self.assertEqual(spans[0].name, 'mako.template.render_context') self.assertEqual(spans[0].resource, '<memory>')
def test_200(self): # setup our test app @self.app.route('/hi/<name>') def hi(name): return 'hi %s' % name self._trace_app(self.tracer) # make a request resp = self.app.get('/hi/dougie') assert resp.status_int == 200 assert compat.to_unicode(resp.body) == u'hi dougie' # validate it's traced spans = self.tracer.writer.pop() assert len(spans) == 1 s = spans[0] assert s.name == 'bottle.request' assert s.service == 'bottle-app' assert s.resource == 'GET /hi/<name>' assert s.get_tag('http.status_code') == '200' assert s.get_tag('http.method') == 'GET' services = self.tracer.writer.pop_services() assert services == {}
def test_to_unicode_unicode_encoded(self): # Calling `compat.to_unicode` on a unicode encoded string res = to_unicode(b'\xc3\xbf') assert type(res) == unicode assert res == u'ÿ'
def test_to_unicode_unicode_string(self): # Calling `compat.to_unicode` on a unicode string res = to_unicode(u'ÿ') assert type(res) == unicode assert res == u'ÿ'
def test_to_unicode_bytearray(self): # Calling `compat.to_unicode` with a `bytearray` containing unicode res = to_unicode(bytearray(b'\xc3\xbf')) assert type(res) == unicode assert res == u'ÿ'
def test_to_unicode_non_string(self): # Calling `compat.to_unicode` on non-string types assert to_unicode(1) == u'1' assert to_unicode(True) == u'True' assert to_unicode(None) == u'None' assert to_unicode(dict(key='value')) == u'{\'key\': \'value\'}'
def traced_wsgi_app(pin, wrapped, instance, args, kwargs): """ Wrapper for flask.app.Flask.wsgi_app This wrapper is the starting point for all requests. """ # DEV: This is safe before this is the args for a WSGI handler # https://www.python.org/dev/peps/pep-3333/ environ, start_response = args # Create a werkzeug request from the `environ` to make interacting with it easier # DEV: This executes before a request context is created request = werkzeug.Request(environ) # Configure distributed tracing if config.flask.get('distributed_tracing_enabled', False): propagator = HTTPPropagator() context = propagator.extract(request.headers) # Only need to activate the new context if something was propagated if context.trace_id: pin.tracer.context_provider.activate(context) # Default resource is method and path: # GET / # POST /save # We will override this below in `traced_dispatch_request` when we have a `RequestContext` and possibly a url rule resource = u'{} {}'.format(request.method, request.path) with pin.tracer.trace('flask.request', service=pin.service, resource=resource, span_type=http.TYPE) as s: # set analytics sample rate with global config enabled s.set_tag( ANALYTICS_SAMPLE_RATE_KEY, config.flask.get_analytics_sample_rate(use_global_config=True)) s.set_tag(FLASK_VERSION, flask_version_str) # Wrap the `start_response` handler to extract response code # DEV: We tried using `Flask.finalize_request`, which seemed to work, but gave us hell during tests # DEV: The downside to using `start_response` is we do not have a `Flask.Response` object here, # only `status_code`, and `headers` to work with # On the bright side, this works in all versions of Flask (or any WSGI app actually) def _wrap_start_response(func): def traced_start_response(status_code, headers): code, _, _ = status_code.partition(' ') try: code = int(code) except ValueError: pass # Override root span resource name to be `<method> 404` for 404 requests # DEV: We do this because we want to make it easier to see all unknown requests together # Also, we do this to reduce the cardinality on unknown urls # DEV: If we have an endpoint or url rule tag, then we don't need to do this, # we still want `GET /product/<int:product_id>` grouped together, # even if it is a 404 if not s.get_tag(FLASK_ENDPOINT) and not s.get_tag( FLASK_URL_RULE): s.resource = u'{} {}'.format(request.method, code) s.set_tag(http.STATUS_CODE, code) if 500 <= code < 600: s.error = 1 elif code in config.flask.get('extra_error_codes', set()): s.error = 1 return func(status_code, headers) return traced_start_response start_response = _wrap_start_response(start_response) # DEV: We set response status code in `_wrap_start_response` # DEV: Use `request.base_url` and not `request.url` to keep from leaking any query string parameters s.set_tag(http.URL, request.base_url) s.set_tag(http.METHOD, request.method) if config.flask.trace_query_string: s.set_tag(http.QUERY_STRING, compat.to_unicode(request.query_string)) return wrapped(environ, start_response)
def test_to_unicode_string(self): # Calling `compat.to_unicode` on a non-unicode string res = to_unicode(b'test') assert type(res) == unicode assert res == 'test'