53
53
import opentelemetry .trace as trace_api
54
54
from opentelemetry .sdk .trace import Event
55
55
from opentelemetry .sdk .trace .export import Span , SpanExporter , SpanExportResult
56
+ from opentelemetry .sdk .util import BoundedDict
56
57
from opentelemetry .util import types
57
58
58
59
logger = logging .getLogger (__name__ )
59
60
61
+ MAX_NUM_LINKS = 128
62
+ MAX_NUM_EVENTS = 32
63
+ MAX_EVENT_ATTRS = 4
64
+ MAX_LINK_ATTRS = 32
65
+ MAX_SPAN_ATTRS = 32
66
+
60
67
61
68
class CloudTraceSpanExporter (SpanExporter ):
62
69
"""Cloud Trace span exporter for OpenTelemetry.
@@ -129,7 +136,11 @@ def _translate_to_cloud_trace(
129
136
start_time = _get_time_from_ns (span .start_time )
130
137
end_time = _get_time_from_ns (span .end_time )
131
138
132
- attributes = _extract_attributes (span .attributes )
139
+ if len (span .attributes ) > MAX_SPAN_ATTRS :
140
+ logger .warning (
141
+ "Span has more then %s attributes, some will be truncated" ,
142
+ MAX_SPAN_ATTRS ,
143
+ )
133
144
134
145
cloud_trace_spans .append (
135
146
{
@@ -141,7 +152,9 @@ def _translate_to_cloud_trace(
141
152
"start_time" : start_time ,
142
153
"end_time" : end_time ,
143
154
"parent_span_id" : parent_id ,
144
- "attributes" : attributes ,
155
+ "attributes" : _extract_attributes (
156
+ span .attributes , MAX_SPAN_ATTRS
157
+ ),
145
158
"links" : _extract_links (span .links ),
146
159
"status" : _extract_status (span .status ),
147
160
"time_events" : _extract_events (span .events ),
@@ -185,7 +198,9 @@ def _get_truncatable_str_object(str_to_convert: str, max_length: int):
185
198
186
199
def _truncate_str (str_to_check : str , limit : int ) -> Tuple [str , int ]:
187
200
"""Check the length of a string. If exceeds limit, then truncate it."""
188
- return str_to_check [:limit ], max (0 , len (str_to_check ) - limit )
201
+ encoded = str_to_check .encode ("utf-8" )
202
+ truncated_str = encoded [:limit ].decode ("utf-8" , errors = "ignore" )
203
+ return truncated_str , len (encoded ) - len (truncated_str .encode ("utf-8" ))
189
204
190
205
191
206
def _extract_status (status : trace_api .Status ) -> Status :
@@ -205,30 +220,56 @@ def _extract_links(links: Sequence[trace_api.Link]) -> ProtoSpan.Links:
205
220
if not links :
206
221
return None
207
222
extracted_links = []
223
+ dropped_links = 0
224
+ if len (links ) > MAX_NUM_LINKS :
225
+ logger .warning (
226
+ "Exporting more then %s links, some will be truncated" ,
227
+ MAX_NUM_LINKS ,
228
+ )
229
+ dropped_links = len (links ) - MAX_NUM_LINKS
230
+ links = links [:MAX_NUM_LINKS ]
208
231
for link in links :
232
+ if len (link .attributes ) > MAX_LINK_ATTRS :
233
+ logger .warning (
234
+ "Link has more then %s attributes, some will be truncated" ,
235
+ MAX_LINK_ATTRS ,
236
+ )
209
237
trace_id = _get_hexadecimal_trace_id (link .context .trace_id )
210
238
span_id = _get_hexadecimal_span_id (link .context .span_id )
211
239
extracted_links .append (
212
240
{
213
241
"trace_id" : trace_id ,
214
242
"span_id" : span_id ,
215
243
"type" : "TYPE_UNSPECIFIED" ,
216
- "attributes" : _extract_attributes (link .attributes ),
244
+ "attributes" : _extract_attributes (
245
+ link .attributes , MAX_LINK_ATTRS
246
+ ),
217
247
}
218
248
)
219
- return ProtoSpan .Links (link = extracted_links , dropped_links_count = 0 )
249
+ return ProtoSpan .Links (
250
+ link = extracted_links , dropped_links_count = dropped_links
251
+ )
220
252
221
253
222
254
def _extract_events (events : Sequence [Event ]) -> ProtoSpan .TimeEvents :
223
255
"""Convert span.events to dict."""
224
256
if not events :
225
257
return None
226
258
logs = []
259
+ dropped_annontations = 0
260
+ if len (events ) > MAX_NUM_EVENTS :
261
+ logger .warning (
262
+ "Exporting more then %s annotations, some will be truncated" ,
263
+ MAX_NUM_EVENTS ,
264
+ )
265
+ dropped_annontations = len (events ) - MAX_NUM_EVENTS
266
+ events = events [:MAX_NUM_EVENTS ]
227
267
for event in events :
228
- if len (event .attributes ) > 4 :
268
+ if len (event .attributes ) > MAX_EVENT_ATTRS :
229
269
logger .warning (
230
- "Event %s has more then 4 attributes, some will be truncated" ,
270
+ "Event %s has more then %s attributes, some will be truncated" ,
231
271
event .name ,
272
+ MAX_EVENT_ATTRS ,
232
273
)
233
274
logs .append (
234
275
{
@@ -237,28 +278,35 @@ def _extract_events(events: Sequence[Event]) -> ProtoSpan.TimeEvents:
237
278
"description" : _get_truncatable_str_object (
238
279
event .name , 256
239
280
),
240
- "attributes" : _extract_attributes (event .attributes ),
281
+ "attributes" : _extract_attributes (
282
+ event .attributes , MAX_EVENT_ATTRS
283
+ ),
241
284
},
242
285
}
243
286
)
244
287
return ProtoSpan .TimeEvents (
245
288
time_event = logs ,
246
- dropped_annotations_count = 0 ,
289
+ dropped_annotations_count = dropped_annontations ,
247
290
dropped_message_events_count = 0 ,
248
291
)
249
292
250
293
251
- def _extract_attributes (attrs : types .Attributes ) -> ProtoSpan .Attributes :
294
+ def _extract_attributes (
295
+ attrs : types .Attributes , num_attrs_limit : int
296
+ ) -> ProtoSpan .Attributes :
252
297
"""Convert span.attributes to dict."""
253
- attributes_dict = {}
298
+ attributes_dict = BoundedDict ( num_attrs_limit )
254
299
255
300
for key , value in attrs .items ():
256
301
key = _truncate_str (key , 128 )[0 ]
257
302
value = _format_attribute_value (value )
258
303
259
304
if value is not None :
260
305
attributes_dict [key ] = value
261
- return ProtoSpan .Attributes (attribute_map = attributes_dict )
306
+ return ProtoSpan .Attributes (
307
+ attribute_map = attributes_dict ,
308
+ dropped_attributes_count = len (attrs ) - len (attributes_dict ),
309
+ )
262
310
263
311
264
312
def _format_attribute_value (value : types .AttributeValue ) -> AttributeValue :
0 commit comments