Esempio n. 1
0
    def begin_transaction(self, transaction_type, trace_parent=None, start=None):
        """
        Start a new transactions and bind it in a thread-local variable

        :param transaction_type: type of the transaction, e.g. "request"
        :param trace_parent: an optional TraceParent object
        :param start: override the start timestamp, mostly useful for testing

        :returns the Transaction object
        """
        if trace_parent:
            is_sampled = bool(trace_parent.trace_options.recorded)
        else:
            is_sampled = (
                self.config.transaction_sample_rate == 1.0 or self.config.transaction_sample_rate > random.random()
            )
        transaction = Transaction(self, transaction_type, trace_parent=trace_parent, is_sampled=is_sampled, start=start)
        if trace_parent is None:
            transaction.trace_parent = TraceParent(
                constants.TRACE_CONTEXT_VERSION,
                "%032x" % random.getrandbits(128),
                transaction.id,
                TracingOptions(recorded=is_sampled),
            )
        execution_context.set_transaction(transaction)
        return transaction
Esempio n. 2
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        args, kwargs, params = self._ensure_headers_in_kwargs(args, kwargs)

        signature = params.get("method", "GET").upper()
        signature += " " + get_host_from_url(params["url"])
        url = sanitize_url(params["url"])
        transaction = execution_context.get_transaction()
        with capture_span(
            signature,
            span_type="external",
            span_subtype="http",
            extra={"http": {"url": url}},
            leaf=True,
        ) as span:
            # if httplib2 has been called in a leaf span, this span might be a DroppedSpan.
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            # It's possible that there are only dropped spans, e.g. if we started dropping spans.
            # In this case, the transaction.id is used
            parent_id = leaf_span.id if leaf_span else transaction.id
            trace_parent = transaction.trace_parent.copy_from(
                span_id=parent_id, trace_options=TracingOptions(recorded=True)
            )
            self._set_disttracing_headers(params["headers"], trace_parent, transaction)
            if leaf_span:
                leaf_span.dist_tracing_propagated = True

            response, content = wrapped(*args, **kwargs)
            if span.context:
                span.context["http"]["status_code"] = response.status
            span.set_success() if response.status < 400 else span.set_failure()
            return response, content
Esempio n. 3
0
    def begin_transaction(self, transaction_type, trace_parent=None):
        """
        Start a new transactions and bind it in a thread-local variable

        :returns the Transaction object
        """
        if trace_parent:
            is_sampled = bool(trace_parent.trace_options.recorded)
        else:
            is_sampled = (
                self.config.transaction_sample_rate == 1.0
                or self.config.transaction_sample_rate > random.random())
        transaction = Transaction(self,
                                  transaction_type,
                                  trace_parent=trace_parent,
                                  is_sampled=is_sampled)
        if trace_parent is None:
            transaction.trace_parent = TraceParent(
                constants.TRACE_CONTEXT_VERSION,
                "%032x" % random.getrandbits(128),
                transaction.id,
                TracingOptions(recorded=is_sampled),
            )
        execution_context.set_transaction(transaction)
        return transaction
Esempio n. 4
0
    async def call(self, module, method, wrapped, instance, args, kwargs):
        method = kwargs["method"] if "method" in kwargs else args[0]
        url = kwargs["url"] if "url" in kwargs else args[1]
        url = str(url)

        signature = " ".join([method.upper(), get_host_from_url(url)])
        url = sanitize_url(url)
        transaction = execution_context.get_transaction()

        async with async_capture_span(
                signature,
                span_type="external",
                span_subtype="http",
                extra={"http": {
                    "url": url
                }},
                leaf=True,
        ) as span:
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            parent_id = leaf_span.id if leaf_span else transaction.id
            trace_parent = transaction.trace_parent.copy_from(
                span_id=parent_id, trace_options=TracingOptions(recorded=True))
            headers = kwargs.get("headers") or {}
            self._set_disttracing_headers(headers, trace_parent, transaction)
            kwargs["headers"] = headers
            response = await wrapped(*args, **kwargs)
            if response:
                if span.context:
                    span.context["http"]["status_code"] = response.status
                span.set_success(
                ) if response.status < 400 else span.set_failure()
            return response
Esempio n. 5
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        request_object = args[1] if len(args) > 1 else kwargs["req"]

        method = request_object.get_method()
        host = request_host(request_object)

        url = sanitize_url(request_object.get_full_url())
        signature = method.upper() + " " + host

        transaction = execution_context.get_transaction()

        with capture_span(
            signature, span_type="external", span_subtype="http", extra={"http": {"url": url}}, leaf=True
        ) as span:
            # if urllib has been called in a leaf span, this span might be a DroppedSpan.
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            parent_id = leaf_span.id if leaf_span else transaction.id
            trace_parent = transaction.trace_parent.copy_from(
                span_id=parent_id, trace_options=TracingOptions(recorded=True)
            )
            self._set_disttracing_headers(request_object, trace_parent, transaction)
            return wrapped(*args, **kwargs)
Esempio n. 6
0
    def __init__(
        self,
        tracer: "Tracer",
        transaction_type: str = "custom",
        trace_parent: Optional[TraceParent] = None,
        is_sampled: bool = True,
        start: Optional[float] = None,
        sample_rate: Optional[float] = None,
    ):
        """
        tracer
            Tracer object
        transaction_type
            Transaction type
        trace_parent
            TraceParent object representing the parent trace and trace state
        is_sampled
            Whether or not this transaction is sampled
        start
            Optional start timestamp. This is expected to be an epoch timestamp
            in seconds (such as from `time.time()`). If it is not, it's recommended
            that a `duration` is passed into the `end()` method.
        sample_rate
            Sample rate which was used to decide whether to sample this transaction.
            This is reported to the APM server so that unsampled transactions can
            be extrapolated.
        """
        self.id = self.get_dist_tracing_id()
        if not trace_parent:
            trace_parent = TraceParent(
                constants.TRACE_CONTEXT_VERSION,
                "%032x" % random.getrandbits(128),
                self.id,
                TracingOptions(recorded=is_sampled),
            )

        self.trace_parent: TraceParent = trace_parent
        self.timestamp = start if start is not None else time.time()
        self.name: Optional[str] = None
        self.result: Optional[str] = None
        self.transaction_type = transaction_type
        self._tracer = tracer

        self.dropped_spans: int = 0
        self.context: Dict[str, Any] = {}

        self._is_sampled = is_sampled
        self.sample_rate = sample_rate
        self._span_counter: int = 0
        self._span_timers: Dict[Tuple[str, str], Timer] = defaultdict(Timer)
        self._span_timers_lock = threading.Lock()
        self._dropped_span_statistics = defaultdict(lambda: {"count": 0, "duration.sum.us": 0})
        try:
            self._breakdown = self.tracer._agent._metrics.get_metricset(
                "elasticapm.metrics.sets.breakdown.BreakdownMetricSet"
            )
        except (LookupError, AttributeError):
            self._breakdown = None
        super(Transaction, self).__init__(start=start)
Esempio n. 7
0
    def ListRecommendations(self, request, context):
        # manually populate service map
        # this can be removed once 7.8 is out and the python agent adds this by itself
        product_catalog_destination_info = {
            "address": os.environ.get('PRODUCT_CATALOG_SERVICE_ADDR', ''),
            "port": int(os.environ.get('PORT', 8080)),
            "service": {
                "name": "grpc",
                "resource": os.environ.get('PRODUCT_CATALOG_SERVICE_ADDR', ''),
                "type": "external"
            },
        }

        trace_parent = self.extract_trace_parent(context)
        transaction = client.begin_transaction('request',
                                               trace_parent=trace_parent)
        request_dict = MessageToDict(request)
        elasticapm.label(**{'request': request_dict})
        max_responses = 5
        # fetch list of products from product catalog stub
        list_product_req = demo_pb2.Empty()
        with elasticapm.capture_span(
                '/hipstershop.ProductCatalogService/ListProducts',
                labels=MessageToDict(list_product_req),
                extra={"destination":
                       product_catalog_destination_info}) as span:
            trace_parent = transaction.trace_parent.copy_from(
                span_id=span.id, trace_options=TracingOptions(recorded=True))
            cat_response, call = product_catalog_stub.ListProducts.with_call(
                list_product_req,
                metadata=[(constants.TRACEPARENT_HEADER_NAME,
                           trace_parent.to_string())])
        with elasticapm.capture_span('CalculateRecommendations',
                                     span_subtype='grpc',
                                     span_action='calculate') as span:
            product_ids = [x.id for x in cat_response.products]
            filtered_products = list(
                set(product_ids) - set(request.product_ids))
            num_products = len(filtered_products)
            num_return = min(max_responses, num_products)
            # sample list of indicies to return
            indices = random.sample(range(num_products), num_return)
            # fetch product ids from indices
            prod_list = [filtered_products[i] for i in indices]
            logger.info(
                '[Recv ListRecommendations] product_ids={}'.format(prod_list),
                extra=get_extra_logging_payload())
            # build and return response
        response = demo_pb2.ListRecommendationsResponse()
        response.product_ids.extend(prod_list)
        elasticapm.label(**{'response': MessageToDict(response)})
        elasticapm.set_custom_context({
            'request': request_dict,
            'response': MessageToDict(response)
        })
        client.end_transaction(
            '/hipstershop.RecommendationService/ListRecommendations',
            'success')
        return response
Esempio n. 8
0
 def mutate_unsampled_call_args(self, module, method, wrapped, instance,
                                args, kwargs, transaction):
     # since we don't have a span, we set the span id to the transaction id
     trace_parent = transaction.trace_parent.copy_from(
         span_id=transaction.id,
         trace_options=TracingOptions(recorded=False))
     return update_headers(args, kwargs, instance, transaction,
                           trace_parent)
Esempio n. 9
0
 def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):
     # since we don't have a span, we set the span id to the transaction id
     trace_parent = transaction.trace_parent.copy_from(
         span_id=transaction.id, trace_options=TracingOptions(recorded=False)
     )
     args, kwargs, params = self._ensure_headers_in_kwargs(args, kwargs)
     self._set_disttracing_headers(params["headers"], trace_parent, transaction)
     return args, kwargs
Esempio n. 10
0
 def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):
     # since we don't have a span, we set the span id to the transaction id
     trace_parent = transaction.trace_parent.copy_from(
         span_id=transaction.id, trace_options=TracingOptions(recorded=False)
     )
     headers = utils.get_request_data(args, kwargs)[2]
     utils.set_disttracing_headers(headers, trace_parent, transaction)
     return args, kwargs
Esempio n. 11
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        if "method" in kwargs:
            method = kwargs["method"]
        else:
            method = args[0]

        headers = None
        if "headers" in kwargs:
            headers = kwargs["headers"]
            if headers is None:
                headers = {}
                kwargs["headers"] = headers

        host = instance.host

        if instance.port != default_ports.get(instance.scheme):
            host += ":" + str(instance.port)

        if "url" in kwargs:
            url = kwargs["url"]
        else:
            url = args[1]

        signature = method.upper() + " " + host

        url = "%s://%s%s" % (instance.scheme, host, url)
        destination = url_to_destination(url)

        transaction = execution_context.get_transaction()

        with capture_span(
                signature,
                span_type="external",
                span_subtype="http",
                extra={
                    "http": {
                        "url": url
                    },
                    "destination": destination
                },
                leaf=True,
        ) as span:
            # if urllib3 has been called in a leaf span, this span might be a DroppedSpan.
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            if headers is not None:
                # It's possible that there are only dropped spans, e.g. if we started dropping spans.
                # In this case, the transaction.id is used
                parent_id = leaf_span.id if leaf_span else transaction.id
                trace_parent = transaction.trace_parent.copy_from(
                    span_id=parent_id,
                    trace_options=TracingOptions(recorded=True))
                self._set_disttracing_headers(headers, trace_parent,
                                              transaction)
            return wrapped(*args, **kwargs)
Esempio n. 12
0
    def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):
        request_object = args[1] if len(args) > 1 else kwargs["req"]
        # since we don't have a span, we set the span id to the transaction id
        trace_parent = transaction.trace_parent.copy_from(
            span_id=transaction.id, trace_options=TracingOptions(recorded=False)
        )

        request_object.add_header(constants.TRACEPARENT_HEADER_NAME, trace_parent.to_string())
        return args, kwargs
Esempio n. 13
0
    def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):
        request_object = args[1] if len(args) > 1 else kwargs["req"]
        # since we don't have a span, we set the span id to the transaction id
        trace_parent = transaction.trace_parent.copy_from(
            span_id=transaction.id, trace_options=TracingOptions(recorded=False)
        )

        self._set_disttracing_headers(request_object, trace_parent, transaction)
        return args, kwargs
Esempio n. 14
0
    def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):
        # since we don't have a span, we set the span id to the transaction id
        trace_parent = transaction.trace_parent.copy_from(
            span_id=transaction.id, trace_options=TracingOptions(recorded=False)
        )

        headers = kwargs.get("headers") or {}
        self._set_disttracing_headers(headers, trace_parent, transaction)
        kwargs["headers"] = headers
        return args, kwargs
Esempio n. 15
0
 def mutate_unsampled_call_args(self, module, method, wrapped, instance, args, kwargs, transaction):
     # since we don't have a span, we set the span id to the transaction id
     trace_parent = transaction.trace_parent.copy_from(
         span_id=transaction.id, trace_options=TracingOptions(recorded=False)
     )
     if "headers" in kwargs:
         headers = kwargs["headers"]
         if headers is None:
             headers = {}
             kwargs["headers"] = headers
         headers[constants.TRACEPARENT_HEADER_NAME] = trace_parent.to_ascii()
     return args, kwargs
Esempio n. 16
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        if "method" in kwargs:
            method = kwargs["method"]
        else:
            method = args[0]

        host = instance.host

        if instance.port != default_ports.get(instance.scheme):
            host += ":" + str(instance.port)

        if "url" in kwargs:
            url = kwargs["url"]
        else:
            url = args[1]

        signature = method.upper() + " " + host

        url = "%s://%s%s" % (instance.scheme, host, url)
        destination = url_to_destination(url)

        transaction = execution_context.get_transaction()

        with capture_span(
                signature,
                span_type="external",
                span_subtype="http",
                extra={
                    "http": {
                        "url": url
                    },
                    "destination": destination
                },
                leaf=True,
        ) as span:
            # if urllib3 has been called in a leaf span, this span might be a DroppedSpan.
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            parent_id = leaf_span.id if leaf_span else transaction.id
            trace_parent = transaction.trace_parent.copy_from(
                span_id=parent_id, trace_options=TracingOptions(recorded=True))
            args, kwargs = update_headers(args, kwargs, instance, transaction,
                                          trace_parent)
            response = wrapped(*args, **kwargs)
            if response:
                if span.context:
                    span.context["http"]["status_code"] = response.status
                span.set_success(
                ) if response.status < 400 else span.set_failure()
            return response
Esempio n. 17
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        if "method" in kwargs:
            method = kwargs["method"]
        else:
            method = args[0]

        headers = None
        if "headers" in kwargs:
            headers = kwargs["headers"]
            if headers is None:
                headers = {}
                kwargs["headers"] = headers

        host = instance.host

        if instance.port != default_ports.get(instance.scheme):
            host += ":" + str(instance.port)

        if "url" in kwargs:
            url = kwargs["url"]
        else:
            url = args[1]

        signature = method.upper() + " " + host

        # TODO: reconstruct URL more faithfully, e.g. include port
        url = instance.scheme + "://" + host + url
        transaction = execution_context.get_transaction()

        with capture_span(signature,
                          span_type="external",
                          span_subtype="http",
                          extra={"http": {
                              "url": url
                          }},
                          leaf=True) as span:
            # if urllib3 has been called in a leaf span, this span might be a DroppedSpan.
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            if headers is not None:
                # It's possible that there are only dropped spans, e.g. if we started dropping spans.
                # In this case, the transaction.id is used
                parent_id = leaf_span.id if leaf_span else transaction.id
                trace_parent = transaction.trace_parent.copy_from(
                    span_id=parent_id,
                    trace_options=TracingOptions(recorded=True))
                headers[constants.
                        TRACEPARENT_HEADER_NAME] = trace_parent.to_string()
            return wrapped(*args, **kwargs)
Esempio n. 18
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        url, method, headers = utils.get_request_data(args, kwargs)
        scheme, host, port, target = url
        if port != default_ports.get(scheme):
            host += ":" + str(port)

        signature = "%s %s" % (method.upper(), host)

        url = "%s://%s%s" % (scheme, host, target)

        transaction = execution_context.get_transaction()

        with capture_span(
                signature,
                span_type="external",
                span_subtype="http",
                extra={"http": {
                    "url": url
                }},
                leaf=True,
        ) as span:
            # if httpcore has been called in a leaf span, this span might be a DroppedSpan.
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            if headers is not None:
                # It's possible that there are only dropped spans, e.g. if we started dropping spans.
                # In this case, the transaction.id is used
                parent_id = leaf_span.id if leaf_span else transaction.id
                trace_parent = transaction.trace_parent.copy_from(
                    span_id=parent_id,
                    trace_options=TracingOptions(recorded=True))
                utils.set_disttracing_headers(headers, trace_parent,
                                              transaction)
                if leaf_span:
                    leaf_span.dist_tracing_propagated = True
            response = wrapped(*args, **kwargs)
            status_code = utils.get_status(response)
            if status_code:
                if span.context:
                    span.context["http"]["status_code"] = status_code
                span.set_success() if status_code < 400 else span.set_failure()
            return response
Esempio n. 19
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        request_object = args[1] if len(args) > 1 else kwargs["req"]

        method = request_object.get_method()
        host = request_host(request_object)

        url = sanitize_url(request_object.get_full_url())
        destination = url_to_destination(url)
        signature = method.upper() + " " + host

        transaction = execution_context.get_transaction()

        with capture_span(
                signature,
                span_type="external",
                span_subtype="http",
                extra={
                    "http": {
                        "url": url
                    },
                    "destination": destination
                },
                leaf=True,
        ) as span:
            # if urllib has been called in a leaf span, this span might be a DroppedSpan.
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            parent_id = leaf_span.id if leaf_span else transaction.id
            trace_parent = transaction.trace_parent.copy_from(
                span_id=parent_id, trace_options=TracingOptions(recorded=True))
            self._set_disttracing_headers(request_object, trace_parent,
                                          transaction)
            response = wrapped(*args, **kwargs)
            if response:
                status = getattr(response, "status",
                                 None) or response.getcode()  # Python 2 compat
                if span.context:
                    span.context["http"]["status_code"] = status
                span.set_success() if status < 400 else span.set_failure()
            return response
Esempio n. 20
0
    def call(self, module, method, wrapped, instance, args, kwargs):
        if "method" in kwargs:
            method = kwargs["method"].decode("utf-8")
        else:
            method = args[0].decode("utf-8")

        # URL is a tuple of (scheme, host, port, path), we want path
        if "url" in kwargs:
            url = kwargs["url"][3].decode("utf-8")
        else:
            url = args[1][3].decode("utf-8")

        headers = None
        if "headers" in kwargs:
            headers = kwargs["headers"]
            if headers is None:
                headers = []
                kwargs["headers"] = headers

        scheme, host, port = instance.origin
        scheme = scheme.decode("utf-8")
        host = host.decode("utf-8")

        if port != default_ports.get(scheme):
            host += ":" + str(port)

        signature = "%s %s" % (method.upper(), host)

        url = "%s://%s%s" % (scheme, host, url)
        destination = url_to_destination(url)

        transaction = execution_context.get_transaction()

        with capture_span(
                signature,
                span_type="external",
                span_subtype="http",
                extra={
                    "http": {
                        "url": url
                    },
                    "destination": destination
                },
                leaf=True,
        ) as span:
            # if httpcore has been called in a leaf span, this span might be a DroppedSpan.
            leaf_span = span
            while isinstance(leaf_span, DroppedSpan):
                leaf_span = leaf_span.parent

            if headers is not None:
                # It's possible that there are only dropped spans, e.g. if we started dropping spans.
                # In this case, the transaction.id is used
                parent_id = leaf_span.id if leaf_span else transaction.id
                trace_parent = transaction.trace_parent.copy_from(
                    span_id=parent_id,
                    trace_options=TracingOptions(recorded=True))
                self._set_disttracing_headers(headers, trace_parent,
                                              transaction)
            response = wrapped(*args, **kwargs)
            if len(response) > 4:
                # httpcore < 0.11.0
                # response = (http_version, status_code, reason_phrase, headers, stream)
                status_code = response[1]
            else:
                # httpcore >= 0.11.0
                # response = (status_code, headers, stream, ext)
                status_code = response[0]
            if status_code:
                if span.context:
                    span.context["http"]["status_code"] = status_code
                span.set_success() if status_code < 400 else span.set_failure()
            return response