Beispiel #1
0
    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)
Beispiel #2
0
    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
Beispiel #3
0
    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
Beispiel #4
0
    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
Beispiel #5
0
 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
Beispiel #6
0
 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)
Beispiel #7
0
 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()
Beispiel #8
0
    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()
Beispiel #9
0
 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")
Beispiel #11
0
        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()
Beispiel #12
0
    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)
Beispiel #15
0
    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
Beispiel #18
0
    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