def test_configuration_get_from_twice(self): # ensure the configuration is the same if `get_from` is used # in the same instance instance = self.Klass() cfg1 = config.get_from(instance) cfg2 = config.get_from(instance) ok_(cfg1 is cfg2)
def test_configuration_override_instance(self): # ensure instance configuration doesn't override global settings global_cfg = config.get_from(self.Klass) global_cfg['distributed_tracing'] = True instance = self.Klass() cfg = config.get_from(instance) cfg['distributed_tracing'] = False ok_(config.get_from(self.Klass)['distributed_tracing'] is True) ok_(config.get_from(instance)['distributed_tracing'] is False)
def test_propagation_true(self): # ensure distributed tracing can be enabled manually cfg = config.get_from(self.session) cfg['distributed_tracing'] = True adapter = Adapter() self.session.mount('mock', adapter) with self.tracer.trace('root') as root: def matcher(request): return self.headers_here(self.tracer, request, root) adapter.register_uri('GET', 'mock://datadog/foo', additional_matcher=matcher, text='bar') resp = self.session.get('mock://datadog/foo') eq_(200, resp.status_code) eq_('bar', resp.text) spans = self.tracer.writer.spans root, req = spans eq_('root', root.name) eq_('requests.request', req.name) eq_(root.trace_id, req.trace_id) eq_(root.span_id, req.parent_id)
def _wrap_request(func, instance, args, kwargs): # Use any attached tracer if available, otherwise use the global tracer pin = Pin.get_from(instance) if should_skip_request(pin, instance): return func(*args, **kwargs) cfg = config.get_from(instance) try: # Create a new span and attach to this instance (so we can retrieve/update/close later on the response) span = pin.tracer.trace(span_name, span_type=SpanTypes.HTTP) setattr(instance, "_datadog_span", span) # propagate distributed tracing headers if cfg.get("distributed_tracing"): if len(args) > 3: headers = args[3] else: headers = kwargs.setdefault("headers", {}) HTTPPropagator.inject(span.context, headers) except Exception: log.debug("error configuring request", exc_info=True) span = getattr(instance, "_datadog_span", None) if span: span.finish() try: return func(*args, **kwargs) except Exception: span = getattr(instance, "_datadog_span", None) exc_info = sys.exc_info() if span: span.set_exc_info(*exc_info) span.finish() six.reraise(*exc_info)
def test_service_name_for_pin(self): # ensure for backward compatibility that changing the service # name via the Pin object also updates integration config Pin(service='intake').onto(self.Klass) instance = self.Klass() cfg = config.get_from(instance) eq_(cfg['service_name'], 'intake')
def test_propagation_true(self): # ensure distributed tracing can be enabled manually cfg = config.get_from(self.session) cfg["distributed_tracing"] = True adapter = Adapter() self.session.mount("mock", adapter) with self.tracer.trace("root") as root: def matcher(request): return self.headers_here(self.tracer, request, root) adapter.register_uri("GET", "mock://datadog/foo", additional_matcher=matcher, text="bar") resp = self.session.get("mock://datadog/foo") assert 200 == resp.status_code assert "bar" == resp.text spans = self.tracer.writer.spans root, req = spans assert "root" == root.name assert "requests.request" == req.name assert root.trace_id == req.trace_id assert root.span_id == req.parent_id
def test_service_attribute_priority(self): # ensure the `service` arg has highest priority over configuration # for backward compatibility global_config = { "service_name": "primary_service", } Pin(service="service", _config=global_config).onto(self.Klass) instance = self.Klass() cfg = config.get_from(instance) assert cfg["service_name"] == "service"
def test_service_attribute_priority(self): # ensure the `service` arg has highest priority over configuration # for backward compatibility global_config = { 'service_name': 'primary_service', } Pin(service='service', _config=global_config).onto(self.Klass) instance = self.Klass() cfg = config.get_from(instance) eq_(cfg['service_name'], 'service')
def test_configuration_copy(self): # ensure when a Pin is used, the given configuration is copied global_config = { 'service_name': 'service', } Pin(service='service', _config=global_config).onto(self.Klass) instance = self.Klass() cfg = config.get_from(instance) cfg['service_name'] = 'metrics' eq_(global_config['service_name'], 'service')
def test_user_set_service(self): # ensure a service name set by the user has precedence cfg = config.get_from(self.session) cfg["service"] = "clients" out = self.session.get(URL_200) assert out.status_code == 200 spans = self.pop_spans() assert len(spans) == 1 s = spans[0] assert s.service == "clients"
def test_configuration_copy(self): # ensure when a Pin is used, the given configuration is copied global_config = { "service_name": "service", } Pin(service="service", _config=global_config).onto(self.Klass) instance = self.Klass() cfg = config.get_from(instance) cfg["service_name"] = "metrics" assert global_config["service_name"] == "service"
def _distributed_tracing(self): """Deprecated: this method has been deprecated in favor of the configuration system. It will be removed in newer versions of the Tracer. """ deprecation( name="client.distributed_tracing", message="Use the configuration object instead `config.get_from(client)['distributed_tracing'`", version="1.0.0", ) return config.get_from(self)["distributed_tracing"]
def _distributed_tracing(self): """Deprecated: this method has been deprecated in favor of the configuration system. It will be removed in newer versions of the Tracer. """ deprecation( name='client.distributed_tracing', message='Use the configuration object instead `config.get_from(client)[\'distributed_tracing\'`', version='1.0.0', ) return config.get_from(self)['distributed_tracing']
def test_split_by_domain_wrong(self): # ensure the split by domain doesn't crash in case of a wrong URL; # in that case, no spans are created cfg = config.get_from(self.session) cfg['split_by_domain'] = True with pytest.raises(MissingSchema): self.session.get('http:/some>thing') # We are wrapping `requests.Session.send` and this error gets thrown before that function spans = self.tracer.writer.pop() assert len(spans) == 0
def test_split_by_domain_remove_auth_in_url(self): # ensure that auth details are stripped from URL cfg = config.get_from(self.session) cfg['split_by_domain'] = True out = self.session.get('http://*****:*****@httpbin.org') eq_(out.status_code, 200) spans = self.tracer.writer.pop() eq_(len(spans), 1) s = spans[0] eq_(s.service, 'httpbin.org')
def test_split_by_domain_includes_port(self): # ensure that port is included if present in URL cfg = config.get_from(self.session) cfg['split_by_domain'] = True out = self.session.get('http://httpbin.org:80') eq_(out.status_code, 200) spans = self.tracer.writer.pop() eq_(len(spans), 1) s = spans[0] eq_(s.service, 'httpbin.org:80')
def test_user_set_service_name(self): # ensure a service name set by the user has precedence cfg = config.get_from(self.session) cfg['service_name'] = 'clients' out = self.session.get(URL_200) eq_(out.status_code, 200) spans = self.tracer.writer.pop() eq_(len(spans), 1) s = spans[0] eq_(s.service, 'clients')
def test_global_config_service_env_precedence(self): out = self.session.get(URL_200) assert out.status_code == 200 spans = self.pop_spans() assert spans[0].service == "override" cfg = config.get_from(self.session) cfg["service"] = "override2" out = self.session.get(URL_200) assert out.status_code == 200 spans = self.pop_spans() assert spans[0].service == "override2"
def _distributed_tracing_setter(self, value): """Deprecated: this method has been deprecated in favor of the configuration system. It will be removed in newer versions of the Tracer. """ deprecation( name='client.distributed_tracing', message= 'Use the configuration object instead `config.get_from(client)[\'distributed_tracing\'] = value`', version='1.0.0', ) config.get_from(self)['distributed_tracing'] = value
def test_split_by_domain_remove_auth_in_url(self): # ensure that auth details are stripped from URL cfg = config.get_from(self.session) cfg['split_by_domain'] = True out = self.session.get('http://*****:*****@httpbin.org') assert out.status_code == 200 spans = self.tracer.writer.pop() assert len(spans) == 1 s = spans[0] assert s.service == 'httpbin.org'
def test_split_by_domain_includes_port_path(self): # ensure that port is included if present in URL but not path cfg = config.get_from(self.session) cfg['split_by_domain'] = True out = self.session.get('http://httpbin.org:80/anything/v1/foo') assert out.status_code == 200 spans = self.tracer.writer.pop() assert len(spans) == 1 s = spans[0] assert s.service == 'httpbin.org:80'
def test_user_set_service_name(self): # ensure a service name set by the user has precedence cfg = config.get_from(self.session) cfg['service_name'] = 'clients' out = self.session.get(URL_200) assert out.status_code == 200 spans = self.tracer.writer.pop() assert len(spans) == 1 s = spans[0] assert s.service == 'clients'
def test_split_by_domain_includes_port(self): # ensure that port is included if present in URL cfg = config.get_from(self.session) cfg["split_by_domain"] = True out = self.session.get("http://httpbin.org:80") assert out.status_code == 200 spans = self.pop_spans() assert len(spans) == 1 s = spans[0] assert s.service == "httpbin.org:80"
def _wrap_request(func, instance, args, kwargs): """Trace the `Session.request` 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) method = kwargs.get('method') or args[0] url = kwargs.get('url') or args[1] headers = kwargs.get('headers', {}) parsed_uri = parse.urlparse(url) # sanitize url of query 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 hostname = parsed_uri.hostname if parsed_uri.port: hostname += ':{}'.format(parsed_uri.port) 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, headers) kwargs['headers'] = headers response = None try: response = func(*args, **kwargs) return response finally: try: span.set_tag(http.METHOD, 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) except Exception: log.debug("requests: error adding tags", exc_info=True)
def test_split_by_domain_precedence(self): # ensure the split by domain has precedence all the time cfg = config.get_from(self.session) cfg['split_by_domain'] = True cfg['service_name'] = 'intake' out = self.session.get(URL_200) eq_(out.status_code, 200) spans = self.tracer.writer.pop() eq_(len(spans), 1) s = spans[0] eq_(s.service, 'httpbin.org')
def test_split_by_domain_precedence(self): # ensure the split by domain has precedence all the time cfg = config.get_from(self.session) cfg['split_by_domain'] = True cfg['service_name'] = 'intake' out = self.session.get(URL_200) assert out.status_code == 200 spans = self.tracer.writer.pop() assert len(spans) == 1 s = spans[0] assert s.service == 'httpbin.org'
def test_split_by_domain(self): # ensure a service name is generated by the domain name # of the ongoing call cfg = config.get_from(self.session) cfg['split_by_domain'] = True out = self.session.get(URL_200) assert out.status_code == 200 spans = self.tracer.writer.pop() assert len(spans) == 1 s = spans[0] assert s.service == 'httpbin.org'
def test_split_by_domain_precedence(self): # ensure the split by domain has precedence all the time cfg = config.get_from(self.session) cfg["split_by_domain"] = True cfg["service_name"] = "intake" out = self.session.get(URL_200) assert out.status_code == 200 spans = self.pop_spans() assert len(spans) == 1 s = spans[0] assert s.service == "httpbin.org"
def test_split_by_domain_wrong(self): # ensure the split by domain doesn't crash in case of a wrong URL; # in that case, the default service name must be used cfg = config.get_from(self.session) cfg['split_by_domain'] = True with assert_raises(MissingSchema): self.session.get('http:/some>thing') spans = self.tracer.writer.pop() eq_(len(spans), 1) s = spans[0] eq_(s.service, 'requests')
def test_split_by_domain(self): # ensure a service name is generated by the domain name # of the ongoing call cfg = config.get_from(self.session) cfg['split_by_domain'] = True out = self.session.get(URL_200) eq_(out.status_code, 200) spans = self.tracer.writer.pop() eq_(len(spans), 1) s = spans[0] eq_(s.service, 'httpbin.org')
def test_propagation_false(self): # ensure distributed tracing can be disabled cfg = config.get_from(self.session) cfg['distributed_tracing'] = False adapter = Adapter() self.session.mount('mock', adapter) with self.tracer.trace('root'): def matcher(request): return self.headers_not_here(self.tracer, request) adapter.register_uri('GET', 'mock://datadog/foo', additional_matcher=matcher, text='bar') resp = self.session.get('mock://datadog/foo') eq_(200, resp.status_code) eq_('bar', resp.text)
def test_configuration_copy_upside_down(self): # ensure when a Pin is created, it does not copy the given configuration # until it's used for at least once global_config = { 'service_name': 'service', } Pin(service='service', _config=global_config).onto(self.Klass) # override the global config: users do that before using the integration global_config['service_name'] = 'metrics' # use the Pin via `get_from` instance = self.Klass() cfg = config.get_from(instance) # it should have users updated value eq_(cfg['service_name'], 'metrics')
def test_user_service_name_precedence(self): # ensure the user service name takes precedence over # the parent Span cfg = config.get_from(self.session) cfg['service_name'] = 'clients' with self.tracer.trace('parent.span', service='web'): out = self.session.get(URL_200) eq_(out.status_code, 200) spans = self.tracer.writer.pop() eq_(len(spans), 2) s = spans[1] eq_(s.name, 'requests.request') eq_(s.service, 'clients')
def _wrap_request(func, instance, args, kwargs): """Trace the `Session.request` 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) method = kwargs.get('method') or args[0] url = kwargs.get('url') or args[1] headers = kwargs.get('headers', {}) parsed_uri = parse.urlparse(url) 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, netloc=parsed_uri.netloc) # propagate distributed tracing headers if config.get_from(instance).get('distributed_tracing'): propagator = HTTPPropagator() propagator.inject(span.context, headers) kwargs['headers'] = headers response = None try: response = func(*args, **kwargs) return response finally: try: span.set_tag(http.METHOD, method.upper()) span.set_tag(http.URL, 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) except Exception: log.debug("requests: error adding tags", exc_info=True)
def test_propagation_true(self): # ensure distributed tracing can be enabled cfg = config.get_from(self.session) cfg['distributed_tracing'] = True adapter = Adapter() self.session.mount('mock', adapter) with self.tracer.trace('root') as root: def matcher(request): return self.headers_here(self.tracer, request, root) adapter.register_uri('GET', 'mock://datadog/foo', additional_matcher=matcher, text='bar') resp = self.session.get('mock://datadog/foo') eq_(200, resp.status_code) eq_('bar', resp.text) spans = self.tracer.writer.spans root, req = spans eq_('root', root.name) eq_('requests.request', req.name) eq_(root.trace_id, req.trace_id) eq_(root.span_id, req.parent_id)
def _extract_service_name(session, span, netloc=None): """Extracts the right service name based on the following logic: - `requests` is the default service name - users can change it via `session.service_name = 'clients'` - if the Span doesn't have a parent, use the set service name or fallback to the default - if the Span has a parent, use the set service name or the parent service value if the set service name is the default - if `split_by_domain` is used, always override users settings and use the network location as a service name The priority can be represented as: Updated service name > parent service name > default to `requests`. """ cfg = config.get_from(session) if cfg['split_by_domain'] and netloc: return netloc service_name = cfg['service_name'] if (service_name == DEFAULT_SERVICE and span._parent is not None and span._parent.service is not None): service_name = span._parent.service return service_name
def test_configuration_get_from(self): # ensure a dictionary is returned cfg = config.get_from(self.Klass) ok_(isinstance(cfg, dict))
def test_configuration_set(self): # ensure the configuration can be updated in the Pin instance = self.Klass() cfg = config.get_from(instance) cfg['distributed_tracing'] = True ok_(config.get_from(instance)['distributed_tracing'] is True)
def test_global_configuration_inheritance(self): # ensure global configuration is inherited when it's set cfg = config.get_from(self.Klass) cfg['distributed_tracing'] = True instance = self.Klass() ok_(config.get_from(instance)['distributed_tracing'] is True)