@@ -4,6 +4,7 @@ use opentelemetry::{
44 trace:: { self as otel, noop, SpanBuilder , SpanKind , Status , TraceContextExt } ,
55 Context as OtelContext , Key , KeyValue , StringValue , Value ,
66} ;
7+ use opentelemetry_sdk:: trace:: SpanLimits ;
78use std:: fmt;
89use std:: marker;
910use std:: thread;
@@ -126,7 +127,7 @@ struct SpanBuilderUpdates {
126127}
127128
128129impl SpanBuilderUpdates {
129- fn update ( self , span_builder : & mut SpanBuilder ) {
130+ fn update ( self , span_builder : & mut SpanBuilder , limits : Option < SpanLimits > ) {
130131 let Self {
131132 name,
132133 span_kind,
@@ -143,20 +144,46 @@ impl SpanBuilderUpdates {
143144 if let Some ( status) = status {
144145 span_builder. status = status;
145146 }
146- if let Some ( attributes) = attributes {
147+ if let Some ( mut attributes) = attributes {
147148 if let Some ( builder_attributes) = & mut span_builder. attributes {
149+ if let Some ( limits) = limits {
150+ attributes. truncate (
151+ ( limits. max_attributes_per_span as usize )
152+ . saturating_sub ( builder_attributes. len ( ) ) ,
153+ ) ;
154+ }
148155 builder_attributes. extend ( attributes) ;
149156 } else {
157+ if let Some ( limits) = limits {
158+ attributes. truncate ( limits. max_attributes_per_span as usize ) ;
159+ }
150160 span_builder. attributes = Some ( attributes) ;
151161 }
152162 }
153163 }
154164}
155165
166+ fn push_unless_limit_reached < T > ( attributes_or_events : & mut Vec < T > , limit : Option < u32 > , value : T ) {
167+ if limit. map_or ( true , |limit| ( limit as usize ) > attributes_or_events. len ( ) ) {
168+ attributes_or_events. push ( value) ;
169+ }
170+ }
171+
156172struct SpanEventVisitor < ' a , ' b > {
157173 event_builder : & ' a mut otel:: Event ,
158174 span_builder_updates : & ' b mut Option < SpanBuilderUpdates > ,
159175 sem_conv_config : SemConvConfig ,
176+ limits : Option < SpanLimits > ,
177+ }
178+
179+ impl < ' a , ' b > SpanEventVisitor < ' a , ' b > {
180+ fn push_event_attribute ( & mut self , attribute : KeyValue ) {
181+ push_unless_limit_reached (
182+ & mut self . event_builder . attributes ,
183+ self . limits . map ( |limits| limits. max_attributes_per_event ) ,
184+ attribute,
185+ ) ;
186+ }
160187}
161188
162189impl < ' a , ' b > field:: Visit for SpanEventVisitor < ' a , ' b > {
@@ -170,9 +197,7 @@ impl<'a, 'b> field::Visit for SpanEventVisitor<'a, 'b> {
170197 #[ cfg( feature = "tracing-log" ) ]
171198 name if name. starts_with ( "log." ) => ( ) ,
172199 name => {
173- self . event_builder
174- . attributes
175- . push ( KeyValue :: new ( name, value) ) ;
200+ self . push_event_attribute ( KeyValue :: new ( name, value) ) ;
176201 }
177202 }
178203 }
@@ -187,9 +212,7 @@ impl<'a, 'b> field::Visit for SpanEventVisitor<'a, 'b> {
187212 #[ cfg( feature = "tracing-log" ) ]
188213 name if name. starts_with ( "log." ) => ( ) ,
189214 name => {
190- self . event_builder
191- . attributes
192- . push ( KeyValue :: new ( name, value) ) ;
215+ self . push_event_attribute ( KeyValue :: new ( name, value) ) ;
193216 }
194217 }
195218 }
@@ -204,9 +227,7 @@ impl<'a, 'b> field::Visit for SpanEventVisitor<'a, 'b> {
204227 #[ cfg( feature = "tracing-log" ) ]
205228 name if name. starts_with ( "log." ) => ( ) ,
206229 name => {
207- self . event_builder
208- . attributes
209- . push ( KeyValue :: new ( name, value) ) ;
230+ self . push_event_attribute ( KeyValue :: new ( name, value) ) ;
210231 }
211232 }
212233 }
@@ -229,23 +250,19 @@ impl<'a, 'b> field::Visit for SpanEventVisitor<'a, 'b> {
229250 }
230251 if self . sem_conv_config . error_events_to_exceptions {
231252 self . event_builder . name = EVENT_EXCEPTION_NAME . into ( ) ;
232- self . event_builder . attributes . push ( KeyValue :: new (
253+ self . push_event_attribute ( KeyValue :: new (
233254 FIELD_EXCEPTION_MESSAGE ,
234255 format ! ( "{:?}" , value) ,
235256 ) ) ;
236257 } else {
237- self . event_builder
238- . attributes
239- . push ( KeyValue :: new ( "error" , format ! ( "{:?}" , value) ) ) ;
258+ self . push_event_attribute ( KeyValue :: new ( "error" , format ! ( "{:?}" , value) ) ) ;
240259 }
241260 }
242261 // Skip fields that are actually log metadata that have already been handled
243262 #[ cfg( feature = "tracing-log" ) ]
244263 name if name. starts_with ( "log." ) => ( ) ,
245264 name => {
246- self . event_builder
247- . attributes
248- . push ( KeyValue :: new ( name, value. to_string ( ) ) ) ;
265+ self . push_event_attribute ( KeyValue :: new ( name, value. to_string ( ) ) ) ;
249266 }
250267 }
251268 }
@@ -269,23 +286,19 @@ impl<'a, 'b> field::Visit for SpanEventVisitor<'a, 'b> {
269286 }
270287 if self . sem_conv_config . error_events_to_exceptions {
271288 self . event_builder . name = EVENT_EXCEPTION_NAME . into ( ) ;
272- self . event_builder . attributes . push ( KeyValue :: new (
289+ self . push_event_attribute ( KeyValue :: new (
273290 FIELD_EXCEPTION_MESSAGE ,
274291 format ! ( "{:?}" , value) ,
275292 ) ) ;
276293 } else {
277- self . event_builder
278- . attributes
279- . push ( KeyValue :: new ( "error" , format ! ( "{:?}" , value) ) ) ;
294+ self . push_event_attribute ( KeyValue :: new ( "error" , format ! ( "{:?}" , value) ) ) ;
280295 }
281296 }
282297 // Skip fields that are actually log metadata that have already been handled
283298 #[ cfg( feature = "tracing-log" ) ]
284299 name if name. starts_with ( "log." ) => ( ) ,
285300 name => {
286- self . event_builder
287- . attributes
288- . push ( KeyValue :: new ( name, format ! ( "{:?}" , value) ) ) ;
301+ self . push_event_attribute ( KeyValue :: new ( name, format ! ( "{:?}" , value) ) ) ;
289302 }
290303 }
291304 }
@@ -310,19 +323,15 @@ impl<'a, 'b> field::Visit for SpanEventVisitor<'a, 'b> {
310323 let error_msg = value. to_string ( ) ;
311324
312325 if self . sem_conv_config . error_fields_to_exceptions {
313- self . event_builder
314- . attributes
315- . push ( Key :: new ( FIELD_EXCEPTION_MESSAGE ) . string ( error_msg. clone ( ) ) ) ;
326+ self . push_event_attribute ( Key :: new ( FIELD_EXCEPTION_MESSAGE ) . string ( error_msg. clone ( ) ) ) ;
316327
317328 // NOTE: This is actually not the stacktrace of the exception. This is
318329 // the "source chain". It represents the heirarchy of errors from the
319330 // app level to the lowest level such as IO. It does not represent all
320331 // of the callsites in the code that led to the error happening.
321332 // `std::error::Error::backtrace` is a nightly-only API and cannot be
322333 // used here until the feature is stabilized.
323- self . event_builder
324- . attributes
325- . push ( Key :: new ( FIELD_EXCEPTION_STACKTRACE ) . array ( chain. clone ( ) ) ) ;
334+ self . push_event_attribute ( Key :: new ( FIELD_EXCEPTION_STACKTRACE ) . array ( chain. clone ( ) ) ) ;
326335 }
327336
328337 if self . sem_conv_config . error_records_to_exceptions {
@@ -349,12 +358,8 @@ impl<'a, 'b> field::Visit for SpanEventVisitor<'a, 'b> {
349358 ) ) ;
350359 }
351360
352- self . event_builder
353- . attributes
354- . push ( Key :: new ( field. name ( ) ) . string ( error_msg) ) ;
355- self . event_builder
356- . attributes
357- . push ( Key :: new ( format ! ( "{}.chain" , field. name( ) ) ) . array ( chain) ) ;
361+ self . push_event_attribute ( Key :: new ( field. name ( ) ) . string ( error_msg) ) ;
362+ self . push_event_attribute ( Key :: new ( format ! ( "{}.chain" , field. name( ) ) ) . array ( chain) ) ;
358363 }
359364}
360365
@@ -913,34 +918,62 @@ where
913918 builder. trace_id = Some ( self . tracer . new_trace_id ( ) ) ;
914919 }
915920
921+ let limits = self . tracer . span_limits ( ) ;
922+ let attribute_limit = limits. map ( |limits| limits. max_attributes_per_span ) ;
916923 let builder_attrs = builder. attributes . get_or_insert ( Vec :: with_capacity (
917- attrs. fields ( ) . len ( ) + self . extra_span_attrs ( ) ,
924+ ( attrs. fields ( ) . len ( ) + self . extra_span_attrs ( ) ) . min (
925+ attribute_limit
926+ . map ( |limit| limit as usize )
927+ . unwrap_or ( usize:: MAX ) ,
928+ ) ,
918929 ) ) ;
919930
920931 if self . location {
921932 let meta = attrs. metadata ( ) ;
922933
923934 if let Some ( filename) = meta. file ( ) {
924- builder_attrs. push ( KeyValue :: new ( "code.filepath" , filename) ) ;
935+ push_unless_limit_reached (
936+ builder_attrs,
937+ attribute_limit,
938+ KeyValue :: new ( "code.filepath" , filename) ,
939+ ) ;
925940 }
926941
927942 if let Some ( module) = meta. module_path ( ) {
928- builder_attrs. push ( KeyValue :: new ( "code.namespace" , module) ) ;
943+ push_unless_limit_reached (
944+ builder_attrs,
945+ attribute_limit,
946+ KeyValue :: new ( "code.namespace" , module) ,
947+ ) ;
929948 }
930949
931950 if let Some ( line) = meta. line ( ) {
932- builder_attrs. push ( KeyValue :: new ( "code.lineno" , line as i64 ) ) ;
951+ push_unless_limit_reached (
952+ builder_attrs,
953+ attribute_limit,
954+ KeyValue :: new ( "code.lineno" , line as i64 ) ,
955+ ) ;
933956 }
934957 }
935958
936959 if self . with_threads {
937- THREAD_ID . with ( |id| builder_attrs. push ( KeyValue :: new ( "thread.id" , * * id as i64 ) ) ) ;
960+ THREAD_ID . with ( |id| {
961+ push_unless_limit_reached (
962+ builder_attrs,
963+ attribute_limit,
964+ KeyValue :: new ( "thread.id" , * * id as i64 ) ,
965+ )
966+ } ) ;
938967 if let Some ( name) = std:: thread:: current ( ) . name ( ) {
939968 // TODO(eliza): it's a bummer that we have to allocate here, but
940969 // we can't easily get the string as a `static`. it would be
941970 // nice if `opentelemetry` could also take `Arc<str>`s as
942971 // `String` values...
943- builder_attrs. push ( KeyValue :: new ( "thread.name" , name. to_string ( ) ) ) ;
972+ push_unless_limit_reached (
973+ builder_attrs,
974+ attribute_limit,
975+ KeyValue :: new ( "thread.name" , name. to_string ( ) ) ,
976+ ) ;
944977 }
945978 }
946979
@@ -950,7 +983,7 @@ where
950983 sem_conv_config : self . sem_conv_config ,
951984 } ) ;
952985
953- updates. update ( & mut builder) ;
986+ updates. update ( & mut builder, limits ) ;
954987 extensions. insert ( OtelData { builder, parent_cx } ) ;
955988 }
956989
@@ -996,7 +1029,7 @@ where
9961029 } ) ;
9971030 let mut extensions = span. extensions_mut ( ) ;
9981031 if let Some ( data) = extensions. get_mut :: < OtelData > ( ) {
999- updates. update ( & mut data. builder ) ;
1032+ updates. update ( & mut data. builder , self . tracer . span_limits ( ) ) ;
10001033 }
10011034 }
10021035
@@ -1024,8 +1057,18 @@ where
10241057 . clone ( ) ;
10251058 let follows_link = otel:: Link :: new ( follows_context, Vec :: new ( ) ) ;
10261059 if let Some ( ref mut links) = data. builder . links {
1027- links. push ( follows_link) ;
1028- } else {
1060+ push_unless_limit_reached (
1061+ links,
1062+ self . tracer
1063+ . span_limits ( )
1064+ . map ( |limits| limits. max_links_per_span ) ,
1065+ follows_link,
1066+ ) ;
1067+ } else if self
1068+ . tracer
1069+ . span_limits ( )
1070+ . map_or ( true , |limits| limits. max_links_per_span > 0 )
1071+ {
10291072 data. builder . links = Some ( vec ! [ follows_link] ) ;
10301073 }
10311074 }
@@ -1068,6 +1111,8 @@ where
10681111 #[ cfg( not( feature = "tracing-log" ) ) ]
10691112 let target = target. string ( meta. target ( ) ) ;
10701113
1114+ let limits = self . tracer . span_limits ( ) ;
1115+
10711116 let mut otel_event = otel:: Event :: new (
10721117 String :: new ( ) ,
10731118 crate :: time:: now ( ) ,
@@ -1080,6 +1125,7 @@ where
10801125 event_builder : & mut otel_event,
10811126 span_builder_updates : & mut builder_updates,
10821127 sem_conv_config : self . sem_conv_config ,
1128+ limits,
10831129 } ) ;
10841130
10851131 let mut extensions = span. extensions_mut ( ) ;
@@ -1095,7 +1141,21 @@ where
10951141 }
10961142
10971143 if let Some ( builder_updates) = builder_updates {
1098- builder_updates. update ( builder) ;
1144+ builder_updates. update ( builder, limits) ;
1145+ }
1146+
1147+ if builder
1148+ . events
1149+ . as_ref ( )
1150+ . map ( |events| events. len ( ) )
1151+ . zip ( limits)
1152+ . map_or ( false , |( current_length, limits) | {
1153+ current_length >= limits. max_events_per_span as usize
1154+ } )
1155+ {
1156+ // We have reached the configured limit for events so there is no point in storing any more.
1157+ // This is however the earliest we can abort this as the event can change e.g. the span's status.
1158+ return ;
10991159 }
11001160
11011161 if self . location {
@@ -1112,20 +1172,30 @@ where
11121172 ) ,
11131173 } ;
11141174
1175+ let event_attributes_limit =
1176+ limits. map ( |limits| limits. max_attributes_per_event ) ;
1177+ let event_attributes = & mut otel_event. attributes ;
1178+
11151179 if let Some ( file) = file {
1116- otel_event
1117- . attributes
1118- . push ( KeyValue :: new ( "code.filepath" , file) ) ;
1180+ push_unless_limit_reached (
1181+ event_attributes,
1182+ event_attributes_limit,
1183+ KeyValue :: new ( "code.filepath" , file) ,
1184+ ) ;
11191185 }
11201186 if let Some ( module) = module {
1121- otel_event
1122- . attributes
1123- . push ( KeyValue :: new ( "code.namespace" , module) ) ;
1187+ push_unless_limit_reached (
1188+ event_attributes,
1189+ event_attributes_limit,
1190+ KeyValue :: new ( "code.namespace" , module) ,
1191+ ) ;
11241192 }
11251193 if let Some ( line) = meta. line ( ) {
1126- otel_event
1127- . attributes
1128- . push ( KeyValue :: new ( "code.lineno" , line as i64 ) ) ;
1194+ push_unless_limit_reached (
1195+ event_attributes,
1196+ event_attributes_limit,
1197+ KeyValue :: new ( "code.lineno" , line as i64 ) ,
1198+ ) ;
11291199 }
11301200 }
11311201
@@ -1159,8 +1229,17 @@ where
11591229 let attributes = builder
11601230 . attributes
11611231 . get_or_insert_with ( || Vec :: with_capacity ( 2 ) ) ;
1162- attributes. push ( KeyValue :: new ( busy_ns, timings. busy ) ) ;
1163- attributes. push ( KeyValue :: new ( idle_ns, timings. idle ) ) ;
1232+ let limits = self . tracer . span_limits ( ) ;
1233+ push_unless_limit_reached (
1234+ attributes,
1235+ limits. map ( |limits| limits. max_attributes_per_span ) ,
1236+ KeyValue :: new ( busy_ns, timings. busy ) ,
1237+ ) ;
1238+ push_unless_limit_reached (
1239+ attributes,
1240+ limits. map ( |limits| limits. max_attributes_per_span ) ,
1241+ KeyValue :: new ( idle_ns, timings. idle ) ,
1242+ ) ;
11641243 }
11651244 }
11661245
0 commit comments