def test_should_skip_request(self): """ When calling should_skip_request with an enabled Pin and non-internal request returns False with a disabled Pin and non-internal request returns True with an enabled Pin and internal request returns True with a disabled Pin and internal request returns True """ # Enabled Pin and non-internal request self.tracer.enabled = True request = self.get_http_connection(SOCKET) pin = Pin.get_from(request) self.assertFalse(should_skip_request(pin, request)) # Disabled Pin and non-internal request self.tracer.enabled = False request = self.get_http_connection(SOCKET) pin = Pin.get_from(request) self.assertTrue(should_skip_request(pin, request)) # Enabled Pin and internal request self.tracer.enabled = True request = self.get_http_connection(self.tracer.writer.api.hostname, self.tracer.writer.api.port) pin = Pin.get_from(request) self.assertTrue(should_skip_request(pin, request)) # Disabled Pin and internal request self.tracer.enabled = False request = self.get_http_connection(self.tracer.writer.api.hostname, self.tracer.writer.api.port) pin = Pin.get_from(request) self.assertTrue(should_skip_request(pin, request))
def _trace_method(self, method, resource, extra_tags, *args, **kwargs): pin = Pin.get_from(self) if not pin or not pin.enabled(): result = yield from method(*args, **kwargs) return result service = pin.service with pin.tracer.trace(self._datadog_name, service=service, resource=resource, span_type=SpanTypes.SQL) as s: s.set_tag(SPAN_MEASURED_KEY) s.set_tag(sql.QUERY, resource) s.set_tags(pin.tags) s.set_tags(extra_tags) # set analytics sample rate s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.aiopg.get_analytics_sample_rate()) try: result = yield from method(*args, **kwargs) return result finally: s.set_metric("db.rowcount", self.rowcount)
def _patched_search(func, instance, wrapt_args, wrapt_kwargs): """ wrapt_args is called the way it is to distinguish it from the 'args' argument to the algoliasearch.index.Index.search() method. """ if algoliasearch_version < (2, 0) and algoliasearch_version >= (1, 0): function_query_arg_name = 'args' elif algoliasearch_version >= (2, 0) and algoliasearch_version < (3, 0): function_query_arg_name = 'request_options' else: return func(*wrapt_args, **wrapt_kwargs) pin = Pin.get_from(instance) if not pin or not pin.enabled(): return func(*wrapt_args, **wrapt_kwargs) with pin.tracer.trace('algoliasearch.search', service=pin.service) as span: span.set_tag(SPAN_MEASURED_KEY) if not span.sampled: return func(*wrapt_args, **wrapt_kwargs) if config.algoliasearch.collect_query_text: span.set_tag('query.text', wrapt_kwargs.get('query', wrapt_args[0])) query_args = wrapt_kwargs.get(function_query_arg_name, wrapt_args[1] if len(wrapt_args) > 1 else None) if query_args and isinstance(query_args, dict): for query_arg, tag_name in QUERY_ARGS_DD_TAG_MAP.items(): value = query_args.get(query_arg) if value is not None: span.set_tag('query.args.{}'.format(tag_name), value) # Result would look like this # { # 'hits': [ # { # .... your search results ... # } # ], # 'processingTimeMS': 1, # 'nbHits': 1, # 'hitsPerPage': 20, # 'exhaustiveNbHits': true, # 'params': 'query=xxx', # 'nbPages': 1, # 'query': 'xxx', # 'page': 0 # } result = func(*wrapt_args, **wrapt_kwargs) if isinstance(result, dict): if result.get('processingTimeMS', None) is not None: span.set_metric('processing_time_ms', int(result['processingTimeMS'])) if result.get('nbHits', None) is not None: span.set_metric('number_of_hits', int(result['nbHits'])) return result
def pytest_configure(config): """Make sure the tracer on pin is the same as global one.""" from ddtrace.pin import Pin pin = Pin.get_from(config) if pin: pin.tracer.writer = tracer.writer
async def traced_13_execute_pipeline(func, instance, args, kwargs): pin = Pin.get_from(instance) if not pin or not pin.enabled(): return await func(*args, **kwargs) cmds = [] for _, cmd, cmd_args, _ in instance._pipeline: parts = [cmd] parts.extend(cmd_args) cmds.append(format_command_args(parts)) resource = "\n".join(cmds) with pin.tracer.trace( redisx.CMD, resource=resource, service=trace_utils.ext_service(pin, config.aioredis), span_type=SpanTypes.REDIS, ) as span: span.set_tags({ net.TARGET_HOST: instance._pool_or_conn.address[0], net.TARGET_PORT: instance._pool_or_conn.address[1], redisx.DB: instance._pool_or_conn.db or 0, }) span.set_tag(SPAN_MEASURED_KEY) span.set_tag(redisx.RAWCMD, resource) span.set_metric(redisx.PIPELINE_LEN, len(instance._pipeline)) # set analytics sample rate if enabled span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.aioredis.get_analytics_sample_rate()) return await func(*args, **kwargs)
async def traced_execute_command(func, instance, args, kwargs): pin = Pin.get_from(instance) if not pin or not pin.enabled(): return await func(*args, **kwargs) with _trace_redis_cmd(pin, config.aioredis, instance, args): return await func(*args, **kwargs)
def pytest_sessionstart(session): pin = Pin.get_from(session.config) if pin is not None: tracer_filters = pin.tracer._filters if not any( isinstance(tracer_filter, TraceCiVisibilityFilter) for tracer_filter in tracer_filters): tracer_filters += [TraceCiVisibilityFilter()] pin.tracer.configure(settings={"FILTERS": tracer_filters})
def traced_13_execute_command(func, instance, args, kwargs): # If we have a _RedisBuffer then we are in a pipeline if isinstance(instance.connection, _RedisBuffer): return func(*args, **kwargs) pin = Pin.get_from(instance) if not pin or not pin.enabled(): return func(*args, **kwargs) # Don't activate the span since this operation is performed as a future which concludes sometime later on in # execution so subsequent operations in the stack are not necessarily semantically related # (we don't want this span to be the parent of all other spans created before the future is resolved) parent = pin.tracer.current_span() span = pin.tracer.start_span( redisx.CMD, service=trace_utils.ext_service(pin, config.aioredis), span_type=SpanTypes.REDIS, activate=False, child_of=parent, ) span.set_tag(SPAN_MEASURED_KEY) query = format_command_args(args) span.resource = query span.set_tag(redisx.RAWCMD, query) if pin.tags: span.set_tags(pin.tags) span.set_tags({ net.TARGET_HOST: instance.address[0], net.TARGET_PORT: instance.address[1], redisx.DB: instance.db or 0, }) span.set_metric(redisx.ARGS_LEN, len(args)) # set analytics sample rate if enabled span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.aioredis.get_analytics_sample_rate()) def _finish_span(future): try: # Accessing the result will raise an exception if: # - The future was cancelled # - There was an error executing the future (`future.exception()`) # - The future is in an invalid state future.result() except Exception: span.set_exc_info(*sys.exc_info()) finally: span.finish() task = func(*args, **kwargs) # Execute command returns a coroutine when no free connections are available # https://github.com/aio-libs/aioredis-py/blob/v1.3.1/aioredis/pool.py#L191 task = asyncio.ensure_future(task) task.add_done_callback(_finish_span) return task
async def traced_execute_pipeline(func, instance, args, kwargs): pin = Pin.get_from(instance) if not pin or not pin.enabled(): return await func(*args, **kwargs) cmds = [format_command_args(c) for c, _ in instance.command_stack] resource = "\n".join(cmds) with _trace_redis_execute_pipeline(pin, config.aioredis, resource, instance): return await func(*args, **kwargs)
def pytest_runtest_protocol(item, nextitem): pin = Pin.get_from(item.config) if pin is None: yield return with pin.tracer.trace( ddtrace.config.pytest.operation_name, service=int_service(pin, ddtrace.config.pytest), resource=item.nodeid, span_type=SpanTypes.TEST.value, ) as span: span.context.dd_origin = ci.CI_APP_TEST_ORIGIN span.context.sampling_priority = AUTO_KEEP span.set_tags(pin.tags) span.set_tag(SPAN_KIND, KIND) span.set_tag(test.FRAMEWORK, FRAMEWORK) span.set_tag(test.NAME, item.name) if hasattr(item, "module"): span.set_tag(test.SUITE, item.module.__name__) elif hasattr(item, "dtest") and isinstance(item.dtest, DocTest): span.set_tag(test.SUITE, item.dtest.globs["__name__"]) span.set_tag(test.TYPE, SpanTypes.TEST.value) span.set_tag(test.FRAMEWORK_VERSION, pytest.__version__) # We preemptively set FAIL as a status, because if pytest_runtest_makereport is not called # (where the actual test status is set), it means there was a pytest error span.set_tag(test.STATUS, test.Status.FAIL.value) # Parameterized test cases will have a `callspec` attribute attached to the pytest Item object. # Pytest docs: https://docs.pytest.org/en/6.2.x/reference.html#pytest.Function if getattr(item, "callspec", None): parameters = { "arguments": {}, "metadata": {} } # type: Dict[str, Dict[str, str]] for param_name, param_val in item.callspec.params.items(): try: parameters["arguments"][param_name] = repr(param_val) except Exception: parameters["arguments"][param_name] = "Could not encode" log.warning("Failed to encode %r", param_name, exc_info=True) span.set_tag(test.PARAMETERS, json.dumps(parameters)) markers = [ marker.kwargs for marker in item.iter_markers(name="dd_tags") ] for tags in markers: span.set_tags(tags) _store_span(item, span) yield
def patched_query_request(original_func, instance, args, kwargs): pin = Pin.get_from(instance) if not pin or not pin.enabled(): return original_func(*args, **kwargs) endpoint_name = getattr(instance, "host").split(".")[0] with pin.tracer.trace( "{}.command".format(endpoint_name), service="{}.{}".format(pin.service, endpoint_name), span_type=SpanTypes.HTTP, ) as span: span.set_tag(SPAN_MEASURED_KEY) operation_name = None if args: operation_name = get_argument_value(args, kwargs, 0, "action") span.resource = "%s.%s" % (endpoint_name, operation_name.lower()) else: span.resource = endpoint_name aws.add_span_arg_tags(span, endpoint_name, args, AWS_QUERY_ARGS_NAME, AWS_QUERY_TRACED_ARGS) # Obtaining region name region_name = _get_instance_region_name(instance) meta = { aws.AGENT: "boto", aws.OPERATION: operation_name, } if region_name: meta[aws.REGION] = region_name span.set_tags(meta) # Original func returns a boto.connection.HTTPResponse object result = original_func(*args, **kwargs) span.set_tag(http.STATUS_CODE, getattr(result, "status")) span.set_tag(http.METHOD, getattr(result, "_method")) # set analytics sample rate span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.boto.get_analytics_sample_rate()) return result
async def _wrapped_async_send( wrapped, # type: BoundFunctionWrapper instance, # type: httpx.AsyncClient args, # type: typing.Tuple[httpx.Request], kwargs, # type: typing.Dict[typing.Str, typing.Any] ): # type: (...) -> typing.Coroutine[None, None, httpx.Response] req = get_argument_value(args, kwargs, 0, "request") pin = Pin.get_from(instance) if not pin or not pin.enabled(): return await wrapped(*args, **kwargs) with pin.tracer.trace("http.request", service=_get_service_name(pin, req), span_type=SpanTypes.HTTP) as span: _init_span(span, req) resp = None try: resp = await wrapped(*args, **kwargs) return resp finally: _set_span_meta(span, req, resp)
def traced_execute_pipeline(func, instance, args, kwargs): pin = Pin.get_from(instance) if not pin or not pin.enabled(): return func(*args, **kwargs) cmds = [format_command_args(c.args) for c in instance.command_stack] resource = "\n".join(cmds) tracer = pin.tracer with tracer.trace(redisx.CMD, resource=resource, service=pin.service, span_type=SpanTypes.REDIS) as s: s.set_tag(SPAN_MEASURED_KEY) s.set_tag(redisx.RAWCMD, resource) s.set_metric(redisx.PIPELINE_LEN, len(instance.command_stack)) # set analytics sample rate if enabled s.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.rediscluster.get_analytics_sample_rate()) return func(*args, **kwargs)
def pytest_runtest_protocol(item, nextitem): pin = Pin.get_from(item.config) if pin is None: yield return with pin.tracer.trace( ddtrace.config.pytest.operation_name, service=int_service(pin, ddtrace.config.pytest), resource=item.nodeid, span_type=SpanTypes.TEST.value, ) as span: span.context.dd_origin = ci.CI_APP_TEST_ORIGIN span.set_tags(pin.tags) span.set_tag(SPAN_KIND, KIND) span.set_tag(test.FRAMEWORK, FRAMEWORK) span.set_tag(test.NAME, item.name) span.set_tag(test.SUITE, item.module.__name__) span.set_tag(test.TYPE, SpanTypes.TEST.value) # Parameterized test cases will have a `callspec` attribute attached to the pytest Item object. # Pytest docs: https://docs.pytest.org/en/6.2.x/reference.html#pytest.Function if getattr(item, "callspec", None): parameters = {"arguments": {}, "metadata": {}} # type: Dict[str, Dict[str, str]] for param_name, param_val in item.callspec.params.items(): try: parameters["arguments"][param_name] = repr(param_val) except Exception: parameters["arguments"][param_name] = "Could not encode" log.warning("Failed to encode %r", param_name, exc_info=True) span.set_tag(test.PARAMETERS, json.dumps(parameters)) markers = [marker.kwargs for marker in item.iter_markers(name="dd_tags")] for tags in markers: span.set_tags(tags) _store_span(item, span) yield
def ddspan(request): pin = Pin.get_from(request.config) if pin: return _extract_span(request.node)
def pytest_sessionfinish(session, exitstatus): """Flush open tracer.""" pin = Pin.get_from(session.config) if pin is not None: pin.tracer.shutdown()
def _cursor(self, *args, **kwargs): cursor = yield from self.__wrapped__._cursor(*args, **kwargs) pin = Pin.get_from(self) if not pin: return cursor return self._self_cursor_cls(cursor, pin)
def traced_13_pipeline(func, instance, args, kwargs): pipeline = func(*args, **kwargs) pin = Pin.get_from(instance) if pin: pin.onto(pipeline) return pipeline
def _wrap_urlopen(func, instance, args, kwargs): """ Wrapper function for the lower-level urlopen in urllib3 :param func: The original target function "urlopen" :param instance: The patched instance of ``HTTPConnectionPool`` :param args: Positional arguments from the target function :param kwargs: Keyword arguments from the target function :return: The ``HTTPResponse`` from the target function """ request_method = get_argument_value(args, kwargs, 0, "method") request_url = get_argument_value(args, kwargs, 1, "url") try: request_headers = get_argument_value(args, kwargs, 3, "headers") except ArgumentError: request_headers = None try: request_retries = get_argument_value(args, kwargs, 4, "retries") except ArgumentError: request_retries = None # HTTPConnectionPool allows relative path requests; convert the request_url to an absolute url if request_url.startswith("/"): request_url = parse.urlunparse(( instance.scheme, "{}:{}".format(instance.host, instance.port) if instance.port and instance.port not in DROP_PORTS else str(instance.host), request_url, None, None, None, )) parsed_uri = parse.urlparse(request_url) hostname = parsed_uri.netloc pin = Pin.get_from(instance) if not pin or not pin.enabled(): return func(*args, **kwargs) with pin.tracer.trace("urllib3.request", service=trace_utils.ext_service(pin, config.urllib3), span_type=SpanTypes.HTTP) as span: if config.urllib3.split_by_domain: span.service = hostname # If distributed tracing is enabled, propagate the tracing headers to downstream services if config.urllib3.distributed_tracing: if request_headers is None: request_headers = {} kwargs["headers"] = request_headers HTTPPropagator.inject(span.context, request_headers) if config.urllib3.analytics_enabled: span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.urllib3.get_analytics_sample_rate()) retries = request_retries.total if isinstance( request_retries, urllib3.util.retry.Retry) else None # Call the target function response = None try: response = func(*args, **kwargs) finally: trace_utils.set_http_meta( span, integration_config=config.urllib3, method=request_method, url=request_url, status_code=None if response is None else response.status, query=parsed_uri.query, request_headers=request_headers, response_headers={} if response is None else dict(response.headers), retries_remain=retries, ) return response
def patched_auth_request(original_func, instance, args, kwargs): # Catching the name of the operation that called make_request() operation_name = None # Go up the stack until we get the first non-ddtrace module # DEV: For `lambda.list_functions()` this should be: # - ddtrace.contrib.boto.patch # - ddtrace.vendor.wrapt.wrappers # - boto.awslambda.layer1 (make_request) # - boto.awslambda.layer1 (list_functions) # But can vary depending on Python versions; that's why we use an heuristic frame = inspect.currentframe().f_back operation_name = None while frame: if frame.f_code.co_name == "make_request": operation_name = frame.f_back.f_code.co_name break frame = frame.f_back pin = Pin.get_from(instance) if not pin or not pin.enabled(): return original_func(*args, **kwargs) endpoint_name = getattr(instance, "host").split(".")[0] with pin.tracer.trace( "{}.command".format(endpoint_name), service="{}.{}".format(pin.service, endpoint_name), span_type=SpanTypes.HTTP, ) as span: span.set_tag(SPAN_MEASURED_KEY) if args: http_method = get_argument_value(args, kwargs, 0, "method") span.resource = "%s.%s" % (endpoint_name, http_method.lower()) else: span.resource = endpoint_name aws.add_span_arg_tags(span, endpoint_name, args, AWS_AUTH_ARGS_NAME, AWS_AUTH_TRACED_ARGS) # Obtaining region name region_name = _get_instance_region_name(instance) meta = { aws.AGENT: "boto", aws.OPERATION: operation_name, } if region_name: meta[aws.REGION] = region_name span.set_tags(meta) # Original func returns a boto.connection.HTTPResponse object result = original_func(*args, **kwargs) span.set_tag(http.STATUS_CODE, getattr(result, "status")) span.set_tag(http.METHOD, getattr(result, "_method")) # set analytics sample rate span.set_tag(ANALYTICS_SAMPLE_RATE_KEY, config.boto.get_analytics_sample_rate()) return result