@@ -43,6 +43,11 @@ fn replace_chars<MatchFn: Fn(u8) -> bool>(
43
43
f : MatchFn ,
44
44
replacement_char : char ,
45
45
) -> Cow < ' _ , str > {
46
+ // Fast first pass
47
+ if s. as_bytes ( ) . iter ( ) . all ( |c| c. is_ascii ( ) && !f ( * c) ) {
48
+ return Cow :: Borrowed ( s) ;
49
+ }
50
+
46
51
let mut replaced = String :: new ( ) ;
47
52
let mut tail = s;
48
53
loop {
@@ -68,11 +73,7 @@ fn replace_chars<MatchFn: Fn(u8) -> bool>(
68
73
} ;
69
74
tail = & tail[ pos + offset..] ;
70
75
}
71
- if replaced. is_empty ( ) {
72
- Cow :: Borrowed ( s)
73
- } else {
74
- Cow :: Owned ( replaced)
75
- }
76
+ Cow :: Owned ( replaced)
76
77
}
77
78
78
79
pub fn inject ( context : & InjectSpanContext , carrier : & mut dyn Injector ) {
@@ -88,30 +89,104 @@ fn inject_traceparent(context: &InjectSpanContext, carrier: &mut dyn Injector) {
88
89
// TODO: if higher trace_id 64bits are 0, we should verify _dd.p.tid is unset
89
90
// if not 0, verify that `_dd.p.tid` is either unset or set to the encoded value of
90
91
// the higher-order 64 bits
91
- let trace_id = format ! ( "{:032x}" , context. trace_id) ;
92
- let parent_id = format ! ( "{:016x}" , context. span_id) ;
93
92
94
93
let flags = context
95
94
. sampling
96
95
. priority
97
96
. map ( |priority| if priority. is_keep ( ) { "01" } else { "00" } )
98
97
. unwrap_or ( "00" ) ;
99
98
100
- let traceparent = format ! ( "00-{trace_id}-{parent_id}-{flags}" ) ;
99
+ let traceparent = format ! (
100
+ "00-{:032x}-{:016x}-{flags}" ,
101
+ context. trace_id, context. span_id
102
+ ) ;
101
103
102
104
dd_debug ! ( "Propagator (tracecontext): injecting traceparent: {traceparent}" ) ;
103
105
104
106
carrier. set ( TRACEPARENT_KEY , traceparent) ;
105
107
}
106
108
109
+ fn buf_appender ( buf : & mut String ) -> BufAppender < ' _ > {
110
+ BufAppender {
111
+ start : buf. len ( ) ,
112
+ buf,
113
+ }
114
+ }
115
+
116
+ struct BufAppender < ' a > {
117
+ start : usize ,
118
+ buf : & ' a mut String ,
119
+ }
120
+
121
+ impl BufAppender < ' _ > {
122
+ fn push_str ( & mut self , s : & str ) {
123
+ self . buf . push_str ( s) ;
124
+ }
125
+
126
+ fn len ( & self ) -> usize {
127
+ self . buf . len ( ) - self . start
128
+ }
129
+
130
+ fn appender ( & mut self ) -> BufAppender < ' _ > {
131
+ BufAppender {
132
+ start : self . buf . len ( ) ,
133
+ buf : self . buf ,
134
+ }
135
+ }
136
+
137
+ fn truncate ( & mut self , len : usize ) {
138
+ self . buf . truncate ( self . start + len) ;
139
+ }
140
+ }
141
+
142
+ fn append_dd_propagation_tags ( context : & InjectSpanContext , tags_buffer : & mut BufAppender ) {
143
+ for ( key, value) in context. tags . iter ( ) {
144
+ let Some ( key_suffix) = key. strip_prefix ( DATADOG_PROPAGATION_TAG_PREFIX ) else {
145
+ continue ;
146
+ } ;
147
+
148
+ let t_key_suffix = replace_chars (
149
+ key_suffix,
150
+ |c| !matches ! ( c, b'!' ..=b'+' | b'-' ..=b'<' | b'>' ..=b'~' ) ,
151
+ INVALID_CHAR_REPLACEMENT ,
152
+ ) ;
153
+ let encoded_value = replace_chars (
154
+ value,
155
+ |c| !matches ! ( c, b' ' ..=b'+' | b'-' ..=b':' | b'<' ..=b'}' ) ,
156
+ INVALID_CHAR_REPLACEMENT ,
157
+ ) ;
158
+ let encoded_value = encode_tag_value ( & encoded_value) ;
159
+
160
+ let entry_size = TRACESTATE_DD_PAIR_SEPARATOR . len ( )
161
+ + TRACESTATE_DATADOG_PROPAGATION_TAG_PREFIX . len ( )
162
+ + t_key_suffix. len ( )
163
+ + 1
164
+ + encoded_value. len ( ) ;
165
+
166
+ if tags_buffer. len ( ) + entry_size > TRACESTATE_DD_KEY_MAX_LENGTH / 2 {
167
+ break ;
168
+ }
169
+
170
+ tags_buffer. push_str ( crate :: const_concat!(
171
+ TRACESTATE_DD_PAIR_SEPARATOR ,
172
+ TRACESTATE_DATADOG_PROPAGATION_TAG_PREFIX ,
173
+ ) ) ;
174
+ tags_buffer. push_str ( & t_key_suffix) ;
175
+ tags_buffer. push_str ( ":" ) ;
176
+ tags_buffer. push_str ( & encoded_value) ;
177
+ }
178
+ }
179
+
107
180
fn inject_tracestate ( context : & InjectSpanContext , carrier : & mut dyn Injector ) {
181
+ let mut tracestate = String :: with_capacity ( 256 ) ;
182
+ tracestate. push_str ( "dd=" ) ;
183
+
108
184
// Use a single String buffer to build the entire tracestate, avoiding intermediate allocations
109
- let mut dd_parts = String :: new ( ) ;
185
+ let mut dd_parts = buf_appender ( & mut tracestate ) ;
110
186
111
187
// Build sampling priority part
112
188
let priority = context. sampling . priority . unwrap_or ( priority:: USER_KEEP ) ;
113
- dd_parts. push_str ( TRACESTATE_SAMPLING_PRIORITY_KEY ) ;
114
- dd_parts. push ( ':' ) ;
189
+ dd_parts. push_str ( crate :: const_concat!( TRACESTATE_SAMPLING_PRIORITY_KEY , ":" , ) ) ;
115
190
dd_parts. push_str ( & priority. to_string ( ) ) ;
116
191
117
192
// Build origin part if present
@@ -130,9 +205,11 @@ fn inject_tracestate(context: &InjectSpanContext, carrier: &mut dyn Injector) {
130
205
+ origin_encoded. len ( )
131
206
< TRACESTATE_DD_KEY_MAX_LENGTH
132
207
{
133
- dd_parts. push_str ( TRACESTATE_DD_PAIR_SEPARATOR ) ;
134
- dd_parts. push_str ( TRACESTATE_ORIGIN_KEY ) ;
135
- dd_parts. push ( ':' ) ;
208
+ dd_parts. push_str ( crate :: const_concat!(
209
+ TRACESTATE_DD_PAIR_SEPARATOR ,
210
+ TRACESTATE_ORIGIN_KEY ,
211
+ ":" ,
212
+ ) ) ;
136
213
dd_parts. push_str ( & origin_encoded) ;
137
214
}
138
215
}
@@ -142,78 +219,35 @@ fn inject_tracestate(context: &InjectSpanContext, carrier: &mut dyn Injector) {
142
219
dd_parts. len ( ) + TRACESTATE_DD_PAIR_SEPARATOR . len ( ) + TRACESTATE_LAST_PARENT_KEY . len ( ) + 1 ;
143
220
if last_parent_id_part_start + 16 < TRACESTATE_DD_KEY_MAX_LENGTH {
144
221
// 16 chars for hex span_id
145
- dd_parts. push_str ( TRACESTATE_DD_PAIR_SEPARATOR ) ;
146
- dd_parts. push_str ( TRACESTATE_LAST_PARENT_KEY ) ;
147
- dd_parts. push ( ':' ) ;
222
+
223
+ dd_parts. push_str ( crate :: const_concat!(
224
+ TRACESTATE_DD_PAIR_SEPARATOR ,
225
+ TRACESTATE_LAST_PARENT_KEY ,
226
+ ":" ,
227
+ ) ) ;
148
228
149
229
if context. is_remote {
150
230
if let Some ( id) = context. tags . get ( DATADOG_LAST_PARENT_ID_KEY ) {
151
231
dd_parts. push_str ( id) ;
152
232
} else {
153
- let _ = write ! ( & mut dd_parts, "{:016x}" , context. span_id) ;
233
+ let _ = write ! ( & mut dd_parts. buf , "{:016x}" , context. span_id) ;
154
234
}
155
235
} else {
156
- let _ = write ! ( & mut dd_parts, "{:016x}" , context. span_id) ;
236
+ let _ = write ! ( & mut dd_parts. buf , "{:016x}" , context. span_id) ;
157
237
}
158
238
}
159
239
240
+ let index_before_tags = dd_parts. len ( ) ;
160
241
// Build propagation tags part
161
- let mut tags_buffer = String :: new ( ) ;
162
- let mut first_tag = true ;
242
+ let mut tags_buffer = dd_parts. appender ( ) ;
163
243
164
- for ( key, value) in context. tags . iter ( ) {
165
- let Some ( key_suffix) = key. strip_prefix ( DATADOG_PROPAGATION_TAG_PREFIX ) else {
166
- continue ;
167
- } ;
168
-
169
- let t_key_suffix = replace_chars (
170
- key_suffix,
171
- |c| !matches ! ( c, b'!' ..=b'+' | b'-' ..=b'<' | b'>' ..=b'~' ) ,
172
- INVALID_CHAR_REPLACEMENT ,
173
- ) ;
174
- let encoded_value = replace_chars (
175
- value,
176
- |c| !matches ! ( c, b' ' ..=b'+' | b'-' ..=b':' | b'<' ..=b'}' ) ,
177
- INVALID_CHAR_REPLACEMENT ,
178
- ) ;
179
- let encoded_value = encode_tag_value ( & encoded_value) ;
180
-
181
- let entry_size = if first_tag {
182
- 0
183
- } else {
184
- TRACESTATE_DD_PAIR_SEPARATOR . len ( )
185
- } + TRACESTATE_DATADOG_PROPAGATION_TAG_PREFIX . len ( )
186
- + t_key_suffix. len ( )
187
- + 1
188
- + encoded_value. len ( ) ;
189
-
190
- if tags_buffer. len ( ) + entry_size > TRACESTATE_DD_KEY_MAX_LENGTH / 2 {
191
- break ;
192
- }
193
-
194
- if !first_tag {
195
- tags_buffer. push_str ( TRACESTATE_DD_PAIR_SEPARATOR ) ;
196
- }
197
- tags_buffer. push_str ( TRACESTATE_DATADOG_PROPAGATION_TAG_PREFIX ) ;
198
- tags_buffer. push_str ( & t_key_suffix) ;
199
- tags_buffer. push ( ':' ) ;
200
- tags_buffer. push_str ( & encoded_value) ;
201
- first_tag = false ;
202
- }
244
+ append_dd_propagation_tags ( context, & mut tags_buffer) ;
203
245
204
246
// Add tags part to dd_parts if there's room
205
- if !tags_buffer. is_empty ( )
206
- && dd_parts. len ( ) + TRACESTATE_DD_PAIR_SEPARATOR . len ( ) + tags_buffer. len ( )
207
- < TRACESTATE_DD_KEY_MAX_LENGTH
208
- {
209
- dd_parts. push_str ( TRACESTATE_DD_PAIR_SEPARATOR ) ;
210
- dd_parts. push_str ( & tags_buffer) ;
247
+ if tags_buffer. len ( ) == 0 || dd_parts. len ( ) >= TRACESTATE_DD_KEY_MAX_LENGTH {
248
+ dd_parts. truncate ( index_before_tags) ;
211
249
}
212
250
213
- let mut tracestate = String :: with_capacity ( 256 ) ;
214
- tracestate. push_str ( "dd=" ) ;
215
- tracestate. push_str ( & dd_parts) ;
216
-
217
251
// Add additional tracestate values if present
218
252
if let Some ( ts) = context. tracestate {
219
253
if let Some ( ref additional) = ts. additional_values {
@@ -226,7 +260,10 @@ fn inject_tracestate(context: &InjectSpanContext, carrier: &mut dyn Injector) {
226
260
}
227
261
}
228
262
229
- dd_debug ! ( "Propagator (tracecontext): injecting tracestate: {tracestate}" ) ;
263
+ dd_debug ! (
264
+ "Propagator (tracecontext): injecting tracestate: {}" ,
265
+ tracestate
266
+ ) ;
230
267
231
268
carrier. set ( TRACESTATE_KEY , tracestate) ;
232
269
}
@@ -472,7 +509,7 @@ pub fn keys() -> &'static [String] {
472
509
mod test {
473
510
use dd_trace:: { configuration:: TracePropagationStyle , sampling:: priority, Config } ;
474
511
475
- use crate :: Propagator ;
512
+ use crate :: { context :: span_context_to_inject , Propagator } ;
476
513
477
514
use super :: * ;
478
515
@@ -787,6 +824,95 @@ mod test {
787
824
assert ! ( carrier[ TRACESTATE_KEY ] . ends_with( "state30=value-30" ) ) ;
788
825
}
789
826
827
+ #[ test]
828
+ fn test_tracestate_with_tags_longer_than_limit ( ) {
829
+ let long_origin = "abcd" . repeat ( 32 ) ;
830
+ let long_tag = "abcd" . repeat ( 30 ) ;
831
+ let mut context = SpanContext {
832
+ trace_id : u128:: from_str_radix ( "1111aaaa2222bbbb3333cccc4444dddd" , 16 ) . unwrap ( ) ,
833
+ span_id : u64:: from_str_radix ( "5555eeee6666ffff" , 16 ) . unwrap ( ) ,
834
+ sampling : Sampling {
835
+ priority : Some ( priority:: USER_KEEP ) ,
836
+ mechanism : Some ( mechanism:: MANUAL ) ,
837
+ } ,
838
+ origin : Some ( long_origin. clone ( ) ) ,
839
+ tags : HashMap :: from ( [ ( "_dd.p.foo" . to_string ( ) , long_tag. clone ( ) ) ] ) ,
840
+ links : vec ! [ ] ,
841
+ is_remote : false ,
842
+ tracestate : None ,
843
+ } ;
844
+ let mut carrier: HashMap < String , String > = HashMap :: new ( ) ;
845
+ TracePropagationStyle :: TraceContext . inject (
846
+ & mut span_context_to_inject ( & mut context) ,
847
+ & mut carrier,
848
+ & Config :: builder ( ) . build ( ) ,
849
+ ) ;
850
+ assert_eq ! (
851
+ carrier[ TRACESTATE_KEY ] ,
852
+ format!( "dd=s:2;o:{long_origin};p:5555eeee6666ffff" )
853
+ ) ;
854
+ }
855
+
856
+ #[ test]
857
+ fn test_tracestate_with_tags_shorter_than_limit ( ) {
858
+ #[ allow( clippy:: repeat_once) ]
859
+ let short_origin = "abcd" . repeat ( 1 ) ;
860
+ let long_tag = "abcd" . repeat ( 30 ) ;
861
+ let mut context = SpanContext {
862
+ trace_id : u128:: from_str_radix ( "1111aaaa2222bbbb3333cccc4444dddd" , 16 ) . unwrap ( ) ,
863
+ span_id : u64:: from_str_radix ( "5555eeee6666ffff" , 16 ) . unwrap ( ) ,
864
+ sampling : Sampling {
865
+ priority : Some ( priority:: USER_KEEP ) ,
866
+ mechanism : Some ( mechanism:: MANUAL ) ,
867
+ } ,
868
+ origin : Some ( short_origin. clone ( ) ) ,
869
+ tags : HashMap :: from ( [ ( "_dd.p.foo" . to_string ( ) , long_tag. clone ( ) ) ] ) ,
870
+ links : vec ! [ ] ,
871
+ is_remote : false ,
872
+ tracestate : None ,
873
+ } ;
874
+ let mut carrier: HashMap < String , String > = HashMap :: new ( ) ;
875
+ TracePropagationStyle :: TraceContext . inject (
876
+ & mut span_context_to_inject ( & mut context) ,
877
+ & mut carrier,
878
+ & Config :: builder ( ) . build ( ) ,
879
+ ) ;
880
+ assert_eq ! (
881
+ carrier[ TRACESTATE_KEY ] ,
882
+ format!( "dd=s:2;o:{short_origin};p:5555eeee6666ffff;t.foo:{long_tag}" )
883
+ ) ;
884
+ }
885
+
886
+ #[ test]
887
+ fn test_tracestate_with_long_dd_tags ( ) {
888
+ #[ allow( clippy:: repeat_once) ]
889
+ let short_origin = "abcd" . repeat ( 1 ) ;
890
+ let long_tag = "abcd" . repeat ( 32 ) ;
891
+ let mut context = SpanContext {
892
+ trace_id : u128:: from_str_radix ( "1111aaaa2222bbbb3333cccc4444dddd" , 16 ) . unwrap ( ) ,
893
+ span_id : u64:: from_str_radix ( "5555eeee6666ffff" , 16 ) . unwrap ( ) ,
894
+ sampling : Sampling {
895
+ priority : Some ( priority:: USER_KEEP ) ,
896
+ mechanism : Some ( mechanism:: MANUAL ) ,
897
+ } ,
898
+ origin : Some ( short_origin. clone ( ) ) ,
899
+ tags : HashMap :: from ( [ ( "_dd.p.foo" . to_string ( ) , long_tag. clone ( ) ) ] ) ,
900
+ links : vec ! [ ] ,
901
+ is_remote : false ,
902
+ tracestate : None ,
903
+ } ;
904
+ let mut carrier: HashMap < String , String > = HashMap :: new ( ) ;
905
+ TracePropagationStyle :: TraceContext . inject (
906
+ & mut span_context_to_inject ( & mut context) ,
907
+ & mut carrier,
908
+ & Config :: builder ( ) . build ( ) ,
909
+ ) ;
910
+ assert_eq ! (
911
+ carrier[ TRACESTATE_KEY ] ,
912
+ format!( "dd=s:2;o:{short_origin};p:5555eeee6666ffff" )
913
+ ) ;
914
+ }
915
+
790
916
#[ test]
791
917
fn test_replace_chars ( ) {
792
918
let tests = vec ! [
0 commit comments