def test_no_whitelist(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ store_response_headers({"Content-Type": "some;value",}, span, integration_config) assert span.get_tag("http.response.headers.content-type") is None
def test_whitelist_case_insensitive(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers("CoNtEnT-tYpE") store_response_headers({"cOnTeNt-TyPe": "some;value",}, span, integration_config) assert span.get_tag("http.response.headers.content-type") == "some;value"
def test_numbers_in_headers_names_are_allowed(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers("Content-Type123") store_response_headers({"Content-Type123": "some;value",}, span, integration_config) assert span.get_tag("http.response.headers.content-type123") == "some;value"
def test_value_not_trim_leading_trailing_spaced(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers("Content-Type") store_response_headers({"Content-Type": " some;value ",}, span, integration_config) assert span.get_tag("http.response.headers.content-type") == " some;value "
def test_whitelist_exact(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers("content-type") store_response_headers({"Content-Type": "some;value",}, span, integration_config) assert span.get_tag("http.response.headers.content-type") == "some;value"
def test_no_allowlist(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ store_response_headers({ 'Content-Type': 'some;value', }, span, integration_config) assert span.get_tag('http.response.headers.content-type') is None
def test_non_allowed_chars_replaced(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ # See: https://docs.datadoghq.com/tagging/#defining-tags integration_config.http.trace_headers("C!#ontent-Type") store_response_headers({"C!#ontent-Type": "some;value",}, span, integration_config) assert span.get_tag("http.response.headers.c__ontent-type") == "some;value"
def update_span(response): if isinstance(response, sanic.response.BaseHTTPResponse): status_code = response.status store_response_headers(response.headers, span, config.sanic) else: # invalid response causes ServerError exception which must be handled status_code = 500 trace_utils.set_http_meta(span, config.sanic, status_code=status_code) span.finish()
async def wrapped_send(message): if span and message.get("type") == "http.response.start": if "status" in message: status_code = message["status"] span.set_tag(http.STATUS_CODE, status_code) if "headers" in message: store_response_headers(message["headers"], span, config.asgi) return await send(message)
def test_period_is_replaced_by_underscore(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ # Deviation from https://docs.datadoghq.com/tagging/#defining-tags in order to allow # consistent representation of headers having the period in the name. integration_config.http.trace_headers("api.token") store_response_headers({"api.token": "some;value",}, span, integration_config) assert span.get_tag("http.response.headers.api_token") == "some;value"
def test_whitelist_case_insensitive(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers('CoNtEnT-tYpE') store_response_headers({ 'cOnTeNt-TyPe': 'some;value', }, span, integration_config) assert span.get_tag( 'http.response.headers.content-type') == 'some;value'
def test_whitelist_exact(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers('content-type') store_response_headers({ 'Content-Type': 'some;value', }, span, integration_config) assert span.get_tag( 'http.response.headers.content-type') == 'some;value'
def test_key_trim_leading_trailing_spaced(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers('Content-Type') store_response_headers({ ' Content-Type ': 'some;value', }, span, integration_config) assert span.get_tag( 'http.response.headers.content-type') == 'some;value'
async def send_with_tracing(message: Message) -> None: span = self.tracer.current_span() if span and message.get("type") == "http.response.start": if "status" in message: status_code: int = message["status"] span.set_tag(http_tags.STATUS_CODE, str(status_code)) if "headers" in message: response_headers = Headers(raw=message["headers"]) store_response_headers(response_headers, span, config.asgi) await send(message)
async def wrapped_send(message): if span and message.get("type") == "http.response.start": if "status" in message: status_code = message["status"] span.set_tag(http.STATUS_CODE, status_code) if 500 <= int(status_code) < 600: span.error = 1 if "headers" in message: store_response_headers(message["headers"], span, self.integration_config) return await send(message)
def test_allowed_chars_not_replaced_in_tag_name(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ # See: https://docs.datadoghq.com/tagging/#defining-tags integration_config.http.trace_headers('C0n_t:e/nt-Type') store_response_headers({ 'C0n_t:e/nt-Type': 'some;value', }, span, integration_config) assert span.get_tag( 'http.response.headers.c0n_t:e/nt-type') == 'some;value'
def update_span(response): error = 0 if isinstance(response, sanic.response.BaseHTTPResponse): status_code = response.status if 500 <= response.status < 600: error = 1 store_response_headers(response.headers, span, config.sanic) else: # invalid response causes ServerError exception which must be handled status_code = 500 error = 1 span.set_tag(http.STATUS_CODE, status_code) span.error = error span.finish()
def test_store_multiple_response_headers_as_dict(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers(["Content-Type", "Max-Age"]) store_response_headers( {"Content-Type": "some;value;content-type", "Max-Age": "some;value;max_age", "Other": "some;value;other",}, span, integration_config, ) assert span.get_tag("http.response.headers.content-type") == "some;value;content-type" assert span.get_tag("http.response.headers.max-age") == "some;value;max_age" assert None is span.get_tag("http.response.headers.other")
async def wrapped_send(message): if span and message.get( "type") == "http.response.start" and "status" in message: status_code = message["status"] else: status_code = None trace_utils.set_http_meta(span, self.integration_config, status_code=status_code) if "headers" in message: store_response_headers(message["headers"], span, self.integration_config) return await send(message)
async def send_with_tracing(message: Message) -> None: span = self.tracer.current_span() if not span: # Unexpected. await send(message) return if message["type"] == "http.response.start": status_code: int = message["status"] response_headers = Headers(raw=message["headers"]) store_response_headers(response_headers, span, config.asgi) span.set_tag(http_tags.STATUS_CODE, str(status_code)) await send(message)
def test_store_multiple_response_headers_as_dict(self, span, integration_config): """ :type span: Span :type integration_config: IntegrationConfig """ integration_config.http.trace_headers(['Content-Type', 'Max-Age']) store_response_headers( { 'Content-Type': 'some;value;content-type', 'Max-Age': 'some;value;max_age', 'Other': 'some;value;other', }, span, integration_config) assert span.get_tag( 'http.response.headers.content-type') == 'some;value;content-type' assert span.get_tag( 'http.response.headers.max-age') == 'some;value;max_age' assert None is span.get_tag('http.response.headers.other')
def process_response(self, req, resp, resource, req_succeeded=None): # req_succeded is not a kwarg in the API, but we need that to support # Falcon 1.0 that doesn't provide this argument span = self.tracer.current_span() if not span: return # unexpected status = httpx.normalize_status_code(resp.status) # Note: any response header set after this line will not be stored in the span store_response_headers(resp._headers, span, config.falcon) # FIXME[matt] falcon does not map errors or unmatched routes # to proper status codes, so we we have to try to infer them # here. See https://github.com/falconry/falcon/issues/606 if resource is None: status = '404' span.resource = '%s 404' % req.method span.set_tag(httpx.STATUS_CODE, status) span.finish() return err_type = sys.exc_info()[0] if err_type is not None: if req_succeeded is None: # backward-compatibility with Falcon 1.0; any version # greater than 1.0 has req_succeded in [True, False] # TODO[manu]: drop the support at some point status = _detect_and_set_status_error(err_type, span) elif req_succeeded is False: # Falcon 1.1+ provides that argument that is set to False # if get an Exception (404 is still an exception) status = _detect_and_set_status_error(err_type, span) span.set_tag(httpx.STATUS_CODE, status) if 500 <= int(status) < 600: span.error = 1 # Emit span hook for this response # DEV: Emit before closing so they can overwrite `span.resource` if they want config.falcon.hooks.emit('request', span, req, resp) # Close the span span.finish()
def traced_get_response(django, pin, func, instance, args, kwargs): """Trace django.core.handlers.base.BaseHandler.get_response() (or other implementations). This is the main entry point for requests. Django requests are handled by a Handler.get_response method (inherited from base.BaseHandler). This method invokes the middleware chain and returns the response generated by the chain. """ request = kwargs.get("request", args[0]) if request is None: return func(*args, **kwargs) try: request_headers = request.META if config.django.distributed_tracing_enabled: context = propagator.extract(request_headers) if context.trace_id: pin.tracer.context_provider.activate(context) # Determine the resolver and resource name for this request resolver = get_resolver(getattr(request, "urlconf", None)) if django.VERSION < (1, 10, 0): error_type_404 = django.core.urlresolvers.Resolver404 else: error_type_404 = django.urls.exceptions.Resolver404 route = None resolver_match = None resource = request.method try: # Resolve the requested url and build resource name pieces resolver_match = resolver.resolve(request.path_info) handler, _, _ = resolver_match handler = func_name(handler) urlpattern = "" resource_format = None if config.django.use_handler_resource_format: resource_format = "{method} {handler}" else: # In Django >= 2.2.0 we can access the original route or regex pattern # TODO: Validate if `resolver.pattern.regex.pattern` is available on django<2.2 if django.VERSION >= (2, 2, 0): route = utils.get_django_2_route(resolver, resolver_match) resource_format = "{method} {urlpattern}" else: resource_format = "{method} {handler}" if route is not None: urlpattern = route resource = resource_format.format(method=request.method, urlpattern=urlpattern, handler=handler) except error_type_404: # Normalize all 404 requests into a single resource name # DEV: This is for potential cardinality issues resource = "{0} 404".format(request.method) except Exception: log.debug( "Failed to resolve request path %r with path info %r", request, getattr(request, "path_info", "not-set"), exc_info=True, ) except Exception: log.debug("Failed to trace django request %r", args, exc_info=True) return func(*args, **kwargs) else: with pin.tracer.trace( "django.request", resource=resource, service=trace_utils.int_service(pin, config.django), span_type=SpanTypes.HTTP, ) as span: analytics_sr = config.django.get_analytics_sample_rate(use_global_config=True) if analytics_sr is not None: span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, analytics_sr) if config.django.http.trace_query_string: span.set_tag(http.QUERY_STRING, request_headers["QUERY_STRING"]) # Not a 404 request if resolver_match: span.set_tag("django.view", resolver_match.view_name) utils.set_tag_array(span, "django.namespace", resolver_match.namespaces) # Django >= 2.0.0 if hasattr(resolver_match, "app_names"): utils.set_tag_array(span, "django.app", resolver_match.app_names) if route: span.set_tag("http.route", route) # Set HTTP Request tags span.set_tag(http.URL, utils.get_request_uri(request)) response = func(*args, **kwargs) # Note: this call must be done after the function call because # some attributes (like `user`) are added to the request through # the middleware chain _set_request_tags(django, span, request) if response: span.set_tag(http.STATUS_CODE, response.status_code) if 500 <= response.status_code < 600: span.error = 1 span.set_tag("django.response.class", func_name(response)) if hasattr(response, "template_name"): # template_name is a bit of a misnomer, as it could be any of: # a list of strings, a tuple of strings, a single string, or an instance of Template # for more detail, see: # https://docs.djangoproject.com/en/3.0/ref/template-response/#django.template.response.SimpleTemplateResponse.template_name template = response.template_name if isinstance(template, six.string_types): template_names = [template] elif isinstance( template, ( list, tuple, ), ): template_names = template elif hasattr(template, "template"): # ^ checking by attribute here because # django backend implementations don't have a common base # `.template` is also the most consistent across django versions template_names = [template.template.name] else: template_names = None utils.set_tag_array(span, "django.response.template", template_names) headers = dict(response.items()) store_response_headers(headers, span, config.django) return response
def test_it_does_not_break_if_headers_are_not_a_dict( self, span, integration_config): store_request_headers(list(), span, integration_config) store_response_headers(list(), span, integration_config)
def test_it_does_not_break_if_no_headers(self, span, integration_config): store_request_headers(None, span, integration_config) store_response_headers(None, span, integration_config)
def update_span(response): span.set_tag(http.STATUS_CODE, response.status) if 500 <= response.status < 600: span.error = 1 store_response_headers(response.headers, span, config.sanic) span.finish()
def _wrap_send(func, instance, args, kwargs): """Trace the `Session.send` instance method""" # TODO[manu]: we already offer a way to provide the Global Tracer # and is ddtrace.tracer; it's used only inside our tests and can # be easily changed by providing a TracingTestCase that sets common # tracing functionalities. tracer = getattr(instance, 'datadog_tracer', ddtrace.tracer) # skip if tracing is not enabled if not tracer.enabled: return func(*args, **kwargs) request = kwargs.get('request') or args[0] if not request: return func(*args, **kwargs) # sanitize url of query parsed_uri = parse.urlparse(request.url) hostname = parsed_uri.hostname if parsed_uri.port: hostname = '{}:{}'.format(hostname, parsed_uri.port) sanitized_url = parse.urlunparse(( parsed_uri.scheme, parsed_uri.netloc, parsed_uri.path, parsed_uri.params, None, # drop parsed_uri.query parsed_uri.fragment )) with tracer.trace('requests.request', span_type=http.TYPE) as span: # update the span service name before doing any action span.service = _extract_service_name(instance, span, hostname=hostname) # propagate distributed tracing headers if config.get_from(instance).get('distributed_tracing'): propagator = HTTPPropagator() propagator.inject(span.context, request.headers) # Storing request headers in the span store_request_headers(request.headers, span, config.requests) response = None try: response = func(*args, **kwargs) # Storing response headers in the span. Note that response.headers is not a dict, but an iterable # requests custom structure, that we convert to a dict if hasattr(response, 'headers'): store_response_headers(dict(response.headers), span, config.requests) return response finally: try: span.set_tag(http.METHOD, request.method.upper()) span.set_tag(http.URL, sanitized_url) if response is not None: span.set_tag(http.STATUS_CODE, response.status_code) # `span.error` must be an integer span.error = int(500 <= response.status_code) # Storing response headers in the span. # Note that response.headers is not a dict, but an iterable # requests custom structure, that we convert to a dict response_headers = dict(getattr(response, 'headers', {})) store_response_headers(response_headers, span, config.requests) except Exception: log.debug("requests: error adding tags", exc_info=True)
def _wrap_send(func, instance, args, kwargs): """Trace the `Session.send` instance method""" # TODO[manu]: we already offer a way to provide the Global Tracer # and is ddtrace.tracer; it's used only inside our tests and can # be easily changed by providing a TracingTestCase that sets common # tracing functionalities. tracer = getattr(instance, "datadog_tracer", ddtrace.tracer) # skip if tracing is not enabled if not tracer.enabled: return func(*args, **kwargs) request = kwargs.get("request") or args[0] if not request: return func(*args, **kwargs) # sanitize url of query parsed_uri = parse.urlparse(request.url) hostname = parsed_uri.hostname if parsed_uri.port: hostname = "{}:{}".format(hostname, parsed_uri.port) sanitized_url = parse.urlunparse(( parsed_uri.scheme, parsed_uri.netloc, parsed_uri.path, parsed_uri.params, None, # drop parsed_uri.query parsed_uri.fragment, )) with tracer.trace("requests.request", span_type=SpanTypes.HTTP) as span: span.set_tag(SPAN_MEASURED_KEY) # update the span service name before doing any action span.service = _extract_service_name(instance, span, hostname=hostname) # Configure trace search sample rate # DEV: analytics enabled on per-session basis cfg = config.get_from(instance) analytics_enabled = cfg.get("analytics_enabled") if analytics_enabled: span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, cfg.get("analytics_sample_rate", True)) # propagate distributed tracing headers if cfg.get("distributed_tracing"): propagator = HTTPPropagator() propagator.inject(span.context, request.headers) # Storing request headers in the span store_request_headers(request.headers, span, config.requests) response = None try: response = func(*args, **kwargs) # Storing response headers in the span. Note that response.headers is not a dict, but an iterable # requests custom structure, that we convert to a dict if hasattr(response, "headers"): store_response_headers(dict(response.headers), span, config.requests) return response finally: try: span.set_tag(http.METHOD, request.method.upper()) span.set_tag(http.URL, sanitized_url) if config.requests.trace_query_string: span.set_tag(http.QUERY_STRING, parsed_uri.query) if response is not None: span.set_tag(http.STATUS_CODE, response.status_code) # `span.error` must be an integer span.error = int(500 <= response.status_code) # Storing response headers in the span. # Note that response.headers is not a dict, but an iterable # requests custom structure, that we convert to a dict response_headers = dict(getattr(response, "headers", {})) store_response_headers(response_headers, span, config.requests) except Exception: log.debug("requests: error adding tags", exc_info=True)