Example #1
0
def test_empty_measurement_interface(mini_sentry, relay_chain):

    # set up relay

    relay = relay_chain()
    mini_sentry.add_basic_project_config(42)

    # construct envelope

    transaction_item = generate_transaction_item()

    transaction_item.update({"measurements": {}})

    envelope = Envelope()
    envelope.add_transaction(transaction_item)

    # ingest envelope

    relay.send_envelope(42, envelope)

    envelope = mini_sentry.captured_events.get(timeout=1)

    event = envelope.get_transaction_event()

    # test actual output

    assert event["transaction"] == "/organizations/:orgId/performance/:eventSlug/"
    assert "measurements" not in event, event
Example #2
0
def test_it_removes_transactions(mini_sentry, relay):
    """
    Tests that when sampling is set to 0% for the trace context project the transactions are removed
    """
    project_id = 42
    relay = relay(mini_sentry, _outcomes_enabled_config())

    # create a basic project config
    config = mini_sentry.add_basic_project_config(project_id)
    # add a sampling rule to project config that removes all transactions (sample_rate=0)
    public_key = config["publicKeys"][0]["publicKey"]
    _add_sampling_config(config, project_ids=[project_id], sample_rate=0)

    # create an envelope with a trace context that is initiated by this project (for simplicity)
    envelope = Envelope()
    transaction, trace_id = _create_transaction_item()
    envelope.add_transaction(transaction)
    _add_trace_info(envelope, trace_id=trace_id, public_key=public_key)

    # send the event, the transaction should be removed.
    relay.send_envelope(project_id, envelope)
    # the event should be removed by Relay sampling
    with pytest.raises(queue.Empty):
        mini_sentry.captured_events.get(timeout=1)

    outcomes = mini_sentry.captured_outcomes.get(timeout=2)
    assert outcomes is not None
    outcome = outcomes["outcomes"][0]
    assert outcome.get("outcome") == 3
    assert outcome.get("reason") == "transaction_sampled"
Example #3
0
 def inner(user):
     transaction_data = generator()
     project_info = get_project_info(user)
     envelope = Envelope()
     envelope.add_transaction(transaction_data)
     return send_envelope(user.client, project_info.id, project_info.key,
                          envelope)
Example #4
0
def test_fast_path(mini_sentry, relay):
    """
    Tests that the fast path works.

    When the project config is already fetched and the envelope only
    contains a transaction a fast evaluation path is used.

    While this is an implementation detail of Relay that is not visible
    here we test that Relay behaves normally for the conditions that we
    know are going to trigger a fast-path evaluation
    """
    project_id = 42
    relay = relay(mini_sentry, _outcomes_enabled_config())

    # create a basic project config
    config = mini_sentry.add_basic_project_config(project_id)
    # add a sampling rule to project config that removes all transactions (sample_rate=0)
    public_key = config["publicKeys"][0]["publicKey"]
    _add_sampling_config(config, project_ids=[project_id], sample_rate=0)

    for i in range(2):
        # create an envelope with a trace context that is initiated by this project (for simplicity)
        envelope = Envelope()
        transaction, trace_id = _create_transaction_item()
        envelope.add_transaction(transaction)
        _add_trace_info(envelope, trace_id=trace_id, public_key=public_key)

        # send the event, the transaction should be removed.
        relay.send_envelope(project_id, envelope)
        # the event should be removed by Relay sampling
        with pytest.raises(queue.Empty):
            mini_sentry.captured_events.get(timeout=1)

        outcomes = mini_sentry.captured_outcomes.get(timeout=2)
        assert outcomes is not None
Example #5
0
def test_it_keeps_transactions(mini_sentry, relay):
    """
    Tests that when sampling is set to 100% for the trace context project the transactions are kept
    """
    project_id = 42
    relay = relay(mini_sentry, _outcomes_enabled_config())

    # create a basic project config
    config = mini_sentry.add_basic_project_config(project_id)
    # add a sampling rule to project config that keeps all transactions (sample_rate=1)
    public_key = config["publicKeys"][0]["publicKey"]
    _add_sampling_config(config, project_ids=[project_id], sample_rate=1)

    # create an envelope with a trace context that is initiated by this project (for simplicity)
    envelope = Envelope()
    transaction, trace_id = _create_transaction_item()
    envelope.add_transaction(transaction)
    _add_trace_info(envelope, trace_id=trace_id, public_key=public_key)

    # send the event, the transaction should be removed.
    relay.send_envelope(project_id, envelope)
    # the event should be left alone by Relay sampling
    evt = mini_sentry.captured_events.get(timeout=1).get_transaction_event()
    assert evt is not None
    # double check that we get back our trace object (check the trace_id from the object)
    evt_trace_id = (evt.setdefault("contexts",
                                   {}).setdefault("trace", {}).get("trace_id"))
    assert evt_trace_id == trace_id

    # no outcome should be generated since we forward the event to upstream
    with pytest.raises(queue.Empty):
        mini_sentry.captured_outcomes.get(timeout=2)
Example #6
0
def test_normalize_measurement_interface(mini_sentry, relay_with_processing,
                                         transactions_consumer):
    relay = relay_with_processing()
    mini_sentry.add_basic_project_config(42)

    events_consumer = transactions_consumer()

    transaction_item = generate_transaction_item()
    transaction_item.update({
        "measurements": {
            "LCP": {
                "value": 420.69
            },
            "   lcp_final.element-Size123  ": {
                "value": 1
            },
            "fid": {
                "value": 2020
            },
            "cls": {
                "value": None
            },
            "fp": {
                "value": "im a first paint"
            },
            "Total Blocking Time": {
                "value": 3.14159
            },
            "missing_value": "string",
        }
    })

    envelope = Envelope()
    envelope.add_transaction(transaction_item)
    relay.send_envelope(42, envelope)

    event, _ = events_consumer.get_event()
    assert event[
        "transaction"] == "/organizations/:orgId/performance/:eventSlug/"
    assert "trace" in event["contexts"]
    assert "measurements" in event, event
    assert event["measurements"] == {
        "lcp": {
            "value": 420.69
        },
        "lcp_final.element-size123": {
            "value": 1
        },
        "fid": {
            "value": 2020
        },
        "cls": {
            "value": None
        },
        "fp": {
            "value": None
        },
        "missing_value": None,
    }
Example #7
0
def test_empty_span_attributes(
    mini_sentry, relay_with_processing, transactions_consumer
):
    events_consumer = transactions_consumer()

    relay = relay_with_processing()
    config = mini_sentry.add_basic_project_config(42)

    config["config"].setdefault("spanAttributes", [])

    transaction_item = generate_transaction_item()
    transaction_item.update(
        {
            "spans": [
                {
                    "description": "GET /api/0/organizations/?member=1",
                    "op": "http",
                    "parent_span_id": "aaaaaaaaaaaaaaaa",
                    "span_id": "bbbbbbbbbbbbbbbb",
                    "start_timestamp": 1000,
                    "timestamp": 3000,
                    "trace_id": "ff62a8b040f340bda5d830223def1d81",
                },
                {
                    "description": "GET /api/0/organizations/?member=1",
                    "op": "http",
                    "parent_span_id": "bbbbbbbbbbbbbbbb",
                    "span_id": "cccccccccccccccc",
                    "start_timestamp": 1400,
                    "timestamp": 2600,
                    "trace_id": "ff62a8b040f340bda5d830223def1d81",
                },
                {
                    "description": "GET /api/0/organizations/?member=1",
                    "op": "http",
                    "parent_span_id": "cccccccccccccccc",
                    "span_id": "dddddddddddddddd",
                    "start_timestamp": 1700,
                    "timestamp": 2300,
                    "trace_id": "ff62a8b040f340bda5d830223def1d81",
                },
            ],
        }
    )

    envelope = Envelope()
    envelope.add_transaction(transaction_item)
    relay.send_envelope(42, envelope)

    event, _ = events_consumer.get_event()
    assert event["transaction"] == "/organizations/:orgId/performance/:eventSlug/"
    assert "trace" in event["contexts"]
    assert "exclusive_time" not in event["contexts"]["trace"]
    for span in event["spans"]:
        assert "exclusive_time" not in span
Example #8
0
def test_transaction_event():
    envelope = Envelope()

    transaction_item = generate_transaction_item()
    transaction_item.update({"event_id": "a" * 32})
    envelope.add_transaction(transaction_item)

    # typically it should not be possible to be able to add a second transaction;
    # but we do it anyways
    another_transaction_item = generate_transaction_item()
    envelope.add_transaction(another_transaction_item)

    # should only fetch the first inserted transaction event
    assert envelope.get_transaction_event() == transaction_item
Example #9
0
def test_empty_measurement_interface(mini_sentry, relay_chain):
    relay = relay_chain(min_relay_version="20.10.0")
    mini_sentry.add_basic_project_config(42)

    transaction_item = generate_transaction_item()
    transaction_item.update({"measurements": {}})

    envelope = Envelope()
    envelope.add_transaction(transaction_item)
    relay.send_envelope(42, envelope)

    envelope = mini_sentry.captured_events.get(timeout=1)
    event = envelope.get_transaction_event()

    assert event["transaction"] == "/organizations/:orgId/performance/:eventSlug/"
    assert "measurements" not in event, event
Example #10
0
    def send_transaction(self, project_id, payload, item_headers=None):
        envelope = Envelope()
        envelope.add_transaction(payload)
        if item_headers:
            item = envelope.items[0]
            item.headers = {**item.headers, **item_headers}

        if envelope.headers is None:
            envelope.headers = {}

        trace_info = {
            "trace_id": payload["contexts"]["trace"]["trace_id"],
            "public_key": self.get_dsn_public_key(project_id),
        }
        envelope.headers["trace"] = trace_info

        self.send_envelope(project_id, envelope)
Example #11
0
def test_multi_item_envelope(mini_sentry, relay):
    """
    Test that multi item envelopes are handled correctly.

    Pass an envelope containing of a transaction that will be sampled and an event and
    test


    """
    project_id = 42
    relay = relay(mini_sentry, _outcomes_enabled_config())

    # create a basic project config
    config = mini_sentry.add_basic_project_config(project_id)
    # add a sampling rule to project config that removes all transactions (sample_rate=0)
    public_key = config["publicKeys"][0]["publicKey"]
    _add_sampling_config(config, project_ids=[project_id], sample_rate=0)

    # we'll run the test twice to make sure that the fast path works as well
    for i in range(2):
        # create an envelope with a trace context that is initiated by this project (for simplicity)
        envelope = Envelope()
        transaction, trace_id = _create_transaction_item()
        envelope.add_transaction(transaction)
        envelope.add_event({"message": "Hello"})
        _add_trace_info(envelope, trace_id=trace_id, public_key=public_key)

        # send the event, the transaction should be removed.
        relay.send_envelope(project_id, envelope)

        msg = mini_sentry.captured_events.get(timeout=1)
        assert msg is not None
        transaction = msg.get_transaction_event()
        event = msg.get_event()

        # check that the transaction was removed during the transaction sampling process
        assert transaction is None
        # but the event was kept
        assert event is not None
        assert event.setdefault("logentry", {}).get("formatted") == "Hello"

        # no outcome should be generated since we forward the event to upstream
        # NOTE this might change in the future so we might get outcomes here in the future
        with pytest.raises(queue.Empty):
            mini_sentry.captured_outcomes.get(timeout=2)
Example #12
0
def test_ops_breakdowns(mini_sentry, relay_with_processing, transactions_consumer):
    events_consumer = transactions_consumer()

    relay = relay_with_processing()
    config = mini_sentry.add_basic_project_config(42)

    config["config"].setdefault(
        "breakdownsV2",
        {
            "span_ops": {
                "type": "span_operations",
                "matches": ["http", "db", "browser", "resource"],
            },
            "span_ops_2": {
                "type": "spanOperations",
                "matches": ["http", "db", "browser", "resource"],
            },
            "span_ops_3": {
                "type": "whatever",
                "matches": ["http", "db", "browser", "resource"],
            },
        },
    )

    config["config"].setdefault(
        # old name of span operation breakdown key. we expect these to not generate anything at all
        "breakdowns",
        {
            "span_ops_4": {
                "type": "span_operations",
                "matches": ["http", "db", "browser", "resource"],
            },
            "span_ops_5": {
                "type": "spanOperations",
                "matches": ["http", "db", "browser", "resource"],
            },
            "span_ops_6": {
                "type": "whatever",
                "matches": ["http", "db", "browser", "resource"],
            },
        },
    )

    transaction_item = generate_transaction_item()
    transaction_item.update(
        {
            "spans": [
                {
                    "description": "GET /api/0/organizations/?member=1",
                    "op": "http",
                    "parent_span_id": "8f5a2b8768cafb4e",
                    "span_id": "bd429c44b67a3eb4",
                    "start_timestamp": 1000.5,
                    "timestamp": 2000.5,
                    "trace_id": "ff62a8b040f340bda5d830223def1d81",
                },
                {
                    "description": "GET /api/0/organizations/?member=1",
                    "op": "http",
                    "parent_span_id": "8f5a2b8768cafb4e",
                    "span_id": "bd429c44b67a3eb5",
                    "start_timestamp": 1500.5,
                    "timestamp": 2500.5,
                    "trace_id": "ff62a8b040f340bda5d830223def1d81",
                },
                {
                    "description": "GET /api/0/organizations/?member=1",
                    "op": "http.secure",
                    "parent_span_id": "8f5a2b8768cafb4e",
                    "span_id": "bd429c44b67a3eb6",
                    "start_timestamp": 3500,
                    "timestamp": 4000,
                    "trace_id": "ff62a8b040f340bda5d830223def1d81",
                },
                {
                    "description": "sentry-cdn.com/dist.js",
                    "op": "resource.link",
                    "parent_span_id": "8f5a2b8768cafb4f",
                    "span_id": "bd429c44b67a3eb7",
                    "start_timestamp": 500.001,
                    "timestamp": 600.002003,
                    "trace_id": "ff62a8b040f340bda5d830223def1d81",
                },
                {
                    "description": "sentry.middleware.env.SentryEnvMiddleware.process_request",
                    "op": "django.middleware",
                    "parent_span_id": "8f5a2b8768cafb4e",
                    "span_id": "bd429c44b67a3eb8",
                    "start_timestamp": 100,
                    "timestamp": 200,
                    "trace_id": "ff62a8b040f340bda5d830223def1d81",
                },
            ],
        }
    )

    envelope = Envelope()
    envelope.add_transaction(transaction_item)
    relay.send_envelope(42, envelope)

    event, _ = events_consumer.get_event()
    assert event["transaction"] == "/organizations/:orgId/performance/:eventSlug/"
    assert "trace" in event["contexts"]
    assert "breakdowns" in event, event
    assert event["breakdowns"] == {
        "span_ops": {
            "ops.http": {"value": 2000000.0, "unit": "millisecond"},
            "ops.resource": {"value": 100001.003, "unit": "millisecond"},
            "total.time": {"value": 2200001.003, "unit": "millisecond"},
        },
        "span_ops_2": {
            "ops.http": {"value": 2000000.0, "unit": "millisecond"},
            "ops.resource": {"value": 100001.003, "unit": "millisecond"},
            "total.time": {"value": 2200001.003, "unit": "millisecond"},
        },
    }
Example #13
0
    def capture_event(
            self,
            event,  # type: Event
            hint=None,  # type: Optional[Hint]
            scope=None,  # type: Optional[Scope]
    ):
        # type: (...) -> Optional[str]
        """Captures an event.

        :param event: A ready-made event that can be directly sent to Sentry.

        :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object.

        :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help.
        """
        if disable_capture_event.get(False):
            return None

        if self.transport is None:
            return None
        if hint is None:
            hint = {}
        event_id = event.get("event_id")
        hint = dict(hint or ())  # type: Hint

        if event_id is None:
            event["event_id"] = event_id = uuid.uuid4().hex
        if not self._should_capture(event, hint, scope):
            return None

        event_opt = self._prepare_event(event, hint, scope)
        if event_opt is None:
            return None

        # whenever we capture an event we also check if the session needs
        # to be updated based on that information.
        session = scope._session if scope else None
        if session:
            self._update_session_from_event(session, event)

        attachments = hint.get("attachments")
        is_transaction = event_opt.get("type") == "transaction"

        if is_transaction or attachments:
            # Transactions or events with attachments should go to the
            # /envelope/ endpoint.
            envelope = Envelope(
                headers={
                    "event_id": event_opt["event_id"],
                    "sent_at": format_timestamp(datetime.utcnow()),
                })

            if is_transaction:
                envelope.add_transaction(event_opt)
            else:
                envelope.add_event(event_opt)

            for attachment in attachments or ():
                envelope.add_item(attachment.to_envelope_item())
            self.transport.capture_envelope(envelope)
        else:
            # All other events go to the /store/ endpoint.
            self.transport.capture_event(event_opt)
        return event_id
Example #14
0
def test_uses_trace_public_key(mini_sentry, relay):
    """
    Tests that the public_key from the trace context is used

    The project configuration corresponding to the project pointed to
    by the context public_key DSN is used (not the dsn of the request)

    Create a trace context for projectA and send an event from projectB
    using projectA's trace.

    Configure project1 to sample out all events (sample_rate=0)
    Configure project2 to sample in all events (sample_rate=1)
    First:
        Send event to project2 with trace from project1
        It should be removed (sampled out)
    Second:
        Send event to project1 with trace from project2
        It should pass through

    """
    relay = relay(mini_sentry, _outcomes_enabled_config())

    # create basic project configs
    project_id1 = 42
    config1 = mini_sentry.add_basic_project_config(project_id1)
    public_key1 = config1["publicKeys"][0]["publicKey"]
    _add_sampling_config(config1, project_ids=[project_id1], sample_rate=0)

    project_id2 = 43
    config2 = mini_sentry.add_basic_project_config(project_id2)
    public_key2 = config2["publicKeys"][0]["publicKey"]
    _add_sampling_config(config2, project_ids=[project_id1], sample_rate=1)

    # First
    # send trace with project_id1 context (should be removed)
    envelope = Envelope()
    transaction, trace_id = _create_transaction_item()
    envelope.add_transaction(transaction)
    _add_trace_info(envelope, trace_id=trace_id, public_key=public_key1)

    # send the event, the transaction should be removed.
    relay.send_envelope(project_id2, envelope)
    # the event should be removed by Relay sampling
    with pytest.raises(queue.Empty):
        mini_sentry.captured_events.get(timeout=1)

    # and it should create an outcome
    outcomes = mini_sentry.captured_outcomes.get(timeout=2)
    assert outcomes is not None

    # Second
    # send trace with project_id2 context (should go through)
    envelope = Envelope()
    transaction, trace_id = _create_transaction_item()
    envelope.add_transaction(transaction)
    _add_trace_info(envelope, trace_id=trace_id, public_key=public_key2)

    # send the event.
    relay.send_envelope(project_id1, envelope)

    # the event should be passed along to upstream (with the transaction unchanged)
    evt = mini_sentry.captured_events.get(timeout=1).get_transaction_event()
    assert evt is not None

    # no outcome should be generated (since the event is passed along to the upstream)
    with pytest.raises(queue.Empty):
        mini_sentry.captured_outcomes.get(timeout=2)
Example #15
0
def _create_transaction_envelope(public_key):
    envelope = Envelope()
    transaction, trace_id, event_id = _create_transaction_item()
    envelope.add_transaction(transaction)
    _add_trace_info(envelope, trace_id=trace_id, public_key=public_key)
    return envelope, trace_id, event_id
Example #16
0
    def capture_event(
            self,
            event,  # type: Event
            hint=None,  # type: Optional[Hint]
            scope=None,  # type: Optional[Scope]
    ):
        # type: (...) -> Optional[str]
        """Captures an event.

        :param event: A ready-made event that can be directly sent to Sentry.

        :param hint: Contains metadata about the event that can be read from `before_send`, such as the original exception object or a HTTP request object.

        :returns: An event ID. May be `None` if there is no DSN set or of if the SDK decided to discard the event for other reasons. In such situations setting `debug=True` on `init()` may help.
        """
        if disable_capture_event.get(False):
            return None

        if self.transport is None:
            return None
        if hint is None:
            hint = {}
        event_id = event.get("event_id")
        hint = dict(hint or ())  # type: Hint

        if event_id is None:
            event["event_id"] = event_id = uuid.uuid4().hex
        if not self._should_capture(event, hint, scope):
            return None

        event_opt = self._prepare_event(event, hint, scope)
        if event_opt is None:
            return None

        # whenever we capture an event we also check if the session needs
        # to be updated based on that information.
        session = scope._session if scope else None
        if session:
            self._update_session_from_event(session, event)

        is_transaction = event_opt.get("type") == "transaction"

        if not is_transaction and not self._should_sample_error(event):
            return None

        attachments = hint.get("attachments")

        # this is outside of the `if` immediately below because even if we don't
        # use the value, we want to make sure we remove it before the event is
        # sent
        raw_tracestate = (event_opt.get("contexts",
                                        {}).get("trace",
                                                {}).pop("tracestate", ""))

        # Transactions or events with attachments should go to the /envelope/
        # endpoint.
        if is_transaction or attachments:

            headers = {
                "event_id": event_opt["event_id"],
                "sent_at": format_timestamp(datetime.utcnow()),
            }

            tracestate_data = raw_tracestate and reinflate_tracestate(
                raw_tracestate.replace("sentry=", ""))
            if tracestate_data and has_tracestate_enabled():
                headers["trace"] = tracestate_data

            envelope = Envelope(headers=headers)

            if is_transaction:
                envelope.add_transaction(event_opt)
            else:
                envelope.add_event(event_opt)

            for attachment in attachments or ():
                envelope.add_item(attachment.to_envelope_item())
            self.transport.capture_envelope(envelope)
        else:
            # All other events go to the /store/ endpoint.
            self.transport.capture_event(event_opt)
        return event_id