33
44use ddcommon:: Endpoint ;
55use std:: borrow:: Cow ;
6+ use std:: collections:: HashMap ;
67use std:: env;
78use std:: str:: FromStr ;
9+ use std:: sync:: OnceLock ;
810
911use datadog_trace_obfuscation:: obfuscation_config;
1012use datadog_trace_utils:: config_utils:: {
@@ -15,6 +17,59 @@ use datadog_trace_utils::trace_utils;
1517
1618const DEFAULT_DOGSTATSD_PORT : u16 = 8125 ;
1719
20+ #[ derive( Debug ) ]
21+ pub struct Tags {
22+ tags : HashMap < String , String > ,
23+ function_tags_string : OnceLock < String > ,
24+ }
25+
26+ impl Tags {
27+ pub fn from_env_string ( env_tags : & str ) -> Self {
28+ let mut tags = HashMap :: new ( ) ;
29+
30+ // Space-separated key:value tags are the standard for tagging. For compatibility reasons
31+ // we also support comma-separated key:value tags as well.
32+ let normalized = env_tags. replace ( ',' , " " ) ;
33+
34+ for kv in normalized. split_whitespace ( ) {
35+ let parts = kv. split ( ':' ) . collect :: < Vec < & str > > ( ) ;
36+ if parts. len ( ) == 2 {
37+ tags. insert ( parts[ 0 ] . to_string ( ) , parts[ 1 ] . to_string ( ) ) ;
38+ }
39+ }
40+ Self {
41+ tags,
42+ function_tags_string : OnceLock :: new ( ) ,
43+ }
44+ }
45+
46+ pub fn new ( ) -> Self {
47+ Self {
48+ tags : HashMap :: new ( ) ,
49+ function_tags_string : OnceLock :: new ( ) ,
50+ }
51+ }
52+
53+ pub fn tags ( & self ) -> & HashMap < String , String > {
54+ & self . tags
55+ }
56+
57+ pub fn function_tags ( & self ) -> Option < & str > {
58+ if self . tags . is_empty ( ) {
59+ return None ;
60+ }
61+ Some ( self . function_tags_string . get_or_init ( || {
62+ let mut kvs = self
63+ . tags
64+ . iter ( )
65+ . map ( |( k, v) | format ! ( "{k}:{v}" ) )
66+ . collect :: < Vec < String > > ( ) ;
67+ kvs. sort ( ) ;
68+ kvs. join ( "," )
69+ } ) )
70+ }
71+ }
72+
1873#[ derive( Debug ) ]
1974pub struct Config {
2075 pub dd_site : String ,
@@ -24,6 +79,7 @@ pub struct Config {
2479 pub max_request_content_length : usize ,
2580 pub obfuscation_config : obfuscation_config:: ObfuscationConfig ,
2681 pub os : String ,
82+ pub tags : Tags ,
2783 /// how often to flush stats, in seconds
2884 pub stats_flush_interval : u64 ,
2985 /// how often to flush traces, in seconds
@@ -69,6 +125,12 @@ impl Config {
69125 )
70126 } ) ?;
71127
128+ let tags = if let Ok ( env_tags) = env:: var ( "DD_TAGS" ) {
129+ Tags :: from_env_string ( & env_tags)
130+ } else {
131+ Tags :: new ( )
132+ } ;
133+
72134 #[ allow( clippy:: unwrap_used) ]
73135 Ok ( Config {
74136 app_name : Some ( app_name) ,
@@ -94,6 +156,7 @@ impl Config {
94156 proxy_url : env:: var ( "DD_PROXY_HTTPS" )
95157 . or_else ( |_| env:: var ( "HTTPS_PROXY" ) )
96158 . ok ( ) ,
159+ tags,
97160 } )
98161 }
99162}
@@ -102,6 +165,7 @@ impl Config {
102165mod tests {
103166 use duplicate:: duplicate_item;
104167 use serial_test:: serial;
168+ use std:: collections:: HashMap ;
105169 use std:: env;
106170
107171 use crate :: config;
@@ -250,4 +314,81 @@ mod tests {
250314 env:: remove_var ( "ASCSVCRT_SPRING__APPLICATION__NAME" ) ;
251315 env:: remove_var ( "DD_DOGSTATSD_PORT" ) ;
252316 }
317+
318+ fn test_config_with_dd_tags ( dd_tags : & str ) -> config:: Config {
319+ env:: set_var ( "DD_API_KEY" , "_not_a_real_key_" ) ;
320+ env:: set_var ( "ASCSVCRT_SPRING__APPLICATION__NAME" , "test-spring-app" ) ;
321+ env:: set_var ( "DD_TAGS" , dd_tags) ;
322+ let config_res = config:: Config :: new ( ) ;
323+ assert ! ( config_res. is_ok( ) ) ;
324+ let config = config_res. unwrap ( ) ;
325+ env:: remove_var ( "DD_API_KEY" ) ;
326+ env:: remove_var ( "ASCSVCRT_SPRING__APPLICATION__NAME" ) ;
327+ env:: remove_var ( "DD_TAGS" ) ;
328+ config
329+ }
330+
331+ #[ test]
332+ #[ serial]
333+ fn test_dd_tags_comma_separated ( ) {
334+ let config = test_config_with_dd_tags ( "some:tag,another:thing,invalid:thing:here" ) ;
335+ let expected_tags = HashMap :: from ( [
336+ ( "some" . to_string ( ) , "tag" . to_string ( ) ) ,
337+ ( "another" . to_string ( ) , "thing" . to_string ( ) ) ,
338+ ] ) ;
339+ assert_eq ! ( config. tags. tags( ) , & expected_tags) ;
340+ assert_eq ! ( config. tags. function_tags( ) , Some ( "another:thing,some:tag" ) ) ;
341+ }
342+
343+ #[ test]
344+ #[ serial]
345+ fn test_dd_tags_space_separated ( ) {
346+ let config = test_config_with_dd_tags ( "some:tag another:thing invalid:thing:here" ) ;
347+ let expected_tags = HashMap :: from ( [
348+ ( "some" . to_string ( ) , "tag" . to_string ( ) ) ,
349+ ( "another" . to_string ( ) , "thing" . to_string ( ) ) ,
350+ ] ) ;
351+ assert_eq ! ( config. tags. tags( ) , & expected_tags) ;
352+ assert_eq ! ( config. tags. function_tags( ) , Some ( "another:thing,some:tag" ) ) ;
353+ }
354+
355+ #[ test]
356+ #[ serial]
357+ fn test_dd_tags_mixed_separators ( ) {
358+ let config = test_config_with_dd_tags ( "some:tag,another:thing extra:value" ) ;
359+ let expected_tags = HashMap :: from ( [
360+ ( "some" . to_string ( ) , "tag" . to_string ( ) ) ,
361+ ( "another" . to_string ( ) , "thing" . to_string ( ) ) ,
362+ ( "extra" . to_string ( ) , "value" . to_string ( ) ) ,
363+ ] ) ;
364+ assert_eq ! ( config. tags. tags( ) , & expected_tags) ;
365+ assert_eq ! (
366+ config. tags. function_tags( ) ,
367+ Some ( "another:thing,extra:value,some:tag" )
368+ ) ;
369+ }
370+
371+ #[ test]
372+ #[ serial]
373+ fn test_dd_tags_no_valid_tags ( ) {
374+ // Test with only invalid tags
375+ let config = test_config_with_dd_tags ( "invalid:thing:here,also-bad" ) ;
376+ assert_eq ! ( config. tags. tags( ) , & HashMap :: new( ) ) ;
377+ assert_eq ! ( config. tags. function_tags( ) , None ) ;
378+
379+ // Test with empty string
380+ let config = test_config_with_dd_tags ( "" ) ;
381+ assert_eq ! ( config. tags. tags( ) , & HashMap :: new( ) ) ;
382+ assert_eq ! ( config. tags. function_tags( ) , None ) ;
383+
384+ // Test with just whitespace
385+ let config = test_config_with_dd_tags ( " " ) ;
386+ assert_eq ! ( config. tags. tags( ) , & HashMap :: new( ) ) ;
387+ assert_eq ! ( config. tags. function_tags( ) , None ) ;
388+
389+ // Test with just commas and spaces
390+ let config = test_config_with_dd_tags ( " , , " ) ;
391+ assert_eq ! ( config. tags. tags( ) , & HashMap :: new( ) ) ;
392+ assert_eq ! ( config. tags. function_tags( ) , None ) ;
393+ }
253394}
0 commit comments