@@ -264,21 +264,26 @@ fn extract_span_links(event_payload: &str) -> Vec<Link> {
264264
265265fn add_resource_attributes ( span : & mut Span ) {
266266 use crate :: otlp:: attributes:: * ;
267- if let Some ( account_id) = crate :: state:: global:: get_account_id ( ) {
268- span. attributes . push ( KeyValue {
269- key : CLOUD_ACCOUNT_ID . to_string ( ) ,
270- value : Some ( AnyValue {
271- value : Some ( Value :: StringValue ( account_id) ) ,
272- } ) ,
273- } ) ;
267+ let has_attribute = |span : & Span , key : & str | span. attributes . iter ( ) . any ( |kv| kv. key == key) ;
268+ if !has_attribute ( span, CLOUD_ACCOUNT_ID ) {
269+ if let Some ( account_id) = crate :: state:: global:: get_account_id ( ) {
270+ span. attributes . push ( KeyValue {
271+ key : CLOUD_ACCOUNT_ID . to_string ( ) ,
272+ value : Some ( AnyValue {
273+ value : Some ( Value :: StringValue ( account_id) ) ,
274+ } ) ,
275+ } ) ;
276+ }
274277 }
275- if let Some ( function_arn) = crate :: state:: global:: get_function_arn ( ) {
276- span. attributes . push ( KeyValue {
277- key : CLOUD_RESOURCE_ID . to_string ( ) ,
278- value : Some ( AnyValue {
279- value : Some ( Value :: StringValue ( function_arn) ) ,
280- } ) ,
281- } ) ;
278+ if !has_attribute ( span, CLOUD_RESOURCE_ID ) {
279+ if let Some ( function_arn) = crate :: state:: global:: get_function_arn ( ) {
280+ span. attributes . push ( KeyValue {
281+ key : CLOUD_RESOURCE_ID . to_string ( ) ,
282+ value : Some ( AnyValue {
283+ value : Some ( Value :: StringValue ( function_arn) ) ,
284+ } ) ,
285+ } ) ;
286+ }
282287 }
283288}
284289
@@ -641,6 +646,7 @@ pub fn process_trace_request(
641646 }
642647
643648 for span in & mut scope_span. spans {
649+ add_resource_attributes ( span) ;
644650 let invocation_id = match extract_invocation_id ( span) {
645651 Some ( id) => id,
646652 None => continue ,
@@ -995,6 +1001,115 @@ mod tests {
9951001 ) ;
9961002 }
9971003
1004+ #[ test]
1005+ #[ serial]
1006+ fn add_resource_attributes_does_not_duplicate_existing_keys ( ) {
1007+ use crate :: otlp:: attributes:: { CLOUD_ACCOUNT_ID , CLOUD_RESOURCE_ID } ;
1008+
1009+ crate :: state:: global:: reset_for_tests ( ) ;
1010+ crate :: state:: global:: store_account_id ( "999988887777" ) ;
1011+ crate :: state:: global:: store_function_arn (
1012+ "arn:aws:lambda:us-east-1:999988887777:function:from-global" ,
1013+ ) ;
1014+
1015+ let mut span = Span {
1016+ attributes : vec ! [
1017+ KeyValue {
1018+ key: CLOUD_ACCOUNT_ID . to_string( ) ,
1019+ value: Some ( AnyValue {
1020+ value: Some ( Value :: StringValue ( "preexisting-account" . to_string( ) ) ) ,
1021+ } ) ,
1022+ } ,
1023+ KeyValue {
1024+ key: CLOUD_RESOURCE_ID . to_string( ) ,
1025+ value: Some ( AnyValue {
1026+ value: Some ( Value :: StringValue ( "preexisting-arn" . to_string( ) ) ) ,
1027+ } ) ,
1028+ } ,
1029+ ] ,
1030+ ..Default :: default ( )
1031+ } ;
1032+
1033+ super :: add_resource_attributes ( & mut span) ;
1034+
1035+ let account_keys: Vec < & KeyValue > = span
1036+ . attributes
1037+ . iter ( )
1038+ . filter ( |kv| kv. key == CLOUD_ACCOUNT_ID )
1039+ . collect ( ) ;
1040+ assert_eq ! (
1041+ account_keys. len( ) ,
1042+ 1 ,
1043+ "cloud.account.id should not duplicate"
1044+ ) ;
1045+ assert_eq ! (
1046+ account_keys[ 0 ]
1047+ . value
1048+ . as_ref( )
1049+ . and_then( |v| v. value. as_ref( ) ) ,
1050+ Some ( & Value :: StringValue ( "preexisting-account" . to_string( ) ) ) ,
1051+ "preexisting cloud.account.id should be preserved"
1052+ ) ;
1053+
1054+ let resource_keys: Vec < & KeyValue > = span
1055+ . attributes
1056+ . iter ( )
1057+ . filter ( |kv| kv. key == CLOUD_RESOURCE_ID )
1058+ . collect ( ) ;
1059+ assert_eq ! (
1060+ resource_keys. len( ) ,
1061+ 1 ,
1062+ "cloud.resource_id should not duplicate"
1063+ ) ;
1064+ assert_eq ! (
1065+ resource_keys[ 0 ]
1066+ . value
1067+ . as_ref( )
1068+ . and_then( |v| v. value. as_ref( ) ) ,
1069+ Some ( & Value :: StringValue ( "preexisting-arn" . to_string( ) ) ) ,
1070+ "preexisting cloud.resource_id should be preserved"
1071+ ) ;
1072+
1073+ crate :: state:: global:: reset_for_tests ( ) ;
1074+ }
1075+
1076+ #[ test]
1077+ #[ serial]
1078+ fn add_resource_attributes_adds_when_missing ( ) {
1079+ use crate :: otlp:: attributes:: { CLOUD_ACCOUNT_ID , CLOUD_RESOURCE_ID } ;
1080+
1081+ crate :: state:: global:: reset_for_tests ( ) ;
1082+ crate :: state:: global:: store_account_id ( "111122223333" ) ;
1083+ crate :: state:: global:: store_function_arn (
1084+ "arn:aws:lambda:us-east-1:111122223333:function:added" ,
1085+ ) ;
1086+
1087+ let mut span = Span :: default ( ) ;
1088+ super :: add_resource_attributes ( & mut span) ;
1089+
1090+ let account = find_attribute ( & span, CLOUD_ACCOUNT_ID )
1091+ . and_then ( |v| v. value . as_ref ( ) )
1092+ . cloned ( ) ;
1093+ assert_eq ! (
1094+ account,
1095+ Some ( Value :: StringValue ( "111122223333" . to_string( ) ) ) ,
1096+ "cloud.account.id should be added from global state"
1097+ ) ;
1098+
1099+ let resource = find_attribute ( & span, CLOUD_RESOURCE_ID )
1100+ . and_then ( |v| v. value . as_ref ( ) )
1101+ . cloned ( ) ;
1102+ assert_eq ! (
1103+ resource,
1104+ Some ( Value :: StringValue (
1105+ "arn:aws:lambda:us-east-1:111122223333:function:added" . to_string( )
1106+ ) ) ,
1107+ "cloud.resource_id should be added from global state"
1108+ ) ;
1109+
1110+ crate :: state:: global:: reset_for_tests ( ) ;
1111+ }
1112+
9981113 #[ test]
9991114 #[ serial]
10001115 fn process_trace_request_updates_encoded_body_correctly ( ) {
0 commit comments