Ejemplo n.º 1
0
 def _register(self):
     self._send(
         Register(
             app=scout_config.value("name"),
             key=scout_config.value("key"),
             hostname=scout_config.value("hostname"),
         ))
Ejemplo n.º 2
0
def install(config=None):
    global shutdown_registered
    if config is not None:
        scout_config.set(**config)
    scout_config.log()

    if os.name == "nt":
        logger.info("APM Not Launching on PID: %s - Windows is not supported",
                    os.getpid())
        return False

    if not scout_config.value("monitor"):
        logger.info(
            "APM Not Launching on PID: %s - Configuration 'monitor' is not true",
            os.getpid(),
        )
        return False

    instruments.ensure_all_installed()
    objtrace.enable()

    logger.debug("APM Launching on PID: %s", os.getpid())
    launched = CoreAgentManager().launch()

    report_app_metadata()
    if launched:
        # Stop the thread to avoid running threads pre-fork
        CoreAgentSocketThread.ensure_stopped()

    if scout_config.value(
            "shutdown_timeout_seconds") > 0.0 and not shutdown_registered:
        atexit.register(shutdown)
        shutdown_registered = True

    return True
Ejemplo n.º 3
0
 def __init__(self):
     self.core_agent_bin_path = None
     self.core_agent_bin_version = None
     self.core_agent_dir = "{}/{}".format(
         scout_config.value("core_agent_dir"),
         scout_config.value("core_agent_full_name"),
     )
     self.downloader = CoreAgentDownloader(
         self.core_agent_dir, scout_config.value("core_agent_full_name"))
Ejemplo n.º 4
0
    def log_file(self):
        # Old deprecated name "log_file"
        path = scout_config.value("log_file")
        if path is None:
            path = scout_config.value("core_agent_log_file")

        if path is not None:
            return ["--log-file", path]
        else:
            return []
 def log_level(self):
     # Old deprecated name "log_level"
     log_level = scout_config.value("log_level")
     if log_level is not None:
         logger.warning(
             "The config name 'log_level' is deprecated - " +
             "please use the new name 'core_agent_log_level' instead. " +
             "This might be configured in your environment variables or " +
             "framework settings as SCOUT_LOG_LEVEL.")
     else:
         log_level = scout_config.value("core_agent_log_level")
     return ["--log-level", log_level]
Ejemplo n.º 6
0
def test_config():
    sha = "4de21f8ea228a082d4f039c0c991ee41dfb6f9d8"
    try:
        Config.set(revision_sha=sha)
        assert scout_config.value("revision_sha") == sha
    finally:
        scout_config.reset_all()
Ejemplo n.º 7
0
    def __call__(self, request):
        """
        Wrap a single incoming request with start and stop calls.
        This will start timing, but relies on the process_view callback to
        capture more details about what view was really called, and other
        similar info.

        If process_view isn't called, then the request will not
        be recorded.  This can happen if a middleware further along the stack
        doesn't call onward, and instead returns a response directly.
        """
        if not scout_config.value("monitor"):
            return self.get_response(request)

        tracked_request = TrackedRequest.instance()

        # This operation name won't be recorded unless changed later in
        # process_view
        tracked_request.start_span(operation="Unknown",
                                   should_capture_backtrace=False)
        try:
            response = self.get_response(request)
            if 500 <= response.status_code <= 599:
                tracked_request.tag("error", "true")
            return response
        finally:
            tracked_request.stop_span()
Ejemplo n.º 8
0
def werkzeug_track_request_data(werkzeug_request, tracked_request):
    """
    Several integrations use Werkzeug requests, so share the code for
    extracting common data here.
    """
    path = werkzeug_request.path
    tracked_request.tag(
        "path",
        create_filtered_path(path, werkzeug_request.args.items(multi=True)))
    if ignore_path(path):
        tracked_request.tag("ignore_transaction", True)

    if scout_config.value("collect_remote_ip"):
        # Determine a remote IP to associate with the request. The value is
        # spoofable by the requester so this is not suitable to use in any
        # security sensitive context.
        user_ip = (werkzeug_request.headers.get("x-forwarded-for",
                                                default="").split(",")[0]
                   or werkzeug_request.headers.get("client-ip",
                                                   default="").split(",")[0]
                   or werkzeug_request.remote_addr)
        tracked_request.tag("user_ip", user_ip)

    queue_time = werkzeug_request.headers.get(
        "x-queue-start", default="") or werkzeug_request.headers.get(
            "x-request-start", default="")
    tracked_queue_time = track_request_queue_time(queue_time, tracked_request)
    if not tracked_queue_time:
        amazon_queue_time = werkzeug_request.headers.get("x-amzn-trace-id",
                                                         default="")
        track_amazon_request_queue_time(amazon_queue_time, tracked_request)
Ejemplo n.º 9
0
def track_request_view_data(request, tracked_request):
    path = request.path
    tracked_request.tag(
        "path",
        create_filtered_path(path, [(k, v) for k, vs in request.GET.lists()
                                    for v in vs]),
    )
    if ignore_path(path):
        tracked_request.tag("ignore_transaction", True)

    if scout_config.value("collect_remote_ip"):
        try:
            # Determine a remote IP to associate with the request. The value is
            # spoofable by the requester so this is not suitable to use in any
            # security sensitive context.
            user_ip = (request.META.get("HTTP_X_FORWARDED_FOR",
                                        "").split(",")[0]
                       or request.META.get("HTTP_CLIENT_IP", "").split(",")[0]
                       or request.META.get("REMOTE_ADDR", None))
            tracked_request.tag("user_ip", user_ip)
        except Exception:
            pass

    user = getattr(request, "user", None)
    if user is not None:
        try:
            tracked_request.tag("username", user.get_username())
        except Exception:
            pass

    tracked_request.tag("urlconf", get_urlconf(settings.ROOT_URLCONF))
Ejemplo n.º 10
0
def asgi_track_request_data(scope, tracked_request):
    """
    Track request data from an ASGI HTTP or Websocket scope.
    """
    path = scope.get("root_path", "") + scope["path"]
    query_params = parse_qsl(scope.get("query_string", b"").decode("utf-8"))
    tracked_request.tag("path", create_filtered_path(path, query_params))
    if ignore_path(path):
        tracked_request.tag("ignore_transaction", True)

    # We only care about the last values of headers so don't care that we use
    # a plain dict rather than a multi-value dict
    headers = {k.lower(): v for k, v in scope.get("headers", ())}

    if scout_config.value("collect_remote_ip"):
        user_ip = (
            headers.get(b"x-forwarded-for", b"").decode("latin1").split(",")[0]
            or headers.get(b"client-ip", b"").decode("latin1").split(",")[0]
            or scope.get("client", ("", ))[0])
        tracked_request.tag("user_ip", user_ip)

    queue_time = headers.get(b"x-queue-start", b"") or headers.get(
        b"x-request-start", b"")
    tracked_queue_time = track_request_queue_time(queue_time.decode("latin1"),
                                                  tracked_request)
    if not tracked_queue_time:
        amazon_queue_time = headers.get(b"x-amzn-trace-id", b"")
        track_amazon_request_queue_time(amazon_queue_time.decode("latin1"),
                                        tracked_request)
Ejemplo n.º 11
0
    def run(self):
        batch_size = scout_config.value("errors_batch_size") or 1
        http = urllib3_cert_pool_manager()
        try:
            while True:
                errors = []
                try:
                    # Attempt to fetch the batch size off of the queue.
                    for _ in range(batch_size):
                        error = self._queue.get(block=True, timeout=1 * SECOND)
                        if error:
                            errors.append(error)
                except queue.Empty:
                    pass

                if errors and self._send(http, errors):
                    for _ in range(len(errors)):
                        self._queue.task_done()

                # Check for stop event after each read. This allows opening,
                # sending, and then immediately stopping.
                if self._stop_event.is_set():
                    logger.debug("ErrorServiceThread stopping.")
                    break
        except Exception as exc:
            logger.debug("ErrorServiceThread exception: %r", exc, exc_info=exc)
        finally:
            http.clear()
            logger.debug("ErrorServiceThread stopped.")
Ejemplo n.º 12
0
def track_request_view_data(request, tracked_request):
    path = request.path
    tracked_request.tag(
        "path",
        create_filtered_path(path, [(k, v) for k, vs in request.GET.lists()
                                    for v in vs]),
    )
    if ignore_path(path):
        tracked_request.tag("ignore_transaction", True)

    if scout_config.value("collect_remote_ip"):
        try:
            # Determine a remote IP to associate with the request. The value is
            # spoofable by the requester so this is not suitable to use in any
            # security sensitive context.
            user_ip = (request.META.get("HTTP_X_FORWARDED_FOR",
                                        "").split(",")[0]
                       or request.META.get("HTTP_CLIENT_IP", "").split(",")[0]
                       or request.META.get("REMOTE_ADDR", None))
            tracked_request.tag("user_ip", user_ip)
        except Exception:
            pass

    # Django's request.user caches in this attribute on first access. We only
    # want to track the user if the application code has touched request.user
    # because touching it causes session access, which adds "Cookie" to the
    # "Vary" header.
    user = getattr(request, "_cached_user", None)
    if user is not None:
        try:
            tracked_request.tag("username", user.get_username())
        except Exception:
            pass

    tracked_request.tag("urlconf", get_urlconf(settings.ROOT_URLCONF))
Ejemplo n.º 13
0
    def process_request(self, req, resp):
        if self._do_nothing:
            return
        if self.api is None and self.hug_http_interface is not None:
            self.api = self.hug_http_interface.falcon
        tracked_request = TrackedRequest.instance()
        tracked_request.is_real_request = True
        req.context.scout_tracked_request = tracked_request
        tracked_request.start_span(operation="Middleware",
                                   should_capture_backtrace=False)

        path = req.path
        # Falcon URL parameter values are *either* single items or lists
        url_params = [(k, v) for k, vs in req.params.items()
                      for v in (vs if isinstance(vs, list) else [vs])]
        tracked_request.tag("path", create_filtered_path(path, url_params))
        if ignore_path(path):
            tracked_request.tag("ignore_transaction", True)

        if scout_config.value("collect_remote_ip"):
            # Determine a remote IP to associate with the request. The value is
            # spoofable by the requester so this is not suitable to use in any
            # security sensitive context.
            user_ip = (req.get_header("x-forwarded-for",
                                      default="").split(",")[0]
                       or req.get_header("client-ip", default="").split(",")[0]
                       or req.remote_addr)
            tracked_request.tag("user_ip", user_ip)

        queue_time = req.get_header(
            "x-queue-start", default="") or req.get_header("x-request-start",
                                                           default="")
        track_request_queue_time(queue_time, tracked_request)
Ejemplo n.º 14
0
    def after_request(self):
        if self._do_nothing:
            return
        request = cherrypy.request
        tracked_request = getattr(request, "_scout_tracked_request", None)
        if tracked_request is None:
            return

        # Rename controller span now routing has been done
        operation_name = get_operation_name(request)
        if operation_name is not None:
            request._scout_controller_span.operation = operation_name

        # Grab general request data now it has been parsed
        path = request.path_info
        # Parse params ourselves because we want only GET params but CherryPy
        # parses POST params (nearly always sensitive) into the same dict.
        params = parse_qsl(request.query_string)
        tracked_request.tag("path", create_filtered_path(path, params))
        if ignore_path(path):
            tracked_request.tag("ignore_transaction", True)

        if scout_config.value("collect_remote_ip"):
            # Determine a remote IP to associate with the request. The value is
            # spoofable by the requester so this is not suitable to use in any
            # security sensitive context.
            user_ip = (
                request.headers.get("x-forwarded-for", "").split(",")[0]
                or request.headers.get("client-ip", "").split(",")[0]
                or (request.remote.ip or None)
            )
            tracked_request.tag("user_ip", user_ip)

        queue_time = request.headers.get("x-queue-start", "") or request.headers.get(
            "x-request-start", ""
        )
        tracked_queue_time = track_request_queue_time(queue_time, tracked_request)
        if not tracked_queue_time:
            amazon_queue_time = request.headers.get("x-amzn-trace-id", "")
            track_amazon_request_queue_time(amazon_queue_time, tracked_request)

        response = cherrypy.response
        status = response.status
        if isinstance(status, int):
            status_int = status
        else:
            status_first = status.split(" ", 1)[0]
            try:
                status_int = int(status_first)
            except ValueError:
                # Assume OK
                status_int = 200
        if 500 <= status_int <= 599:
            tracked_request.tag("error", "true")
        elif status_int == 404:
            tracked_request.is_real_request = False

        tracked_request.stop_span()
def create_filtered_path(path, query_params):
    if scout_config.value("uri_reporting") == "path":
        return path
    filtered_params = sorted(
        ((k, "[FILTERED]" if k.lower() in FILTER_PARAMETERS else v)
         for k, v in query_params))
    if not filtered_params:
        return path
    return path + "?" + urlencode(filtered_params)
Ejemplo n.º 16
0
    def process_exception(self, request, exception):
        """
        Mark this request as having errored out

        Does not modify or catch or otherwise change the exception thrown
        """
        if not scout_config.value("monitor"):
            return
        TrackedRequest.instance().tag("error", "true")
Ejemplo n.º 17
0
    def scout_tween(request):
        tracked_request = TrackedRequest.instance()
        span = tracked_request.start_span(operation="Controller/Pyramid",
                                          should_capture_backtrace=False)

        try:
            path = request.path
            # mixed() returns values as *either* single items or lists
            url_params = [(k, v)
                          for k, vs in request.GET.dict_of_lists().items()
                          for v in vs]
            tracked_request.tag("path", create_filtered_path(path, url_params))
            if ignore_path(path):
                tracked_request.tag("ignore_transaction", True)

            if scout_config.value("collect_remote_ip"):
                # Determine a remote IP to associate with the request. The value is
                # spoofable by the requester so this is not suitable to use in any
                # security sensitive context.
                user_ip = (request.headers.get("x-forwarded-for",
                                               default="").split(",")[0]
                           or request.headers.get("client-ip",
                                                  default="").split(",")[0]
                           or request.remote_addr)
                tracked_request.tag("user_ip", user_ip)

            tracked_queue_time = False
            queue_time = request.headers.get(
                "x-queue-start", default="") or request.headers.get(
                    "x-request-start", default="")
            tracked_queue_time = track_request_queue_time(
                queue_time, tracked_request)
            if not tracked_queue_time:
                amazon_queue_time = request.headers.get("x-amzn-trace-id",
                                                        default="")
                track_amazon_request_queue_time(amazon_queue_time,
                                                tracked_request)

            try:
                try:
                    response = handler(request)
                finally:
                    # Routing lives further down the call chain. So time it
                    # starting above, but only set the name if it gets a name
                    if request.matched_route is not None:
                        tracked_request.is_real_request = True
                        span.operation = "Controller/" + request.matched_route.name
            except Exception:
                tracked_request.tag("error", "true")
                raise

            if 500 <= response.status_code <= 599:
                tracked_request.tag("error", "true")
        finally:
            tracked_request.stop_span()

        return response
Ejemplo n.º 18
0
    def launch(self):
        if not scout_config.value("core_agent_launch"):
            logger.debug("Not attempting to launch Core Agent "
                         "due to 'core_agent_launch' setting.")
            return False

        if not self.verify():
            if not scout_config.value("core_agent_download"):
                logger.debug("Not attempting to download Core Agent due "
                             "to 'core_agent_download' setting.")
                return False

            self.download()

            if not self.verify():
                logger.debug(
                    "Failed to verify Core Agent. Not launching Core Agent.")
                return False

        return self.run()
Ejemplo n.º 19
0
 def callback(queue_size):
     if scout_config.value("shutdown_message_enabled"):
         print(  # noqa: T001
             ("Scout draining {queue_size} event{s} for up to" +
              " {timeout_seconds} seconds").format(
                  queue_size=queue_size,
                  s=("" if queue_size == 1 else "s"),
                  timeout_seconds=timeout_seconds,
              ),
             file=sys.stderr,
         )
Ejemplo n.º 20
0
def ensure_all_installed():
    disabled_instruments = scout_config.value("disabled_instruments")
    for instrument_name in instrument_names:
        if instrument_name in disabled_instruments:
            logger.info("%s instrument is disabled. Skipping.",
                        instrument_name)
            continue

        module = importlib.import_module("{}.{}".format(
            __name__, instrument_name))
        module.ensure_installed()
Ejemplo n.º 21
0
    def run(self):
        # Old deprecated name "socket_path"
        socket_path = scout_config.value("socket_path")
        if socket_path is None:
            socket_path = scout_config.value("core_agent_socket_path")
        self.socket_path = socket_path
        self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

        try:
            self._connect()
            self._register()
            while True:
                try:
                    body = self._command_queue.get(block=True,
                                                   timeout=1 * SECOND)
                except queue.Empty:
                    body = None

                if body is not None:
                    result = self._send(body)
                    if result:
                        self._command_queue.task_done()
                    else:
                        # Something was wrong with the socket.
                        self._disconnect()
                        self._connect()
                        self._register()

                # Check for stop event after each read. This allows opening,
                # sending, and then immediately stopping. We do this for
                # the metadata event at application start time.
                if self._stop_event.is_set():
                    logger.debug("CoreAgentSocketThread stopping.")
                    break
        except Exception as exc:
            logger.debug("CoreAgentSocketThread exception: %r",
                         exc,
                         exc_info=exc)
        finally:
            self.socket.close()
            logger.debug("CoreAgentSocketThread stopped.")
Ejemplo n.º 22
0
    def process_view(self, request, view_func, view_args, view_kwargs):
        """
        Capture details about the view_func that is about to execute
        """
        if not scout_config.value("monitor"):
            return
        tracked_request = TrackedRequest.instance()
        tracked_request.is_real_request = True

        span = tracked_request.current_span()
        if span is not None:
            span.operation = get_operation_name(request)
Ejemplo n.º 23
0
def create_filtered_path(path, query_params):
    if scout_config.value("uri_reporting") == "path":
        return path
    # Python 2 unicode compatibility: force all keys and values to bytes
    filtered_params = sorted(((
        key.encode("utf-8"),
        (b"[FILTERED]"
         if key.lower() in FILTER_PARAMETERS else value.encode("utf-8")),
    ) for key, value in query_params))
    if not filtered_params:
        return path
    return path + "?" + urlencode(filtered_params)
Ejemplo n.º 24
0
def create_filtered_path(path, query_params):
    if scout_config.value("uri_reporting") == "path":
        return path
    filtered_params = sorted([
        (
            text_type(key).encode("utf-8"),
            # Apply text_type again to cover the None case.
            text_type(filter_element(key, value)).encode("utf-8"),
        ) for key, value in query_params
    ])
    if not filtered_params:
        return path
    return path + "?" + urlencode(filtered_params)
Ejemplo n.º 25
0
    def process_request(self, request):
        if not scout_config.value("monitor"):
            return
        tracked_request = TrackedRequest.instance()
        request._scout_tracked_request = tracked_request

        queue_time = request.META.get(
            "HTTP_X_QUEUE_START") or request.META.get("HTTP_X_REQUEST_START",
                                                      "")
        track_request_queue_time(queue_time, tracked_request)

        tracked_request.start_span(operation="Middleware",
                                   should_capture_backtrace=False)
Ejemplo n.º 26
0
def shutdown():
    timeout_seconds = scout_config.value("shutdown_timeout_seconds")

    def apm_callback(queue_size):
        if scout_config.value("shutdown_message_enabled"):
            print(  # noqa: T001
                (
                    "Scout draining {queue_size} event{s} for up to"
                    + " {timeout_seconds} seconds"
                ).format(
                    queue_size=queue_size,
                    s=("" if queue_size == 1 else "s"),
                    timeout_seconds=timeout_seconds,
                ),
                file=sys.stderr,
            )

    def error_callback(queue_size):
        if scout_config.value("shutdown_message_enabled"):
            print(  # noqa: T001
                (
                    "Scout draining {queue_size} error{s} for up to"
                    + " {timeout_seconds} seconds"
                ).format(
                    queue_size=queue_size,
                    s=("" if queue_size == 1 else "s"),
                    timeout_seconds=timeout_seconds,
                ),
                file=sys.stderr,
            )

    CoreAgentSocketThread.wait_until_drained(
        timeout_seconds=timeout_seconds, callback=apm_callback
    )

    if scout_config.value("errors_enabled"):
        ErrorServiceThread.wait_until_drained(
            timeout_seconds=timeout_seconds, callback=error_callback
        )
Ejemplo n.º 27
0
def wrap_callback(wrapped, instance, args, kwargs):
    tracked_request = TrackedRequest.instance()
    tracked_request.is_real_request = True

    path = request.path
    # allitems() is an undocumented bottle internal
    tracked_request.tag("path",
                        create_filtered_path(path, request.query.allitems()))
    if ignore_path(path):
        tracked_request.tag("ignore_transaction", True)

    if request.route.name is not None:
        controller_name = request.route.name
    else:
        controller_name = request.route.rule
    if controller_name == "/":
        controller_name = "/home"
    if not controller_name.startswith("/"):
        controller_name = "/" + controller_name
    tracked_request.start_span(
        operation="Controller{}".format(controller_name),
        should_capture_backtrace=False)

    if scout_config.value("collect_remote_ip"):
        # Determine a remote IP to associate with the request. The
        # value is spoofable by the requester so this is not suitable
        # to use in any security sensitive context.
        user_ip = (request.headers.get("x-forwarded-for", "").split(",")[0]
                   or request.headers.get("client-ip", "").split(",")[0]
                   or request.environ.get("REMOTE_ADDR"))
        tracked_request.tag("user_ip", user_ip)

    tracked_queue_time = False
    queue_time = request.headers.get(
        "x-queue-start", "") or request.headers.get("x-request-start", "")
    tracked_queue_time = track_request_queue_time(queue_time, tracked_request)
    if not tracked_queue_time:
        amazon_queue_time = request.headers.get("x-amzn-trace-id", "")
        track_amazon_request_queue_time(amazon_queue_time, tracked_request)

    try:
        value = wrapped(*args, **kwargs)
    except Exception:
        tracked_request.tag("error", "true")
        raise
    else:
        if 500 <= response.status_code <= 599:
            tracked_request.tag("error", "true")
        return value
    finally:
        tracked_request.stop_span()
Ejemplo n.º 28
0
    def __call__(self, request):
        if not scout_config.value("monitor"):
            return self.get_response(request)

        tracked_request = TrackedRequest.instance()

        queue_time = request.META.get(
            "HTTP_X_QUEUE_START") or request.META.get("HTTP_X_REQUEST_START",
                                                      "")
        track_request_queue_time(queue_time, tracked_request)

        with tracked_request.span(
                operation="Middleware",
                should_capture_backtrace=False,
        ):
            return self.get_response(request)
Ejemplo n.º 29
0
    def wrapped_full_dispatch_request(self, wrapped, instance, args, kwargs):
        if not self._attempted_install:
            self.extract_flask_settings()
            installed = scout_apm.core.install()
            self._do_nothing = not installed
            self._attempted_install = True

        if self._do_nothing:
            return wrapped(*args, **kwargs)

        # Pass on routing exceptions (normally 404's)
        if request.routing_exception is not None:
            return wrapped(*args, **kwargs)

        request_components = get_request_components(self.app, request)
        operation = "Controller/{}.{}".format(request_components.module,
                                              request_components.controller)

        tracked_request = TrackedRequest.instance()
        tracked_request.is_real_request = True
        request._scout_tracked_request = tracked_request

        werkzeug_track_request_data(request, tracked_request)

        with tracked_request.span(operation=operation,
                                  should_capture_backtrace=False) as span:
            request._scout_view_span = span

            try:
                response = wrapped(*args, **kwargs)
            except Exception as exc:
                tracked_request.tag("error", "true")
                if scout_config.value("errors_enabled"):
                    ErrorMonitor.send(
                        sys.exc_info(),
                        request_components=get_request_components(
                            self.app, request),
                        request_path=request.path,
                        request_params=dict(request.args.lists()),
                        session=dict(session.items()),
                        environment=self.app.config,
                    )
                raise exc
            else:
                if 500 <= response.status_code <= 599:
                    tracked_request.tag("error", "true")
                return response
Ejemplo n.º 30
0
    def ready(self):
        self.update_scout_config_from_django_settings()
        setting_changed.connect(self.on_setting_changed)

        # Finish installing the agent. If the agent isn't installed for any
        # reason, return without installing instruments
        installed = scout_apm.core.install()
        if not installed:
            return

        if scout_config.value("errors_enabled"):
            got_request_exception.connect(self.on_got_request_exception)

        self.install_middleware()

        # Setup Instruments
        ensure_huey_instrumented()
        ensure_sql_instrumented()
        ensure_templates_instrumented()