Skip to content

Commit f877f22

Browse files
authored
fix(server): Remove dependent items from envelope when dropping transaction item (#960)
When transactions are removed from an envelope due to sampling attachments should also be removed but were left behind this PR fixes the problem.
1 parent 10f2c93 commit f877f22

File tree

3 files changed

+53
-69
lines changed

3 files changed

+53
-69
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
**Bug Fixes**:
66

77
- Make request url scrubbable. ([#955](https://github.com/getsentry/relay/pull/955))
8+
- Remove dependent items from envelope when dropping transaction item. ([#960](https://github.com/getsentry/relay/pull/960))
89

910
**Internal**:
1011

relay-server/src/utils/dynamic_sampling.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,16 @@ fn sample_transaction_internal(
8888

8989
let client_ip = envelope.meta().client_addr();
9090
if let SamplingResult::Drop(rule_id) = trace_context.should_keep(client_ip, sampling_config) {
91-
// TODO: Remove event-dependent items similar to `EnvelopeLimiter`.
92-
envelope.take_item_by(|item| item.ty() == ItemType::Transaction);
91+
// remove transaction and dependent items
92+
if envelope
93+
.take_item_by(|item| item.ty() == ItemType::Transaction)
94+
.is_some()
95+
{
96+
// we have removed the transaction from the envelope
97+
// also remove any dependent items (all items that require event need to go)
98+
envelope.retain_items(|item| !item.requires_event());
99+
}
100+
93101
if envelope.is_empty() {
94102
// if after we removed the transaction we ended up with an empty envelope
95103
// return an error so we can generate an outcome for the rule that dropped the transaction
@@ -249,9 +257,12 @@ mod tests {
249257
let item1 = Item::new(ItemType::Transaction);
250258
envelope.add_item(item1);
251259

252-
let item2 = Item::new(ItemType::Event);
260+
let item2 = Item::new(ItemType::Attachment);
253261
envelope.add_item(item2);
254262

263+
let item3 = Item::new(ItemType::Attachment);
264+
envelope.add_item(item3);
265+
255266
envelope
256267
}
257268

@@ -286,13 +297,17 @@ mod tests {
286297
/// Should remove transaction from envelope when a matching rule is detected
287298
fn test_should_drop_transaction() {
288299
//create an envelope with a event and a transaction
289-
let envelope = new_envelope(true);
300+
let mut envelope = new_envelope(true);
301+
// add an item that is not dependent on the transaction (i.e. will not be dropped with it)
302+
let session_item = Item::new(ItemType::Session);
303+
envelope.add_item(session_item);
304+
290305
let state = get_project_state(Some(0.0), RuleType::Trace);
291306

292307
let result = sample_transaction_internal(envelope, Some(&state), true);
293308
assert!(result.is_ok());
294309
let envelope = result.unwrap();
295-
// the transaction item should have been removed
310+
// the transaction item and dependent items should have been removed
296311
assert_eq!(envelope.len(), 1);
297312
}
298313

@@ -307,7 +322,7 @@ mod tests {
307322
assert!(result.is_ok());
308323
let envelope = result.unwrap();
309324
// both the event and the transaction item should have been left in the envelope
310-
assert_eq!(envelope.len(), 2);
325+
assert_eq!(envelope.len(), 3);
311326
}
312327

313328
#[test]
@@ -320,17 +335,15 @@ mod tests {
320335
assert!(result.is_ok());
321336
let envelope = result.unwrap();
322337
// both the event and the transaction item should have been left in the envelope
323-
assert_eq!(envelope.len(), 2);
338+
assert_eq!(envelope.len(), 3);
324339
}
325340

326341
#[test]
327342
/// When the envelope becomes empty due to sampling we should get back the rule that dropped the
328343
/// transaction
329344
fn test_should_signal_when_envelope_becomes_empty() {
330345
//create an envelope with a event and a transaction
331-
let mut envelope = new_envelope(true);
332-
//remove the event (so the envelope contains only a transaction
333-
envelope.take_item_by(|item| item.ty() == ItemType::Event);
346+
let envelope = new_envelope(true);
334347
let state = get_project_state(Some(0.0), RuleType::Trace);
335348

336349
let result = sample_transaction_internal(envelope, Some(&state), true);

tests/integration/test_dynamic_sampling.py

Lines changed: 29 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import uuid
22

33
import pytest
4-
from sentry_sdk.envelope import Envelope, Item
4+
from sentry_sdk.envelope import Envelope, Item, PayloadRef
55
import queue
66

77

@@ -462,16 +462,22 @@ def test_uses_trace_public_key(mini_sentry, relay):
462462
mini_sentry.captured_outcomes.get(timeout=2)
463463

464464

465-
def test_fast_path(mini_sentry, relay):
465+
@pytest.mark.parametrize(
466+
"rule_type, event_factory",
467+
[
468+
("error", _create_event_envelope),
469+
("transaction", _create_transaction_envelope),
470+
("trace", _create_transaction_envelope),
471+
],
472+
)
473+
def test_multi_item_envelope(mini_sentry, relay, rule_type, event_factory):
466474
"""
467-
Tests that the fast path works.
475+
Associated items are removed together with event item.
468476
469-
When the project config is already fetched and the envelope only
470-
contains a transaction a fast evaluation path is used.
477+
The event is sent twice to account for both fast and slow paths.
471478
472-
While this is an implementation detail of Relay that is not visible
473-
here we test that Relay behaves normally for the conditions that we
474-
know are going to trigger a fast-path evaluation
479+
When sampling decides to remove a transaction it should also remove all
480+
dependent items (attachments).
475481
"""
476482
project_id = 42
477483
relay = relay(mini_sentry, _outcomes_enabled_config())
@@ -480,14 +486,25 @@ def test_fast_path(mini_sentry, relay):
480486
config = mini_sentry.add_basic_project_config(project_id)
481487
# add a sampling rule to project config that removes all transactions (sample_rate=0)
482488
public_key = config["publicKeys"][0]["publicKey"]
483-
_add_sampling_config(config, sample_rate=0, rule_type="trace")
489+
490+
# add a sampling rule to project config that drops all events (sample_rate=0), it should be ignored
491+
# because there is an invalid rule in the configuration
492+
_add_sampling_config(config, sample_rate=0, rule_type=rule_type)
484493

485494
for i in range(2):
486495
# create an envelope with a trace context that is initiated by this project (for simplicity)
487496
envelope = Envelope()
488-
transaction, trace_id, event_id = _create_transaction_item()
489-
envelope.add_transaction(transaction)
490-
_add_trace_info(envelope, trace_id=trace_id, public_key=public_key)
497+
# create an envelope with a trace context that is initiated by this project (for simplicity)
498+
envelope, trace_id, event_id = event_factory(public_key)
499+
envelope.add_item(
500+
Item(payload=PayloadRef(json={"x": "some attachment"}), type="attachment")
501+
)
502+
envelope.add_item(
503+
Item(
504+
payload=PayloadRef(json={"y": "some other attachment"}),
505+
type="attachment",
506+
)
507+
)
491508

492509
# send the event, the transaction should be removed.
493510
relay.send_envelope(project_id, envelope)
@@ -497,50 +514,3 @@ def test_fast_path(mini_sentry, relay):
497514

498515
outcomes = mini_sentry.captured_outcomes.get(timeout=2)
499516
assert outcomes is not None
500-
501-
502-
def test_multi_item_envelope(mini_sentry, relay):
503-
"""
504-
Test that multi item envelopes are handled correctly.
505-
506-
Pass an envelope containing of a transaction that will be sampled and an event and
507-
test
508-
509-
510-
"""
511-
project_id = 42
512-
relay = relay(mini_sentry, _outcomes_enabled_config())
513-
514-
# create a basic project config
515-
config = mini_sentry.add_basic_project_config(project_id)
516-
# add a sampling rule to project config that removes all transactions (sample_rate=0)
517-
public_key = config["publicKeys"][0]["publicKey"]
518-
_add_sampling_config(config, sample_rate=0, rule_type="trace")
519-
520-
# we'll run the test twice to make sure that the fast path works as well
521-
for i in range(2):
522-
# create an envelope with a trace context that is initiated by this project (for simplicity)
523-
envelope = Envelope()
524-
transaction, trace_id, event_id = _create_transaction_item()
525-
envelope.add_transaction(transaction)
526-
envelope.add_event({"message": "Hello"})
527-
_add_trace_info(envelope, trace_id=trace_id, public_key=public_key)
528-
529-
# send the event, the transaction should be removed.
530-
relay.send_envelope(project_id, envelope)
531-
532-
msg = mini_sentry.captured_events.get(timeout=1)
533-
assert msg is not None
534-
transaction = msg.get_transaction_event()
535-
event = msg.get_event()
536-
537-
# check that the transaction was removed during the transaction sampling process
538-
assert transaction is None
539-
# but the event was kept
540-
assert event is not None
541-
assert event.setdefault("logentry", {}).get("formatted") == "Hello"
542-
543-
# no outcome should be generated since we forward the event to upstream
544-
# NOTE this might change in the future so we might get outcomes here in the future
545-
with pytest.raises(queue.Empty):
546-
mini_sentry.captured_outcomes.get(timeout=2)

0 commit comments

Comments
 (0)