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 wrapped(*args, **kwargs) # Late import to avoid ImportErrors from tornado.web import Finish, HTTPError from elasticapm.contrib.tornado.utils import get_data_from_request e = args[0] if isinstance(e, Finish): # Not an error; Finish is an exception that ends a request without an error response return wrapped(*args, **kwargs) client = instance.application.elasticapm_client request = instance.request client.capture_exception( context={ "request": get_data_from_request(instance, request, client.config, constants.ERROR) }) elasticapm.set_transaction_outcome(constants.OUTCOME.FAILURE) if isinstance(e, HTTPError): elasticapm.set_transaction_result("HTTP {}xx".format( int(e.status_code / 100)), override=False) elasticapm.set_context({"status_code": e.status_code}, "response") else: elasticapm.set_transaction_result("HTTP 5xx", override=False) elasticapm.set_context({"status_code": 500}, "response") return wrapped(*args, **kwargs)
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
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 trace_parent = TraceParent.from_headers(request.headers) client = instance.application.elasticapm_client 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) 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
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: 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 end_transaction(task_id, task, *args, **kwargs): name = get_name_from_func(task) state = kwargs.get("state", "None") if state == states.SUCCESS: outcome = constants.OUTCOME.SUCCESS elif state in states.EXCEPTION_STATES: outcome = constants.OUTCOME.FAILURE else: outcome = constants.OUTCOME.UNKNOWN elasticapm.set_transaction_outcome(outcome, override=False) client.end_transaction(name, state)
async def _instrument_response(request: Request, response: HTTPResponse): await set_context( lambda: get_response_info( config=self._client.config, response=response, ), "response", ) self._setup_transaction_name(request=request) result = f"HTTP {response.status // 100}xx" set_transaction_result(result=result, override=False) set_transaction_outcome(http_status_code=response.status, override=False) elastic_context(data={"status_code": response.status}, key="response") self._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, 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()
def request_finished(self, app, response): if not self.app.debug or self.client.config.debug: elasticapm.set_context( lambda: get_data_from_response(response, self.client.config, constants.TRANSACTION), "response" ) if response.status_code: result = "HTTP {}xx".format(response.status_code // 100) elasticapm.set_transaction_outcome(http_status_code=response.status_code, override=False) else: result = response.status elasticapm.set_transaction_outcome(http_status_code=response.status, 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 test_transaction_outcome(elasticapm_client, caplog, outcome, http_status_code, log_message, result): transaction = elasticapm_client.begin_transaction("test") with caplog.at_level(logging.INFO, "elasticapm.traces"): elasticapm.set_transaction_outcome(outcome=outcome, http_status_code=http_status_code) assert transaction.outcome == result if log_message is None: assert not [ True for record in caplog.records if record.name == "elasticapm.traces" ] else: assert_any_record_contains(caplog.records, log_message, "elasticapm.traces")
async def _handler(request: Request, exception: BaseException): if not self._client: return self._client.capture_exception( exc_info=sys.exc_info(), context={ "request": await get_request_info(config=self._client.config, request=request), }, handled=True, ) self._setup_transaction_name(request=request) set_transaction_result(result="HTTP 5xx", override=False) set_transaction_outcome(outcome=constants.OUTCOME.FAILURE, override=False) elastic_context(data={"status_code": 500}, key="response") self._client.end_transaction()
def __exit__(self, exc_type, exc_val, exc_tb): """ Transaction teardown """ if self.response and isinstance(self.response, dict): elasticapm.set_context( lambda: get_data_from_response(self.response, capture_headers=self.client. config.capture_headers), "response", ) if "statusCode" in self.response: try: result = "HTTP {}xx".format( int(self.response["statusCode"]) // 100) elasticapm.set_transaction_result(result, override=False) except ValueError: logger.warning( "Lambda function's statusCode was not formed as an int. Assuming 5xx result." ) elasticapm.set_transaction_result("HTTP 5xx", override=False) if exc_val: self.client.capture_exception(exc_info=(exc_type, exc_val, exc_tb), handled=False) if self.source == "api": 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") else: elasticapm.set_transaction_result("failure", override=False) elasticapm.set_transaction_outcome(outcome="failure", override=False) self.client.end_transaction() try: logger.debug("flushing elasticapm") self.client._transport.flush() logger.debug("done flushing elasticapm") except ValueError: logger.warning("flush timed out")
def test_transaction_outcome_override(elasticapm_client): transaction = elasticapm_client.begin_transaction("test") elasticapm.set_transaction_outcome(constants.OUTCOME.FAILURE) assert transaction.outcome == constants.OUTCOME.FAILURE elasticapm.set_transaction_outcome(constants.OUTCOME.SUCCESS, override=False) # still a failure assert transaction.outcome == constants.OUTCOME.FAILURE elasticapm.set_transaction_outcome(constants.OUTCOME.SUCCESS, override=True) assert transaction.outcome == constants.OUTCOME.SUCCESS
def pytest_report_teststatus(report: Union[CollectReport, TestReport], config: Config): if report.outcome == "failed": # FIXME might need to make sure we are in a specific test transaction e_.set_transaction_outcome(OUTCOME.FAILURE)
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()
def test_set_transaction_outcome(elasticapm_client): transaction = elasticapm_client.begin_transaction("test") elasticapm.set_transaction_outcome("failure") assert "failure" == transaction.outcome
def test_set_transaction_outcome_with_20X_http_status_code(elasticapm_client): transaction = elasticapm_client.begin_transaction("test") elasticapm.set_transaction_outcome("anything", http_status_code=200) assert "success" == transaction.outcome
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_set_transaction_outcome_with_unknown_http_status_code(elasticapm_client): transaction = elasticapm_client.begin_transaction("test") elasticapm.set_transaction_outcome("anything", http_status_code="a") assert "unknown" == transaction.outcome
def test_set_unknown_transaction_outcome(elasticapm_client): transaction = elasticapm_client.begin_transaction("test") elasticapm.set_transaction_outcome("anything") assert "unknown" == transaction.outcome