Skip to content

Commit f338232

Browse files
authored
feat(config): Only propagate known feature flags (#2256)
This PR * reverts #2040, * ensures that feature flags are only propagated to downstream Relays if they are known, * removes unknown features on deserialization, * fixes a bug in #2250, which did not deserialize a new feature flag correctly.
1 parent 5585240 commit f338232

File tree

5 files changed

+72
-44
lines changed

5 files changed

+72
-44
lines changed

relay-dynamic-config/src/feature.rs

+50-31
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,79 @@
1-
use std::borrow::Cow;
1+
use std::collections::BTreeSet;
22

33
use serde::{Deserialize, Serialize};
44

55
/// Features exposed by project config.
6-
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
6+
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
77
pub enum Feature {
88
/// Enables ingestion of Session Replays (Replay Recordings and Replay Events).
9+
#[serde(rename = "organizations:session-replay")]
910
SessionReplay,
1011
/// Enables data scrubbing of replay recording payloads.
12+
#[serde(rename = "organizations:session-replay-recording-scrubbing")]
1113
SessionReplayRecordingScrubbing,
1214
/// Enables device.class synthesis
1315
///
1416
/// Enables device.class tag synthesis on mobile events.
17+
#[serde(rename = "organizations:device-class-synthesis")]
1518
DeviceClassSynthesis,
1619
/// Enables metric extraction from spans.
20+
#[serde(rename = "projects:span-metrics-extraction")]
1721
SpanMetricsExtraction,
1822
/// Apply transaction normalization rules to transactions from legacy SDKs.
23+
#[serde(rename = "organizations:transaction-name-normalize-legacy")]
1924
NormalizeLegacyTransactions,
25+
26+
/// Deprecated, still forwarded for older downstream Relays.
27+
#[serde(rename = "organizations:transaction-name-mark-scrubbed-as-sanitized")]
28+
Deprecated1,
29+
/// Deprecated, still forwarded for older downstream Relays.
30+
#[serde(rename = "organizations:transaction-name-normalize")]
31+
Deprecated2,
32+
/// Deprecated, still forwarded for older downstream Relays.
33+
#[serde(rename = "organizations:profiling")]
34+
Deprecated3,
35+
2036
/// Forward compatibility.
21-
Unknown(String),
37+
#[serde(other)]
38+
Unknown,
39+
}
40+
41+
/// A set of [`Feature`]s.
42+
#[derive(Clone, Debug, Default, Serialize, PartialEq, Eq)]
43+
pub struct FeatureSet(pub BTreeSet<Feature>);
44+
45+
impl FeatureSet {
46+
/// Returns `true` if the set of features is empty.
47+
pub fn is_empty(&self) -> bool {
48+
self.0.is_empty()
49+
}
2250
}
2351

24-
impl<'de> Deserialize<'de> for Feature {
52+
impl<'de> Deserialize<'de> for FeatureSet {
2553
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
2654
where
2755
D: serde::Deserializer<'de>,
2856
{
29-
let feature_name = Cow::<str>::deserialize(deserializer)?;
30-
Ok(match feature_name.as_ref() {
31-
"organizations:session-replay" => Feature::SessionReplay,
32-
"organizations:session-replay-recording-scrubbing" => {
33-
Feature::SessionReplayRecordingScrubbing
34-
}
35-
"organizations:device-class-synthesis" => Feature::DeviceClassSynthesis,
36-
"projects:span-metrics-extraction" => Feature::SpanMetricsExtraction,
37-
_ => Feature::Unknown(feature_name.to_string()),
38-
})
57+
let mut set = BTreeSet::<Feature>::deserialize(deserializer)?;
58+
set.remove(&Feature::Unknown);
59+
Ok(Self(set))
3960
}
4061
}
62+
#[cfg(test)]
63+
mod tests {
64+
use super::*;
4165

42-
impl Serialize for Feature {
43-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
44-
where
45-
S: serde::Serializer,
46-
{
47-
serializer.serialize_str(match self {
48-
Feature::SessionReplay => "organizations:session-replay",
49-
Feature::SessionReplayRecordingScrubbing => {
50-
"organizations:session-replay-recording-scrubbing"
51-
}
52-
Feature::DeviceClassSynthesis => "organizations:device-class-synthesis",
53-
Feature::SpanMetricsExtraction => "projects:span-metrics-extraction",
54-
Feature::NormalizeLegacyTransactions => {
55-
"organizations:transaction-name-normalize-legacy"
56-
}
57-
Feature::Unknown(s) => s,
58-
})
66+
#[test]
67+
fn roundtrip() {
68+
let features: FeatureSet =
69+
serde_json::from_str(r#"["organizations:session-replay", "foo"]"#).unwrap();
70+
assert_eq!(
71+
&features,
72+
&FeatureSet(BTreeSet::from([Feature::SessionReplay]))
73+
);
74+
assert_eq!(
75+
serde_json::to_string(&features).unwrap(),
76+
r#"["organizations:session-replay"]"#
77+
);
5978
}
6079
}

relay-dynamic-config/src/project.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@ use relay_sampling::SamplingConfig;
1212
use serde::{Deserialize, Serialize};
1313
use serde_json::Value;
1414

15-
use crate::feature::Feature;
1615
use crate::metrics::{
1716
MetricExtractionConfig, SessionMetricsConfig, TaggingRule, TransactionMetricsConfig,
1817
};
19-
use crate::ErrorBoundary;
18+
use crate::{ErrorBoundary, FeatureSet};
2019

2120
/// Dynamic, per-DSN configuration passed down from Sentry.
2221
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -68,8 +67,8 @@ pub struct ProjectConfig {
6867
#[serde(skip_serializing_if = "Vec::is_empty")]
6968
pub metric_conditional_tagging: Vec<TaggingRule>,
7069
/// Exposable features enabled for this project.
71-
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
72-
pub features: BTreeSet<Feature>,
70+
#[serde(skip_serializing_if = "FeatureSet::is_empty")]
71+
pub features: FeatureSet,
7372
/// Transaction renaming rules.
7473
#[serde(skip_serializing_if = "Vec::is_empty")]
7574
pub tx_name_rules: Vec<TransactionNameRule>,
@@ -100,7 +99,7 @@ impl Default for ProjectConfig {
10099
metric_extraction: Default::default(),
101100
span_attributes: BTreeSet::new(),
102101
metric_conditional_tagging: Vec::new(),
103-
features: BTreeSet::new(),
102+
features: Default::default(),
104103
tx_name_rules: Vec::new(),
105104
tx_name_ready: false,
106105
span_description_rules: None,
@@ -143,8 +142,8 @@ pub struct LimitedProjectConfig {
143142
pub measurements: Option<MeasurementsConfig>,
144143
#[serde(skip_serializing_if = "Option::is_none")]
145144
pub breakdowns_v2: Option<BreakdownsConfig>,
146-
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
147-
pub features: BTreeSet<Feature>,
145+
#[serde(skip_serializing_if = "FeatureSet::is_empty")]
146+
pub features: FeatureSet,
148147
#[serde(skip_serializing_if = "Vec::is_empty")]
149148
pub tx_name_rules: Vec<TransactionNameRule>,
150149
/// Whether or not a project is ready to mark all URL transactions as "sanitized".

relay-dynamic-config/src/utils.rs

+12-2
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,20 @@ mod tests {
6262
"json atom at path \".b.other\" is missing from rhs",
6363
),
6464
(
65-
// An unknown feature flag for this relay is still serialized
66-
// for the following relays in chain.
65+
// An unknown feature flag will not be serialized by Relay.
6766
r#"{"a": 1, "features": ["organizations:is-cool"]}"#,
6867
true,
68+
r#"json atoms at path ".features[0]" are not equal:
69+
lhs:
70+
"organizations:is-cool"
71+
rhs:
72+
"Unknown""#,
73+
),
74+
(
75+
// A deprecated feature flag for this relay is still serialized
76+
// for the following relays in chain.
77+
r#"{"a": 1, "features": ["organizations:profiling"]}"#,
78+
true,
6979
"",
7080
),
7181
] {

relay-server/src/actors/processor.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2097,9 +2097,9 @@ impl EnvelopeProcessorService {
20972097
return Ok(());
20982098
}
20992099

2100-
let extract_spans_metrics = project_config
2101-
.features
2102-
.contains(&Feature::SpanMetricsExtraction);
2100+
let extract_spans_metrics = state
2101+
.project_state
2102+
.has_feature(Feature::SpanMetricsExtraction);
21032103

21042104
let transaction_from_dsc = state
21052105
.managed_envelope

relay-server/src/actors/project.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ impl ProjectState {
336336
}
337337

338338
pub fn has_feature(&self, feature: Feature) -> bool {
339-
self.config.features.contains(&feature)
339+
self.config.features.0.contains(&feature)
340340
}
341341
}
342342

0 commit comments

Comments
 (0)