@@ -10,8 +10,8 @@ use crate::Dsn;
1010use super :: v7 as protocol;
1111
1212use protocol:: {
13- Attachment , AttachmentType , ClientSdkInfo , DynamicSamplingContext , Event , Log , MonitorCheckIn ,
14- SessionAggregates , SessionUpdate , Transaction ,
13+ Attachment , AttachmentType , ClientSdkInfo , DynamicSamplingContext , Event , Log , Metric ,
14+ MonitorCheckIn , SessionAggregates , SessionUpdate , Transaction ,
1515} ;
1616
1717/// Raised if a envelope cannot be parsed from a given input.
@@ -127,6 +127,10 @@ enum EnvelopeItemType {
127127 /// A container of Log items.
128128 #[ serde( rename = "log" ) ]
129129 LogsContainer ,
130+ /// A container of Metric items.
131+ /// Serialized to a `trace_metric` envelope item.
132+ #[ serde( rename = "trace_metric" ) ]
133+ MetricsContainer ,
130134}
131135
132136/// An Envelope Item Header.
@@ -192,6 +196,8 @@ pub enum EnvelopeItem {
192196pub enum ItemContainer {
193197 /// A list of logs.
194198 Logs ( Vec < Log > ) ,
199+ /// A list of metrics.
200+ Metrics ( Vec < Metric > ) ,
195201}
196202
197203#[ allow( clippy:: len_without_is_empty, reason = "is_empty is not needed" ) ]
@@ -200,20 +206,23 @@ impl ItemContainer {
200206 pub fn len ( & self ) -> usize {
201207 match self {
202208 Self :: Logs ( logs) => logs. len ( ) ,
209+ Self :: Metrics ( metrics) => metrics. len ( ) ,
203210 }
204211 }
205212
206213 /// The `type` of this item container, which corresponds to the `type` of the contained items.
207214 pub fn ty ( & self ) -> & ' static str {
208215 match self {
209216 Self :: Logs ( _) => "log" ,
217+ Self :: Metrics ( _) => "trace_metric" ,
210218 }
211219 }
212220
213221 /// The `content-type` expected by Relay for this item container.
214222 pub fn content_type ( & self ) -> & ' static str {
215223 match self {
216224 Self :: Logs ( _) => "application/vnd.sentry.items.log+json" ,
225+ Self :: Metrics ( _) => "application/vnd.sentry.items.trace-metric+json" ,
217226 }
218227 }
219228}
@@ -235,6 +244,12 @@ struct ItemsSerdeWrapper<'a, T: Clone> {
235244 items : Cow < ' a , [ T ] > ,
236245}
237246
247+ impl From < Vec < Metric > > for ItemContainer {
248+ fn from ( metrics : Vec < Metric > ) -> Self {
249+ Self :: Metrics ( metrics)
250+ }
251+ }
252+
238253impl From < Event < ' static > > for EnvelopeItem {
239254 fn from ( event : Event < ' static > ) -> Self {
240255 EnvelopeItem :: Event ( event)
@@ -283,6 +298,12 @@ impl From<Vec<Log>> for EnvelopeItem {
283298 }
284299}
285300
301+ impl From < Vec < Metric > > for EnvelopeItem {
302+ fn from ( metrics : Vec < Metric > ) -> Self {
303+ EnvelopeItem :: ItemContainer ( metrics. into ( ) )
304+ }
305+ }
306+
286307/// An Iterator over the items of an Envelope.
287308#[ derive( Clone ) ]
288309pub struct EnvelopeItemIter < ' s > {
@@ -506,6 +527,12 @@ impl Envelope {
506527 let wrapper = ItemsSerdeWrapper { items : logs. into ( ) } ;
507528 serde_json:: to_writer ( & mut item_buf, & wrapper) ?
508529 }
530+ ItemContainer :: Metrics ( metrics) => {
531+ let wrapper = ItemsSerdeWrapper {
532+ items : metrics. into ( ) ,
533+ } ;
534+ serde_json:: to_writer ( & mut item_buf, & wrapper) ?
535+ }
509536 } ,
510537 EnvelopeItem :: Raw => {
511538 continue ;
@@ -677,6 +704,10 @@ impl Envelope {
677704 serde_json:: from_slice :: < ItemsSerdeWrapper < _ > > ( payload)
678705 . map ( |x| EnvelopeItem :: ItemContainer ( ItemContainer :: Logs ( x. items . into ( ) ) ) )
679706 }
707+ EnvelopeItemType :: MetricsContainer => {
708+ serde_json:: from_slice :: < ItemsSerdeWrapper < _ > > ( payload)
709+ . map ( |x| EnvelopeItem :: ItemContainer ( ItemContainer :: Metrics ( x. items . into ( ) ) ) )
710+ }
680711 }
681712 . map_err ( EnvelopeError :: InvalidItemPayload ) ?;
682713
@@ -708,6 +739,7 @@ mod test {
708739 use std:: time:: { Duration , SystemTime } ;
709740
710741 use protocol:: Map ;
742+ use serde_json:: Value ;
711743 use time:: format_description:: well_known:: Rfc3339 ;
712744 use time:: OffsetDateTime ;
713745
@@ -1121,6 +1153,49 @@ some content
11211153 assert_eq ! ( expected, serialized. as_bytes( ) ) ;
11221154 }
11231155
1156+ #[ test]
1157+ fn test_metric_container_header ( ) {
1158+ let metrics: EnvelopeItem = vec ! [ Metric {
1159+ r#type: protocol:: MetricType :: Counter ,
1160+ name: "api.requests" . into( ) ,
1161+ value: 1.0 ,
1162+ timestamp: timestamp( "2026-03-02T13:36:02.000Z" ) ,
1163+ trace_id: "335e53d614474acc9f89e632b776cc28" . parse( ) . unwrap( ) ,
1164+ span_id: None ,
1165+ unit: None ,
1166+ attributes: Map :: new( ) ,
1167+ } ]
1168+ . into ( ) ;
1169+
1170+ let mut envelope = Envelope :: new ( ) ;
1171+ envelope. add_item ( metrics) ;
1172+
1173+ let expected = [
1174+ serde_json:: json!( { } ) ,
1175+ serde_json:: json!( {
1176+ "type" : "trace_metric" ,
1177+ "item_count" : 1 ,
1178+ "content_type" : "application/vnd.sentry.items.trace-metric+json"
1179+ } ) ,
1180+ serde_json:: json!( {
1181+ "items" : [ {
1182+ "type" : "counter" ,
1183+ "name" : "api.requests" ,
1184+ "value" : 1.0 ,
1185+ "timestamp" : 1772458562 ,
1186+ "trace_id" : "335e53d614474acc9f89e632b776cc28"
1187+ } ]
1188+ } ) ,
1189+ ] ;
1190+
1191+ let serialized = to_str ( envelope) ;
1192+ let actual = serialized
1193+ . lines ( )
1194+ . map ( |line| serde_json:: from_str :: < Value > ( line) . expect ( "envelope has invalid JSON" ) ) ;
1195+
1196+ assert ! ( actual. eq( expected. into_iter( ) ) ) ;
1197+ }
1198+
11241199 // Test all possible item types in a single envelope
11251200 #[ test]
11261201 fn test_deserialize_serialized ( ) {
@@ -1197,12 +1272,27 @@ some content
11971272 ]
11981273 . into ( ) ;
11991274
1275+ let mut metric_attributes = Map :: new ( ) ;
1276+ metric_attributes. insert ( "route" . into ( ) , "/users" . into ( ) ) ;
1277+ let metrics: EnvelopeItem = vec ! [ Metric {
1278+ r#type: protocol:: MetricType :: Distribution ,
1279+ name: "response.time" . to_owned( ) ,
1280+ value: 123.4 ,
1281+ timestamp: timestamp( "2022-07-26T14:51:14.296Z" ) ,
1282+ trace_id: "335e53d614474acc9f89e632b776cc28" . parse( ) . unwrap( ) ,
1283+ span_id: Some ( "d42cee9fc3e74f5c" . parse( ) . unwrap( ) ) ,
1284+ unit: Some ( "millisecond" . to_owned( ) ) ,
1285+ attributes: metric_attributes,
1286+ } ]
1287+ . into ( ) ;
1288+
12001289 let mut envelope: Envelope = Envelope :: new ( ) ;
12011290 envelope. add_item ( event) ;
12021291 envelope. add_item ( transaction) ;
12031292 envelope. add_item ( session) ;
12041293 envelope. add_item ( attachment) ;
12051294 envelope. add_item ( logs) ;
1295+ envelope. add_item ( metrics) ;
12061296
12071297 let serialized = to_str ( envelope) ;
12081298 let deserialized = Envelope :: from_slice ( serialized. as_bytes ( ) ) . unwrap ( ) ;
0 commit comments