relay_event_schema/protocol/
trace_metric.rs1use relay_protocol::{
2 Annotated, Empty, FromValue, Getter, IntoValue, Object, SkipSerialization, Value,
3};
4use std::collections::BTreeMap;
5use std::fmt::{self, Display};
6
7use relay_base_schema::metrics::MetricUnit;
8use serde::{Deserialize, Serialize, Serializer};
9
10use crate::processor::ProcessValue;
11use crate::protocol::{Attributes, SpanId, Timestamp, TraceId};
12
13#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
14#[metastructure(process_func = "process_trace_metric", value_type = "TraceMetric")]
15pub struct TraceMetric {
16 #[metastructure(required = true)]
18 pub timestamp: Annotated<Timestamp>,
19
20 #[metastructure(required = true, trim = false)]
22 pub trace_id: Annotated<TraceId>,
23
24 #[metastructure(required = false, trim = false)]
26 pub span_id: Annotated<SpanId>,
27
28 #[metastructure(required = true, trim = false)]
30 pub name: Annotated<String>,
31
32 #[metastructure(required = true, field = "type")]
34 pub ty: Annotated<MetricType>,
35
36 #[metastructure(required = false)]
38 pub unit: Annotated<MetricUnit>,
39
40 #[metastructure(pii = "maybe", required = true, trim = false)]
44 pub value: Annotated<Value>,
45
46 #[metastructure(pii = "maybe", trim = false)]
48 pub attributes: Annotated<Attributes>,
49
50 #[metastructure(additional_properties, retain = false)]
52 pub other: Object<Value>,
53}
54
55#[derive(Clone, Debug, Default, Serialize, Deserialize)]
60pub struct TraceMetricHeader {
61 pub byte_size: Option<u64>,
66
67 #[serde(flatten)]
69 pub other: BTreeMap<String, Value>,
70}
71
72impl Getter for TraceMetric {
73 fn get_value(&self, path: &str) -> Option<relay_protocol::Val<'_>> {
74 Some(match path.strip_prefix("trace_metric.")? {
75 "name" => self.name.as_str()?.into(),
76 path => {
77 if let Some(key) = path.strip_prefix("attributes.") {
78 let key = key.strip_suffix(".value")?;
79 self.attributes.value()?.get_value(key)?.into()
80 } else {
81 return None;
82 }
83 }
84 })
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
89pub enum MetricType {
90 Gauge,
92 Distribution,
94 Counter,
96 Unknown(String),
98}
99
100impl MetricType {
101 fn as_str(&self) -> &str {
102 match self {
103 MetricType::Gauge => "gauge",
104 MetricType::Distribution => "distribution",
105 MetricType::Counter => "counter",
106 MetricType::Unknown(s) => s.as_str(),
107 }
108 }
109}
110
111impl Display for MetricType {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(f, "{}", self.as_str())
114 }
115}
116
117impl From<String> for MetricType {
118 fn from(value: String) -> Self {
119 match value.as_str() {
120 "gauge" => MetricType::Gauge,
121 "distribution" => MetricType::Distribution,
122 "counter" => MetricType::Counter,
123 _ => MetricType::Unknown(value),
124 }
125 }
126}
127
128impl FromValue for MetricType {
129 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
130 match String::from_value(value) {
131 Annotated(Some(value), meta) => Annotated(Some(value.into()), meta),
132 Annotated(None, meta) => Annotated(None, meta),
133 }
134 }
135}
136
137impl IntoValue for MetricType {
138 fn into_value(self) -> Value {
139 Value::String(self.to_string())
140 }
141
142 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
143 where
144 Self: Sized,
145 S: Serializer,
146 {
147 Serialize::serialize(self.as_str(), s)
148 }
149}
150
151impl ProcessValue for MetricType {}
152
153impl Empty for MetricType {
154 #[inline]
155 fn is_empty(&self) -> bool {
156 false
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163 use relay_protocol::SerializableAnnotated;
164
165 #[test]
166 fn test_trace_metric_serialization() {
167 let json = r#"{
168 "timestamp": 1544719860.0,
169 "trace_id": "5b8efff798038103d269b633813fc60c",
170 "span_id": "eee19b7ec3c1b174",
171 "name": "http.request.duration",
172 "type": "distribution",
173 "value": 123.45,
174 "attributes": {
175 "http.method": {
176 "value": "GET",
177 "type": "string"
178 },
179 "http.status_code": {
180 "value": "200",
181 "type": "integer"
182 }
183 }
184 }"#;
185
186 let data = Annotated::<TraceMetric>::from_json(json).unwrap();
187
188 insta::assert_json_snapshot!(SerializableAnnotated(&data), @r###"
189 {
190 "timestamp": 1544719860.0,
191 "trace_id": "5b8efff798038103d269b633813fc60c",
192 "span_id": "eee19b7ec3c1b174",
193 "name": "http.request.duration",
194 "type": "distribution",
195 "value": 123.45,
196 "attributes": {
197 "http.method": {
198 "type": "string",
199 "value": "GET"
200 },
201 "http.status_code": {
202 "type": "integer",
203 "value": "200"
204 }
205 }
206 }
207 "###);
208 }
209
210 #[test]
211 fn test_trace_metric_with_custom_unit_preserved() {
212 let json = r#"{
213 "timestamp": 1544719860.0,
214 "trace_id": "5b8efff798038103d269b633813fc60c",
215 "name": "custom.metric",
216 "type": "counter",
217 "value": 42,
218 "unit": "customunit"
219 }"#;
220
221 let data = Annotated::<TraceMetric>::from_json(json).unwrap();
222 let trace_metric = data.value().unwrap();
223
224 assert_eq!(
225 trace_metric.unit.value(),
226 Some(&MetricUnit::Custom("customunit".parse().unwrap()))
227 );
228 }
229}