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
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
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
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
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)
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)
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
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)
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
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
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)
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
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
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
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
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
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)
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
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
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