Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(spans): Ingest standalone spans #2620

Merged
merged 69 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 68 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
559cb77
feat(otel): Add an endpoint to ingest OpenTelemetry spans
phacops Oct 17, 2023
4b09e95
Move process_spans under the processing macro
phacops Oct 17, 2023
8380bfc
Merge branch 'master' into pierre/spans-ingest-otel-schema
phacops Oct 17, 2023
c86a98d
Ingest a real OTLP span schema
phacops Oct 18, 2023
a704859
Handle parent_span_id and segment_id
phacops Oct 18, 2023
24bca6b
Emit OtelSpan from the HTTP handler and parse them when processing
phacops Oct 19, 2023
05fda20
Filter spans if the feature is not active
phacops Oct 19, 2023
8302275
Parse attributes' values with the right type
phacops Oct 19, 2023
319ab23
Use default value instead of Option
phacops Oct 20, 2023
3b00b09
Add data and status fields
phacops Oct 20, 2023
7e44fb0
Reorganize the crate
phacops Oct 20, 2023
7ed09c6
Add docs auto-generation
phacops Oct 20, 2023
1f6f25c
Add a link to the schema we're implementing
phacops Oct 20, 2023
77bc3c5
Omit EventId as it's set later on if needed
phacops Oct 20, 2023
517c807
Move span related code right after the span struct
phacops Oct 20, 2023
30b4f13
Remove unwanted Option
phacops Oct 20, 2023
dc9f603
Use better error messages
phacops Oct 20, 2023
6ddebe0
Rename feature flag
phacops Oct 20, 2023
fe76300
Remove unneeded structs
phacops Oct 20, 2023
74c93d6
Use macro to access map value
phacops Oct 20, 2023
28cc43d
Add documentation
phacops Oct 20, 2023
7f63d7c
Fix links
phacops Oct 20, 2023
fa4e1ca
Add more documentation and use default where appropriate
phacops Oct 20, 2023
b196ac7
Fix link and remove examples trying to be parsed during tests
phacops Oct 20, 2023
21d5db9
Merge branch 'master' into pierre/spans-ingest-otel-schema
phacops Nov 6, 2023
1831a2b
Populate the received field
phacops Nov 6, 2023
b94d80f
Convert OTel attributes to Sentry tags
phacops Nov 6, 2023
ce73994
Add a CHANGELOG entry
phacops Nov 6, 2023
1e1f3f3
Merge branch 'master' into pierre/spans-ingest-otel-schema
phacops Nov 6, 2023
aa8874a
Simplify span handling
phacops Nov 9, 2023
8de70ef
Add missing entry
phacops Nov 13, 2023
3cb8069
Rename Span to OtelSpan
phacops Nov 13, 2023
cbd6676
Merge branch 'master' into pierre/spans-ingest-otel-schema
phacops Nov 13, 2023
da542e9
Extract metrics from standalone spans only
phacops Nov 27, 2023
d0356bc
Merge branch 'master' into pierre/spans-ingest-otel-schema
phacops Nov 27, 2023
f68651d
Accept EventSpan as well
phacops Nov 27, 2023
d6e7b60
Validate timestamps and IDs on the span
phacops Nov 27, 2023
51c6ebf
Fix EventSpan support
phacops Nov 27, 2023
7f8157f
Fix import
phacops Nov 27, 2023
0b7beeb
Make sure everything is behind the feature flag
phacops Nov 27, 2023
36904cc
Remove todos on attribute value handling
phacops Nov 27, 2023
f812d01
Merge branch 'master' into pierre/spans-ingest-otel-schema
phacops Nov 27, 2023
e932262
test: integration
jjbayer Nov 27, 2023
648b921
Revert EventSpan addition
phacops Nov 27, 2023
210504a
Streamline deserialization, normalization and metrics extraction
phacops Nov 27, 2023
e8dc41a
Fix import
phacops Nov 27, 2023
a7c6608
Do not return if the config is disabled
phacops Nov 27, 2023
5900d0f
Add exclusive_time to spans in ingestion integration tests
phacops Nov 27, 2023
99bc5d3
Allow metrics extraction
phacops Nov 27, 2023
7c30bed
Add what the metric is supposed to look like
phacops Nov 28, 2023
319e50e
metrics test
jjbayer Nov 28, 2023
9877032
Read exclusive_time from an attribute
phacops Nov 28, 2023
9025360
Merge remote-tracking branch 'origin/master' into pierre/spans-ingest…
phacops Nov 28, 2023
07e4a67
Refacor span processing into its own file
phacops Nov 28, 2023
7fb5b42
Add exclusive_time attributes to span fixtures
phacops Nov 28, 2023
963c72b
Set received and is_segment for both OTel and Event spans
phacops Nov 28, 2023
0b17b26
Remove unused import
phacops Nov 28, 2023
2a3c80f
Fix test
phacops Nov 29, 2023
b4fbe2a
feat(spans): Normalization for span ingestion (#2777)
jjbayer Nov 29, 2023
3169524
Update CHANGELOG.md
phacops Nov 29, 2023
1d5f2eb
Make NormalizeSpanConfig private
phacops Nov 29, 2023
cefa650
Merge branch 'master' into pierre/spans-ingest-otel-schema
phacops Nov 29, 2023
c3566cf
Move all public functions to the top of the file
phacops Nov 29, 2023
6dcaecd
Less clone, more perform
phacops Nov 29, 2023
2c5643d
Use attributes to get exclusive time or default to duration
phacops Nov 29, 2023
8e95698
Parse a proper OTel trace on HTTP ingestion
phacops Nov 29, 2023
69e2acc
Merge branch 'master' into pierre/spans-ingest-otel-schema
phacops Nov 29, 2023
59fcb53
ref: fewer clones
jjbayer Nov 30, 2023
71bd7e6
Merge branch 'master' into pierre/spans-ingest-otel-schema
phacops Nov 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

**Features**:

- Ingest OpenTelemetry and standalone Sentry spans via HTTP or an envelope. ([#2620](https://github.com/getsentry/relay/pull/2620))
- Partition and split metric buckets just before sending. Log outcomes for metrics. ([#2682](https://github.com/getsentry/relay/pull/2682))
- Return global config ready status to downstream relays. ([#2765](https://github.com/getsentry/relay/pull/2765))
- Add Mixed JS/Android Profiles events processing. ([#2706](https://github.com/getsentry/relay/pull/2706))
Expand Down
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions relay-dynamic-config/src/feature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ pub enum Feature {
/// Enable processing profiles
#[serde(rename = "organizations:profiling")]
Profiling,
/// Enable standalone span ingestion.
#[serde(rename = "organizations:standalone-span-ingestion")]
StandaloneSpanIngestion,
/// Enable metric metadata.
#[serde(rename = "organizations:metric-meta")]
MetricMeta,
Expand Down
4 changes: 4 additions & 0 deletions relay-event-normalization/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ mod trimming;
pub mod replay;
pub use normalize::breakdowns::*;
pub use normalize::*;
pub use remove_other::RemoveOtherProcessor;
pub use schema::SchemaProcessor;
pub use timestamp::TimestampProcessor;
pub use transactions::*;
pub use trimming::TrimmingProcessor;
pub use user_agent::*;

pub use self::clock_drift::*;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ impl std::fmt::Display for RenderBlockingStatus {
}

/// Configuration for span tag extraction.
pub(crate) struct Config {
pub struct Config {
/// The maximum allowed size of tag values in bytes. Longer values will be cropped.
pub max_tag_value_size: usize,
}
Expand Down Expand Up @@ -216,7 +216,7 @@ pub fn extract_shared_tags(event: &Event) -> BTreeMap<SpanTagKey, String> {
/// [span operations](https://develop.sentry.dev/sdk/performance/span-operations/) and
/// existing [span data](https://develop.sentry.dev/sdk/performance/span-data-conventions/) fields,
/// and rely on Sentry conventions and heuristics.
pub(crate) fn extract_tags(
pub fn extract_tags(
span: &Span,
config: &Config,
initial_display: Option<Timestamp>,
Expand Down
1 change: 1 addition & 0 deletions relay-event-normalization/src/remove_other.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fn create_errors(other: &mut Object<Value>) {
}
}

/// Removes unknown, internal and deprecated fields from a payload.
pub struct RemoveOtherProcessor;

impl Processor for RemoveOtherProcessor {
Expand Down
1 change: 1 addition & 0 deletions relay-event-normalization/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use relay_event_schema::processor::{
};
use relay_protocol::{Array, Empty, Error, ErrorKind, Meta, Object};

/// Validates constraints such as empty strings or arrays and invalid characters.
pub struct SchemaProcessor;

impl Processor for SchemaProcessor {
Expand Down
2 changes: 2 additions & 0 deletions relay-event-normalization/src/trimming.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ struct BagSizeState {
size_remaining: usize,
}

/// Limits data bags to a maximum size and depth.
#[derive(Default)]
pub struct TrimmingProcessor {
bag_size_state: Vec<BagSizeState>,
}

impl TrimmingProcessor {
/// Creates a new trimming processor.
pub fn new() -> Self {
Self::default()
}
Expand Down
1 change: 1 addition & 0 deletions relay-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ relay-quotas = { path = "../relay-quotas" }
relay-redis = { path = "../relay-redis" }
relay-replays = { path = "../relay-replays" }
relay-sampling = { path = "../relay-sampling" }
relay-spans = { path = "../relay-spans" }
relay-statsd = { path = "../relay-statsd" }
relay-system = { path = "../relay-system" }
reqwest = { version = "0.11.1", features = [
Expand Down
174 changes: 5 additions & 169 deletions relay-server/src/actors/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ use tokio::sync::Semaphore;
use {
crate::actors::project_cache::UpdateRateLimits,
crate::utils::{EnvelopeLimiter, ItemAction, MetricsLimiter},
relay_event_normalization::{span, StoreConfig, StoreProcessor},
relay_event_schema::protocol::Span,
relay_event_normalization::{StoreConfig, StoreProcessor},
relay_metrics::{Aggregator, RedisMetricMetaStore},
relay_quotas::{RateLimitingError, RedisRateLimiter},
relay_redis::RedisPool,
Expand Down Expand Up @@ -73,6 +72,7 @@ mod profile;
mod replay;
mod report;
mod session;
mod span;

/// The minimum clock drift for correction to apply.
const MINIMUM_CLOCK_DRIFT: Duration = Duration::from_secs(55 * 60);
Expand Down Expand Up @@ -1123,172 +1123,6 @@ impl EnvelopeProcessorService {
Ok(())
}

#[cfg(feature = "processing")]
fn is_span_allowed(&self, span: &Span) -> bool {
let Some(op) = span.op.value() else {
return false;
};
let Some(description) = span.description.value() else {
return false;
};
let system: &str = span
.data
.value()
.and_then(|v| v.get("span.system"))
.and_then(|system| system.as_str())
.unwrap_or_default();
op.contains("resource.script")
|| op.contains("resource.css")
|| op == "http.client"
|| op.starts_with("app.")
|| op.starts_with("ui.load")
|| op.starts_with("file")
|| op.starts_with("db")
&& !(op.contains("clickhouse")
|| op.contains("mongodb")
|| op.contains("redis")
|| op.contains("compiler"))
&& !(op == "db.sql.query" && (description.contains("\"$") || system == "mongodb"))
}

#[cfg(feature = "processing")]
fn extract_spans(&self, state: &mut ProcessEnvelopeState) {
// For now, drop any spans submitted by the SDK.
state.managed_envelope.retain_items(|item| match item.ty() {
ItemType::Span => ItemAction::DropSilently,
_ => ItemAction::Keep,
});

// Only extract spans from transactions (not errors).
if state.event_type() != Some(EventType::Transaction) {
return;
};

// Check feature flag.
if !state
.project_state
.has_feature(Feature::SpanMetricsExtraction)
{
return;
};

let mut add_span = |span: Annotated<Span>| {
let span = match self.validate_span(span) {
Ok(span) => span,
Err(e) => {
relay_log::error!("Invalid span: {e}");
return;
}
};
let span = match span.to_json() {
Ok(span) => span,
Err(e) => {
relay_log::error!(error = &e as &dyn Error, "Failed to serialize span");
return;
}
};
let mut item = Item::new(ItemType::Span);
item.set_payload(ContentType::Json, span);
state.managed_envelope.envelope_mut().add_item(item);
};

let Some(event) = state.event.value() else {
return;
};

// Extract transaction as a span.
let mut transaction_span: Span = event.into();

let all_modules_enabled = state
.project_state
.has_feature(Feature::SpanMetricsExtractionAllModules);

// Add child spans as envelope items.
if let Some(child_spans) = event.spans.value() {
for span in child_spans {
let Some(inner_span) = span.value() else {
continue;
};
// HACK: filter spans based on module until we figure out grouping.
if !all_modules_enabled && !self.is_span_allowed(inner_span) {
continue;
}
// HACK: clone the span to set the segment_id. This should happen
// as part of normalization once standalone spans reach wider adoption.
let mut new_span = inner_span.clone();
new_span.is_segment = Annotated::new(false);
new_span.received = transaction_span.received.clone();
new_span.segment_id = transaction_span.segment_id.clone();

// If a profile is associated with the transaction, also associate it with its
// child spans.
new_span.profile_id = transaction_span.profile_id.clone();

add_span(Annotated::new(new_span));
}
}

// Extract tags to add to this span as well
let shared_tags = span::tag_extraction::extract_shared_tags(event);
transaction_span.sentry_tags = Annotated::new(
shared_tags
.clone()
.into_iter()
.map(|(k, v)| (k.sentry_tag_key().to_owned(), Annotated::new(v)))
.collect(),
);
add_span(transaction_span.into());
}

/// Helper for [`Self::extract_spans`].
///
/// We do not extract spans with missing fields if those fields are required on the Kafka topic.
#[cfg(feature = "processing")]
fn validate_span(&self, mut span: Annotated<Span>) -> Result<Annotated<Span>, anyhow::Error> {
let inner = span
.value_mut()
.as_mut()
.ok_or(anyhow::anyhow!("empty span"))?;
let Span {
ref exclusive_time,
ref mut tags,
ref mut sentry_tags,
..
} = inner;
// The following required fields are already validated by the `TransactionsProcessor`:
// - `timestamp`
// - `start_timestamp`
// - `trace_id`
// - `span_id`
//
// `is_segment` is set by `extract_span`.
exclusive_time
.value()
.ok_or(anyhow::anyhow!("missing exclusive_time"))?;

if let Some(sentry_tags) = sentry_tags.value_mut() {
sentry_tags.retain(|key, value| match value.value() {
Some(s) => {
match key.as_str() {
"group" => {
// Only allow up to 16-char hex strings in group.
s.len() <= 16 && s.chars().all(|c| c.is_ascii_hexdigit())
}
"status_code" => s.parse::<u16>().is_ok(),
_ => true,
}
}
// Drop empty string values.
None => false,
});
}
if let Some(tags) = tags.value_mut() {
tags.retain(|_, value| !value.value().is_empty())
}

Ok(span)
}

/// Computes the sampling decision on the incoming event
fn run_dynamic_sampling(&self, state: &mut ProcessEnvelopeState) {
// Running dynamic sampling involves either:
Expand Down Expand Up @@ -1550,6 +1384,7 @@ impl EnvelopeProcessorService {
);
replay::process(state, self.inner.config.clone())?;
profile::filter(state);
span::filter(state);

if state.creates_event() {
// Some envelopes only create events in processing relays; for example, unreal events.
Expand Down Expand Up @@ -1591,13 +1426,14 @@ impl EnvelopeProcessorService {
self.enforce_quotas(state)?;
profile::process(state, self.inner.config.clone());
self.process_check_ins(state);
span::process(state, self.inner.config.clone());
});

if state.has_event() {
self.scrub_event(state)?;
self.serialize_event(state)?;
if_processing!({
self.extract_spans(state);
span::extract_from_event(state);
});
}

Expand Down
1 change: 1 addition & 0 deletions relay-server/src/actors/processor/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ fn is_duplicate(item: &Item, processing_enabled: bool) -> bool {
ItemType::ReplayRecording => false,
ItemType::CheckIn => false,
ItemType::Span => false,
ItemType::OtelSpan => false,

// Without knowing more, `Unknown` items are allowed to be repeated
ItemType::Unknown(_) => false,
Expand Down
Loading
Loading