relay_event_schema/protocol/
trace_metric.rs1use relay_protocol::{
2 Annotated, Empty, FromValue, Getter, IntoValue, Object, SkipSerialization, Value,
3};
4use std::fmt::{self, Display};
5
6use relay_base_schema::metrics::MetricUnit;
7use serde::{Serialize, Serializer};
8
9use crate::processor::ProcessValue;
10use crate::protocol::{Attributes, SpanId, Timestamp, TraceId};
11
12#[derive(Clone, Debug, Default, PartialEq, Empty, FromValue, IntoValue, ProcessValue)]
13#[metastructure(process_func = "process_trace_metric", value_type = "TraceMetric")]
14pub struct TraceMetric {
15 #[metastructure(required = true)]
17 pub timestamp: Annotated<Timestamp>,
18
19 #[metastructure(required = true, trim = false)]
21 pub trace_id: Annotated<TraceId>,
22
23 #[metastructure(required = false, trim = false)]
25 pub span_id: Annotated<SpanId>,
26
27 #[metastructure(required = true, trim = false)]
29 pub name: Annotated<String>,
30
31 #[metastructure(required = true, field = "type")]
33 pub ty: Annotated<MetricType>,
34
35 #[metastructure(required = false)]
37 pub unit: Annotated<MetricUnit>,
38
39 #[metastructure(pii = "maybe", required = true, trim = false)]
43 pub value: Annotated<Value>,
44
45 #[metastructure(pii = "maybe", trim = false)]
47 pub attributes: Annotated<Attributes>,
48
49 #[metastructure(additional_properties, retain = false)]
51 pub other: Object<Value>,
52}
53
54impl Getter for TraceMetric {
55 fn get_value(&self, path: &str) -> Option<relay_protocol::Val<'_>> {
56 Some(match path.strip_prefix("trace_metric.")? {
57 "name" => self.name.as_str()?.into(),
58 path => {
59 if let Some(key) = path.strip_prefix("attributes.") {
60 let key = key.strip_suffix(".value")?;
61 self.attributes.value()?.get_value(key)?.into()
62 } else {
63 return None;
64 }
65 }
66 })
67 }
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub enum MetricType {
72 Gauge,
74 Distribution,
76 Counter,
78 Unknown(String),
80}
81
82impl MetricType {
83 fn as_str(&self) -> &str {
84 match self {
85 MetricType::Gauge => "gauge",
86 MetricType::Distribution => "distribution",
87 MetricType::Counter => "counter",
88 MetricType::Unknown(s) => s.as_str(),
89 }
90 }
91}
92
93impl Display for MetricType {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 write!(f, "{}", self.as_str())
96 }
97}
98
99impl From<String> for MetricType {
100 fn from(value: String) -> Self {
101 match value.as_str() {
102 "gauge" => MetricType::Gauge,
103 "distribution" => MetricType::Distribution,
104 "counter" => MetricType::Counter,
105 _ => MetricType::Unknown(value),
106 }
107 }
108}
109
110impl FromValue for MetricType {
111 fn from_value(value: Annotated<Value>) -> Annotated<Self> {
112 match String::from_value(value) {
113 Annotated(Some(value), meta) => Annotated(Some(value.into()), meta),
114 Annotated(None, meta) => Annotated(None, meta),
115 }
116 }
117}
118
119impl IntoValue for MetricType {
120 fn into_value(self) -> Value {
121 Value::String(self.to_string())
122 }
123
124 fn serialize_payload<S>(&self, s: S, _behavior: SkipSerialization) -> Result<S::Ok, S::Error>
125 where
126 Self: Sized,
127 S: Serializer,
128 {
129 Serialize::serialize(self.as_str(), s)
130 }
131}
132
133impl ProcessValue for MetricType {}
134
135impl Empty for MetricType {
136 #[inline]
137 fn is_empty(&self) -> bool {
138 false
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use relay_protocol::SerializableAnnotated;
146
147 #[test]
148 fn test_trace_metric_serialization() {
149 let json = r#"{
150 "timestamp": 1544719860.0,
151 "trace_id": "5b8efff798038103d269b633813fc60c",
152 "span_id": "eee19b7ec3c1b174",
153 "name": "http.request.duration",
154 "type": "distribution",
155 "value": 123.45,
156 "attributes": {
157 "http.method": {
158 "value": "GET",
159 "type": "string"
160 },
161 "http.status_code": {
162 "value": "200",
163 "type": "integer"
164 }
165 }
166 }"#;
167
168 let data = Annotated::<TraceMetric>::from_json(json).unwrap();
169
170 insta::assert_json_snapshot!(SerializableAnnotated(&data), @r###"
171 {
172 "timestamp": 1544719860.0,
173 "trace_id": "5b8efff798038103d269b633813fc60c",
174 "span_id": "eee19b7ec3c1b174",
175 "name": "http.request.duration",
176 "type": "distribution",
177 "value": 123.45,
178 "attributes": {
179 "http.method": {
180 "type": "string",
181 "value": "GET"
182 },
183 "http.status_code": {
184 "type": "integer",
185 "value": "200"
186 }
187 }
188 }
189 "###);
190 }
191}