def request_finished(self, app, response): if not self.app.debug or self.client.config.debug: rule = request.url_rule.rule if request.url_rule is not None else "" rule = build_name_with_http_method_prefix(rule, request) elasticapm.set_context( lambda: get_data_from_request( request, capture_body=self.client.config.capture_body in ("transactions", "all") ), "request", ) elasticapm.set_context(lambda: get_data_from_response(response), "response") if response.status_code: result = "HTTP {}xx".format(response.status_code // 100) else: result = response.status elasticapm.set_transaction_name(rule, override=False) elasticapm.set_transaction_result(result, override=False) # Instead of calling end_transaction here, we defer the call until the response is closed. # This ensures that we capture things that happen until the WSGI server closes the response. response.call_on_close(self.client.end_transaction)
def __call__(self, request): self.client.begin_transaction('request') try: response = self.handler(request) transaction_result = response.status[0] + 'xx' elasticapm.set_context( lambda: self.get_data_from_response(response), 'response') return response except Exception: transaction_result = '5xx' self.client.capture_exception( context={'request': self.get_data_from_request(request)}, handled=False, ) reraise(*sys.exc_info()) finally: transaction_name = self.get_transaction_name(request) elasticapm.set_context(lambda: self.get_data_from_request(request), 'request') elasticapm.set_user_context(user_id=request.authenticated_userid, ) self.client.end_transaction(transaction_name, transaction_result)
def process_response(self, request: HttpRequest, response: HttpResponse): if django_settings.DEBUG and not self.client.config.debug: return response try: if hasattr(response, "status_code"): if not getattr(request, "_elasticapm_name_set", False): elasticapm.set_transaction_name( self.get_transaction_name(request), override=False) elasticapm.set_context( lambda: self.client.get_data_from_request( request, constants.TRANSACTION), "request") elasticapm.set_context( lambda: self.client.get_data_from_response( response, constants.TRANSACTION), "response") elasticapm.set_context( lambda: self.client.get_user_info(request), "user") elasticapm.set_transaction_result("HTTP {}xx".format( response.status_code // 100), override=False) elasticapm.set_transaction_outcome( http_status_code=response.status_code, override=False) except Exception: self.client.error_logger.error( "Exception during timing of request", exc_info=True) return response
def process_response(self, request, response): if django_settings.DEBUG and not self.client.config.debug: return response try: if hasattr(response, "status_code"): transaction_name = None if self.client.config.django_transaction_name_from_route and hasattr(request.resolver_match, "route"): transaction_name = request.resolver_match.route elif getattr(request, "_elasticapm_view_func", False): transaction_name = get_name_from_func(request._elasticapm_view_func) if transaction_name: transaction_name = build_name_with_http_method_prefix(transaction_name, request) elasticapm.set_transaction_name(transaction_name, override=False) elasticapm.set_context( lambda: self.client.get_data_from_request(request, constants.TRANSACTION), "request" ) elasticapm.set_context( lambda: self.client.get_data_from_response(response, constants.TRANSACTION), "response" ) elasticapm.set_context(lambda: self.client.get_user_info(request), "user") elasticapm.set_transaction_result("HTTP {}xx".format(response.status_code // 100), override=False) elasticapm.set_transaction_outcome(http_status_code=response.status_code, override=False) except Exception: self.client.error_logger.error("Exception during timing of request", exc_info=True) return response
def process_response(self, request, response): if django_settings.DEBUG and not self.client.config.debug: return response try: if hasattr(response, "status_code"): if getattr(request, "_elasticapm_view_func", False): transaction_name = get_name_from_func( request._elasticapm_view_func) transaction_name = build_name_with_http_method_prefix( transaction_name, request) elasticapm.set_transaction_name(transaction_name, override=False) elasticapm.set_context( lambda: self.client.get_data_from_request( request, capture_body=self.client.config.capture_body in ("all", "transactions")), "request", ) elasticapm.set_context( lambda: self.client.get_data_from_response(response), "response") elasticapm.set_context( lambda: self.client.get_user_info(request), "user") elasticapm.set_transaction_result("HTTP {}xx".format( response.status_code // 100), override=False) except Exception: self.client.error_logger.error( "Exception during timing of request", exc_info=True) return response
def process_response(self, request, response): if django_settings.DEBUG and not self.client.config.debug: return response try: if hasattr(response, 'status_code'): if getattr(request, '_elasticapm_view_func', False): transaction_name = get_name_from_func( request._elasticapm_view_func ) transaction_name = build_name_with_http_method_prefix( transaction_name, request ) elasticapm.set_transaction_name(transaction_name, override=False) elasticapm.set_context(lambda: self.client.get_data_from_request( request, capture_body=self.client.config.capture_body in ('all', 'transactions') ), 'request') elasticapm.set_context(lambda: self.client.get_data_from_response(response), 'response') elasticapm.set_context(lambda: self.client.get_user_info(request), 'user') elasticapm.set_transaction_result('HTTP {}xx'.format(response.status_code // 100), override=False) except Exception: self.client.error_logger.error( 'Exception during timing of request', exc_info=True, ) return response
async def call(self, module, method, wrapped, instance, args, kwargs): if not hasattr(instance.application, "elasticapm_client"): # If tornado was instrumented but not as the main framework # (i.e. in Flower), we should skip it. return await wrapped(*args, **kwargs) # Late import to avoid ImportErrors from elasticapm.contrib.tornado.utils import get_data_from_request, get_data_from_response request = instance.request client = instance.application.elasticapm_client should_ignore = client.should_ignore_url(request.path) if not should_ignore: trace_parent = TraceParent.from_headers(request.headers) client.begin_transaction("request", trace_parent=trace_parent) elasticapm.set_context( lambda: get_data_from_request(instance, request, client.config, constants.TRANSACTION), "request") # TODO: Can we somehow incorporate the routing rule itself here? elasticapm.set_transaction_name("{} {}".format( request.method, type(instance).__name__), override=False) ret = await wrapped(*args, **kwargs) if not should_ignore: elasticapm.set_context( lambda: get_data_from_response(instance, client.config, constants.TRANSACTION), "response") status = instance.get_status() result = "HTTP {}xx".format(status // 100) elasticapm.set_transaction_result(result, override=False) elasticapm.set_transaction_outcome(http_status_code=status) client.end_transaction() return ret
async def handle_request(request, handler): elasticapm_client = app.get(CLIENT_KEY) if elasticapm_client: request[CLIENT_KEY] = elasticapm_client trace_parent = AioHttpTraceParent.from_headers(request.headers) elasticapm_client.begin_transaction("request", trace_parent=trace_parent) resource = request.match_info.route.resource name = request.method if resource: # canonical has been added in 3.3, and returns one of path, formatter, prefix for attr in ("canonical", "_path", "_formatter", "_prefix"): if hasattr(resource, attr): name += " " + getattr(resource, attr) break else: name += " unknown route" else: name += " unknown route" elasticapm.set_transaction_name(name, override=False) elasticapm.set_context( lambda: get_data_from_request(request, elasticapm_client.config, constants.TRANSACTION), "request" ) try: response = await handler(request) elasticapm.set_transaction_result("HTTP {}xx".format(response.status // 100), override=False) elasticapm.set_transaction_outcome(http_status_code=response.status, override=False) elasticapm.set_context( lambda: get_data_from_response(response, elasticapm_client.config, constants.TRANSACTION), "response" ) return response except Exception as exc: if elasticapm_client: elasticapm_client.capture_exception( context={"request": get_data_from_request(request, elasticapm_client.config, constants.ERROR)} ) elasticapm.set_transaction_result("HTTP 5xx", override=False) elasticapm.set_transaction_outcome(http_status_code=500, override=False) elasticapm.set_context({"status_code": 500}, "response") # some exceptions are response-like, e.g. have headers and status code. Let's try and capture them if isinstance(exc, (Response, HTTPException)): elasticapm.set_context( lambda: get_data_from_response(exc, elasticapm_client.config, constants.ERROR), # noqa: F821 "response", ) raise finally: elasticapm_client.end_transaction()
async def handle_request(request, handler): elasticapm_client = app.get(CLIENT_KEY) if elasticapm_client: request[CLIENT_KEY] = elasticapm_client trace_parent = AioHttpTraceParent.from_headers(request.headers) elasticapm_client.begin_transaction("request", trace_parent=trace_parent) resource = request.match_info.route.resource name = request.method if resource: # canonical has been added in 3.3, and returns one of path, formatter, prefix for attr in ("canonical", "_path", "_formatter", "_prefix"): if hasattr(resource, attr): name += " " + getattr(resource, attr) break else: name += " unknown route" else: name += " unknown route" elasticapm.set_transaction_name(name, override=False) elasticapm.set_context( lambda: get_data_from_request( request, capture_body=elasticapm_client.config.capture_body in ("transactions", "all"), capture_headers=elasticapm_client.config.capture_headers, ), "request", ) try: response = await handler(request) elasticapm.set_transaction_result("HTTP {}xx".format( response.status // 100), override=False) elasticapm.set_context( lambda: get_data_from_response( response, capture_headers=elasticapm_client.config.capture_headers), "response", ) return response except Exception: if elasticapm_client: elasticapm_client.capture_exception( context={ "request": get_data_from_request(request, capture_body=elasticapm_client. config.capture_body in ( "all", "errors")) }) elasticapm.set_transaction_result("HTTP 5xx", override=False) elasticapm.set_context({"status_code": 500}, "response") raise finally: elasticapm_client.end_transaction()
def __call__(self, request): self.client.begin_transaction('request') try: response = self.handler(request) transaction_result = response.status[0] + "xx" elasticapm.set_context(lambda: get_data_from_response(response), "response") return response except Exception: transaction_result = '5xx' self.client.capture_exception( context={"request": get_data_from_request(request)}, handled= False, # indicate that this exception bubbled all the way up to the user ) reraise(*sys.exc_info()) finally: transaction_name = request.matched_route.pattern if request.matched_route else request.view_name # prepend request method transaction_name = " ".join( (request.method, transaction_name)) if transaction_name else "" elasticapm.set_context(lambda: get_data_from_request(request), "request") self.client.end_transaction(transaction_name, transaction_result)
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: """Processes the whole request APM capturing. Args: request (Request) call_next (RequestResponseEndpoint): Next request process in Starlette. Returns: Response """ await self._request_started(request) try: response = await call_next(request) elasticapm.set_transaction_outcome(constants.OUTCOME.SUCCESS, override=False) except Exception: await self.capture_exception( context={ "request": await get_data_from_request(request, self.client.config, constants.ERROR) }) elasticapm.set_transaction_result("HTTP 5xx", override=False) elasticapm.set_transaction_outcome(constants.OUTCOME.FAILURE, override=False) elasticapm.set_context({"status_code": 500}, "response") raise else: await self._request_finished(response) finally: self.client.end_transaction() return response
def process_response(self, request, response): if django_settings.DEBUG and not self.client.config.debug: return response if request.META.get('HTTP_USER_AGENT') == 'mcod-heartbeat': return response try: if hasattr(response, "status_code"): try: route = request.resolver_match.route except AttributeError: route = request.path transaction_name = route_to_name(route, prefix='admin', method=request.method) if transaction_name: elasticapm.set_transaction_name(transaction_name, override=False) elasticapm.set_context( lambda: self.client.get_data_from_request( request, capture_body=self.client.config.capture_body in ("all", "transactions")), "request", ) elasticapm.set_context( lambda: self.client.get_data_from_response(response), "response") elasticapm.set_context( lambda: self.client.get_user_info(request), "user") elasticapm.set_transaction_result("HTTP {}xx".format( response.status_code // 100), override=False) except Exception: self.client.error_logger.error( "Exception during timing of request", exc_info=True) return response
def process_response(self, request, response): try: if hasattr(response, 'status_code'): # check if _elasticapm_transaction_name is set if hasattr(request, '_elasticapm_transaction_name'): transaction_name = request._elasticapm_transaction_name elif getattr(request, '_elasticapm_view_func', False): transaction_name = get_name_from_func( request._elasticapm_view_func) else: transaction_name = '' status_code = response.status_code transaction_name = build_name_with_http_method_prefix( transaction_name, request) request_data = lambda: self.client.get_data_from_request( request, capture_body=self.client.config.capture_body in ('all', 'transactions')) response_data = lambda: self.client.get_data_from_response( response) elasticapm.set_context(request_data, 'request') elasticapm.set_context(response_data, 'response') user_data = lambda: self.client.get_user_info(request) if user_data: elasticapm.set_context(user_data, 'user') self.client.end_transaction( transaction_name, 'HTTP {}xx'.format(status_code // 100)) except Exception: self.client.error_logger.error( 'Exception during timing of request', exc_info=True, ) return response
def set_metadata_and_context(self, coldstart: bool) -> None: """ Process the metadata and context fields for this request """ metadata = {} cloud_context = {"origin": {"provider": "aws"}} service_context = {} message_context = {} faas = {} faas["coldstart"] = coldstart faas["trigger"] = {"type": "other"} faas["execution"] = self.context.aws_request_id if self.source == "api": faas["trigger"]["type"] = "http" faas["trigger"]["request_id"] = self.event["requestContext"][ "requestId"] path = (self.event["requestContext"].get("resourcePath") or self.event["requestContext"]["http"]["path"].split( self.event["requestContext"]["stage"])[-1]) service_context["origin"] = { "name": "{} {}/{}".format( self.httpmethod, self.event["requestContext"]["stage"], path, ) } service_context["origin"]["id"] = self.event["requestContext"][ "apiId"] service_context["origin"]["version"] = self.event.get( "version", "1.0") cloud_context["origin"] = {} cloud_context["origin"]["service"] = {"name": "api gateway"} cloud_context["origin"]["account"] = { "id": self.event["requestContext"]["accountId"] } cloud_context["origin"]["provider"] = "aws" elif self.source == "sqs": record = self.event["Records"][0] faas["trigger"]["type"] = "pubsub" faas["trigger"]["request_id"] = record["messageId"] service_context["origin"] = {} service_context["origin"]["name"] = record["eventSourceARN"].split( ":")[5] service_context["origin"]["id"] = record["eventSourceARN"] cloud_context["origin"] = {} cloud_context["origin"]["service"] = {"name": "sqs"} cloud_context["origin"]["region"] = record["awsRegion"] cloud_context["origin"]["account"] = { "id": record["eventSourceARN"].split(":")[4] } cloud_context["origin"]["provider"] = "aws" message_context["queue"] = service_context["origin"]["name"] if "SentTimestamp" in record["attributes"]: message_context["age"] = { "ms": int((time.time() * 1000) - int(record["attributes"]["SentTimestamp"])) } if self.client.config.capture_body in ("transactions", "all") and "body" in record: message_context["body"] = record["body"] if self.client.config.capture_headers and record.get( "messageAttributes"): message_context["headers"] = record["messageAttributes"] elif self.source == "sns": record = self.event["Records"][0] faas["trigger"]["type"] = "pubsub" faas["trigger"]["request_id"] = record["Sns"]["TopicArn"] service_context["origin"] = {} service_context["origin"]["name"] = record["Sns"][ "TopicArn"].split(":")[5] service_context["origin"]["id"] = record["Sns"]["TopicArn"] service_context["origin"]["version"] = record["EventVersion"] service_context["origin"]["service"] = {"name": "sns"} cloud_context["origin"] = {} cloud_context["origin"]["region"] = record["Sns"][ "TopicArn"].split(":")[3] cloud_context["origin"]["account_id"] = record["Sns"][ "TopicArn"].split(":")[4] cloud_context["origin"]["provider"] = "aws" message_context["queue"] = service_context["origin"]["name"] if "Timestamp" in record["Sns"]: message_context["age"] = { "ms": int((datetime.datetime.now() - datetime.datetime.strptime( record["Sns"]["Timestamp"], r"%Y-%m-%dT%H:%M:%S.%fZ")).total_seconds() * 1000) } if self.client.config.capture_body in ( "transactions", "all") and "Message" in record["Sns"]: message_context["body"] = record["Sns"]["Message"] if self.client.config.capture_headers and record["Sns"].get( "MessageAttributes"): message_context["headers"] = record["Sns"]["MessageAttributes"] elif self.source == "s3": record = self.event["Records"][0] faas["trigger"]["type"] = "datasource" faas["trigger"]["request_id"] = record["responseElements"][ "x-amz-request-id"] service_context["origin"] = {} service_context["origin"]["name"] = record["s3"]["bucket"]["name"] service_context["origin"]["id"] = record["s3"]["bucket"]["arn"] service_context["origin"]["version"] = record["eventVersion"] cloud_context["origin"] = {} cloud_context["origin"]["service"] = {"name": "s3"} cloud_context["origin"]["region"] = record["awsRegion"] cloud_context["origin"]["provider"] = "aws" metadata["service"] = {} metadata["service"]["name"] = os.environ.get( "AWS_LAMBDA_FUNCTION_NAME") metadata["service"]["framework"] = {"name": "AWS Lambda"} metadata["service"]["runtime"] = { "name": os.environ.get("AWS_EXECUTION_ENV"), "version": platform.python_version(), } arn = self.context.invoked_function_arn if len(arn.split(":")) > 7: arn = ":".join(arn.split(":")[:7]) metadata["service"]["id"] = arn metadata["service"]["version"] = os.environ.get( "AWS_LAMBDA_FUNCTION_VERSION") metadata["service"]["node"] = { "configured_name": os.environ.get("AWS_LAMBDA_LOG_STREAM_NAME") } # This is the one piece of metadata that requires deep merging. We add it manually # here to avoid having to deep merge in _transport.add_metadata() if self.client._transport._metadata: node_name = nested_key(self.client._transport._metadata, "service", "node", "name") if node_name: metadata["service"]["node"]["name"] = node_name metadata["cloud"] = {} metadata["cloud"]["provider"] = "aws" metadata["cloud"]["region"] = os.environ.get("AWS_REGION") metadata["cloud"]["service"] = {"name": "lambda"} metadata["cloud"]["account"] = {"id": arn.split(":")[4]} elasticapm.set_context(cloud_context, "cloud") elasticapm.set_context(service_context, "service") # faas doesn't actually belong in context, but we handle this in to_dict elasticapm.set_context(faas, "faas") if message_context: elasticapm.set_context(service_context, "message") self.client._transport.add_metadata(metadata)
async def __call__(self, scope, receive, send): """ Args: scope: ASGI scope dictionary receive: receive awaitable callable send: send awaitable callable """ # we only handle the http scope, skip anything else. if scope["type"] != "http": await self.app(scope, receive, send) return @functools.wraps(send) async def wrapped_send(message): if message.get("type") == "http.response.start": await set_context( lambda: get_data_from_response(message, self.client.config, constants.TRANSACTION), "response") result = "HTTP {}xx".format(message["status"] // 100) elasticapm.set_transaction_result(result, override=False) await send(message) # When we consume the body from receive, we replace the streaming # mechanism with a mocked version -- this workaround came from # https://github.com/encode/starlette/issues/495#issuecomment-513138055 body = b"" while True: message = await receive() if not message: break if message["type"] == "http.request": b = message.get("body", b"") if b: body += b if not message.get("more_body", False): break if message["type"] == "http.disconnect": break async def _receive() -> Message: await asyncio.sleep(0) return {"type": "http.request", "body": body} request = Request(scope, receive=_receive) await self._request_started(request) try: await self.app(scope, _receive, wrapped_send) elasticapm.set_transaction_outcome(constants.OUTCOME.SUCCESS, override=False) except Exception: await self.capture_exception( context={ "request": await get_data_from_request(request, self.client.config, constants.ERROR) }) elasticapm.set_transaction_result("HTTP 5xx", override=False) elasticapm.set_transaction_outcome(constants.OUTCOME.FAILURE, override=False) elasticapm.set_context({"status_code": 500}, "response") raise finally: self.client.end_transaction()
async def handle_request(request, handler): elasticapm_client = get_client() if client is None else client should_trace = elasticapm_client and not elasticapm_client.should_ignore_url( request.path) if should_trace: trace_parent = AioHttpTraceParent.from_headers(request.headers) elasticapm_client.begin_transaction("request", trace_parent=trace_parent) resource = request.match_info.route.resource name = request.method if resource: # canonical has been added in 3.3, and returns one of path, formatter, prefix for attr in ("canonical", "_path", "_formatter", "_prefix"): if hasattr(resource, attr): name += " " + getattr(resource, attr) break else: name += " unknown route" else: name += " unknown route" elasticapm.set_transaction_name(name, override=False) elasticapm.set_context( lambda: get_data_from_request(request, elasticapm_client. config, constants.TRANSACTION), "request") try: response = await handler(request) if should_trace: elasticapm.set_transaction_result("HTTP {}xx".format( response.status // 100), override=False) elasticapm.set_transaction_outcome( http_status_code=response.status, override=False) elasticapm.set_context( lambda: get_data_from_response(response, elasticapm_client.config, constants.TRANSACTION), "response", ) return response except HTTPException as exc: # HTTPExceptions are response-like, e.g. have headers and status code. They can represent an HTTP # response below a 500 status code and therefore not something to capture as exception. Like # HTTPOk can be raised but will most likely be wrongly tagged as an APM error. Let's try and # capture this according to the status. if exc.status_code < 500 and not should_trace: raise if elasticapm_client: elasticapm.set_transaction_result("HTTP {}xx".format( exc.status_code // 100), override=False) elasticapm.set_transaction_outcome( http_status_code=exc.status_code, override=False) elasticapm.set_context( lambda: get_data_from_response( exc, # noqa: F821 elasticapm_client.config, constants.ERROR if exc.status_code >= 500 else constants.TRANSACTION, # noqa: F821 ), "response", ) if exc.status_code >= 500: elasticapm_client.capture_exception( context={ "request": get_data_from_request(request, elasticapm_client. config, constants.ERROR) }) raise except Exception: if elasticapm_client: elasticapm.set_transaction_result("HTTP 5xx", override=False) elasticapm.set_transaction_outcome(http_status_code=500, override=False) elasticapm.set_context({"status_code": 500}, "response") elasticapm_client.capture_exception( context={ "request": get_data_from_request( request, elasticapm_client.config, constants.ERROR) }) raise finally: elasticapm_client.end_transaction()
def test_dedot_context_keys(elasticapm_client): elasticapm_client.begin_transaction("test") elasticapm.set_context({"d.o.t": "d_o_t", "s*t*a*r": "s_t_a_r", "q*u*o*t*e": "q_u_o_t_e"}) elasticapm_client.end_transaction("foo", 200) transaction = elasticapm_client.events[TRANSACTION][0] assert transaction["context"]["custom"] == {"s_t_a_r": "s_t_a_r", "q_u_o_t_e": "q_u_o_t_e", "d_o_t": "d_o_t"}
def set_context(data, key="custom"): if data: elasticapm.set_context(json.loads(data), key)