@@ -5,8 +5,8 @@ use std::time::SystemTime;
55
66use anyhow:: { Context , Result } ;
77
8- use sentry:: protocol:: MetricType ;
9- use sentry_core:: protocol:: { EnvelopeItem , ItemContainer } ;
8+ use sentry:: protocol:: { LogAttribute , MetricType } ;
9+ use sentry_core:: protocol:: { Envelope , EnvelopeItem , ItemContainer , Value } ;
1010use sentry_core:: test;
1111use sentry_core:: { ClientOptions , Hub , TransactionContext } ;
1212use sentry_types:: protocol:: v7:: Metric ;
@@ -283,6 +283,93 @@ fn metrics_span_id_from_active_span() {
283283 ) ;
284284}
285285
286+ /// Test that default SDK attributes are attached to metrics.
287+ #[ test]
288+ fn default_attributes_attached ( ) {
289+ let options = ClientOptions {
290+ enable_metrics : true ,
291+ environment : Some ( "test-env" . into ( ) ) ,
292+ release : Some ( "1.0.0" . into ( ) ) ,
293+ server_name : Some ( "test-server" . into ( ) ) ,
294+ ..Default :: default ( )
295+ } ;
296+
297+ let envelopes = test:: with_captured_envelopes_options ( || capture_test_metric ( "test" ) , options) ;
298+ let metric = extract_single_metric ( envelopes) . expect ( "expected a single-metric envelope" ) ;
299+
300+ let expected_attributes = [
301+ ( "sentry.environment" , "test-env" ) ,
302+ ( "sentry.release" , "1.0.0" ) ,
303+ ( "sentry.sdk.name" , "sentry.rust" ) ,
304+ ( "sentry.sdk.version" , env ! ( "CARGO_PKG_VERSION" ) ) ,
305+ ( "server.address" , "test-server" ) ,
306+ ]
307+ . into_iter ( )
308+ . map ( |( attribute, value) | ( attribute. into ( ) , value. into ( ) ) )
309+ . collect ( ) ;
310+
311+ assert_eq ! ( metric. attributes, expected_attributes) ;
312+ }
313+
314+ /// Test that optional default attributes are omitted when not configured.
315+ #[ test]
316+ fn optional_default_attributes_omitted_when_not_configured ( ) {
317+ let options = ClientOptions {
318+ enable_metrics : true ,
319+ ..Default :: default ( )
320+ } ;
321+
322+ let envelopes = test:: with_captured_envelopes_options ( || capture_test_metric ( "test" ) , options) ;
323+ let metric = extract_single_metric ( envelopes) . expect ( "expected a single-metric envelope" ) ;
324+
325+ let expected_attributes = [
326+ // Importantly, no other attributes should be set.
327+ ( "sentry.sdk.name" , "sentry.rust" ) ,
328+ ( "sentry.sdk.version" , env ! ( "CARGO_PKG_VERSION" ) ) ,
329+ ]
330+ . into_iter ( )
331+ . map ( |( attribute, value) | ( attribute. into ( ) , value. into ( ) ) )
332+ . collect ( ) ;
333+
334+ assert_eq ! ( metric. attributes, expected_attributes) ;
335+ }
336+
337+ /// Test that explicitly set metric attributes are not overwritten by defaults.
338+ #[ test]
339+ fn default_attributes_do_not_overwrite_explicit ( ) {
340+ let options = ClientOptions {
341+ enable_metrics : true ,
342+ environment : Some ( "default-env" . into ( ) ) ,
343+ ..Default :: default ( )
344+ } ;
345+
346+ let envelopes = test:: with_captured_envelopes_options (
347+ || {
348+ let mut metric = test_metric ( "test" ) ;
349+ metric. attributes . insert (
350+ "sentry.environment" . into ( ) ,
351+ LogAttribute ( Value :: from ( "custom-env" ) ) ,
352+ ) ;
353+ Hub :: current ( ) . capture_metric ( metric) ;
354+ } ,
355+ options,
356+ ) ;
357+ let metric = extract_single_metric ( envelopes) . expect ( "expected a single-metric envelope" ) ;
358+
359+ let expected_attributes = [
360+ // Check the environment is the one set directly on the metric
361+ ( "sentry.environment" , "custom-env" ) ,
362+ // The other default attributes also stay
363+ ( "sentry.sdk.name" , "sentry.rust" ) ,
364+ ( "sentry.sdk.version" , env ! ( "CARGO_PKG_VERSION" ) ) ,
365+ ]
366+ . into_iter ( )
367+ . map ( |( attribute, value) | ( attribute. into ( ) , value. into ( ) ) )
368+ . collect ( ) ;
369+
370+ assert_eq ! ( metric. attributes, expected_attributes) ;
371+ }
372+
286373/// Returns a [`Metric`] with [type `Counter`](MetricType),
287374/// the provided name, and a value of `1.0`.
288375fn test_metric < S > ( name : S ) -> Metric
@@ -309,6 +396,26 @@ where
309396 Hub :: current ( ) . capture_metric ( test_metric ( name) )
310397}
311398
399+ /// Helper to extract the single metric from a list of captured envelopes.
400+ ///
401+ /// Asserts that the envelope contains only a single item, which contains only
402+ /// a single metrics item, and returns that metrics item, or an error if failed.
403+ fn extract_single_metric < I > ( envelopes : I ) -> Result < Metric >
404+ where
405+ I : IntoIterator < Item = Envelope > ,
406+ {
407+ envelopes
408+ . try_into_only_item ( )
409+ . context ( "expected exactly one envelope" ) ?
410+ . into_items ( )
411+ . try_into_only_item ( )
412+ . context ( "expected exactly one item" ) ?
413+ . into_metrics ( )
414+ . context ( "expected a metrics item" ) ?
415+ . try_into_only_item ( )
416+ . context ( "expected exactly one metric" )
417+ }
418+
312419/// Extension trait for iterators allowing conversion to only item.
313420trait TryIntoOnlyElementExt < I > {
314421 type Item ;
0 commit comments