rebrandly-otel 0.1.14__py3-none-any.whl → 0.1.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rebrandly-otel might be problematic. Click here for more details.
- rebrandly_otel/rebrandly_otel.py +106 -76
- {rebrandly_otel-0.1.14.dist-info → rebrandly_otel-0.1.16.dist-info}/METADATA +1 -1
- {rebrandly_otel-0.1.14.dist-info → rebrandly_otel-0.1.16.dist-info}/RECORD +6 -6
- {rebrandly_otel-0.1.14.dist-info → rebrandly_otel-0.1.16.dist-info}/WHEEL +0 -0
- {rebrandly_otel-0.1.14.dist-info → rebrandly_otel-0.1.16.dist-info}/licenses/LICENSE +0 -0
- {rebrandly_otel-0.1.14.dist-info → rebrandly_otel-0.1.16.dist-info}/top_level.txt +0 -0
rebrandly_otel/rebrandly_otel.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# rebrandly_otel.py
|
|
2
2
|
"""
|
|
3
3
|
Rebrandly OpenTelemetry SDK - Simplified instrumentation for Rebrandly services.
|
|
4
|
+
Updated for consistency with Node.js SDK
|
|
4
5
|
"""
|
|
5
6
|
import time
|
|
6
7
|
import psutil
|
|
@@ -99,8 +100,6 @@ class RebrandlyOTEL:
|
|
|
99
100
|
return wrapper
|
|
100
101
|
return decorator
|
|
101
102
|
|
|
102
|
-
# Fix for the lambda_handler method in rebrandly_otel.py
|
|
103
|
-
# Replace the lambda_handler method (around line 132) with this fixed version:
|
|
104
103
|
def lambda_handler(self,
|
|
105
104
|
name: Optional[str] = None,
|
|
106
105
|
attributes: Optional[Dict[str, Any]] = None,
|
|
@@ -132,6 +131,7 @@ class RebrandlyOTEL:
|
|
|
132
131
|
|
|
133
132
|
# Handle context extraction from AWS events
|
|
134
133
|
token = None
|
|
134
|
+
|
|
135
135
|
if not skip_aws_link and event and isinstance(event, dict) and 'Records' in event:
|
|
136
136
|
first_record = event['Records'][0] if event['Records'] else None
|
|
137
137
|
if first_record:
|
|
@@ -160,11 +160,10 @@ class RebrandlyOTEL:
|
|
|
160
160
|
from opentelemetry import propagate, context as otel_context
|
|
161
161
|
extracted_context = propagate.extract(carrier)
|
|
162
162
|
token = otel_context.attach(extracted_context)
|
|
163
|
-
span_attributes['message.has_trace_context'] = True
|
|
164
163
|
|
|
165
164
|
result = None
|
|
166
165
|
try:
|
|
167
|
-
# Increment invocation counter
|
|
166
|
+
# Increment invocation counter with standardized metric name
|
|
168
167
|
self.meter.GlobalMetrics.invocations.add(1, {'function': span_name})
|
|
169
168
|
|
|
170
169
|
# Create and execute within span
|
|
@@ -173,26 +172,63 @@ class RebrandlyOTEL:
|
|
|
173
172
|
attributes=span_attributes,
|
|
174
173
|
kind=kind
|
|
175
174
|
) as span:
|
|
176
|
-
# Add invocation start event
|
|
177
|
-
|
|
175
|
+
# Add invocation start event with standardized attributes
|
|
176
|
+
start_event_attrs = {
|
|
178
177
|
'event.type': type(event).__name__ if event else 'None'
|
|
179
|
-
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# Add records count if present
|
|
181
|
+
if event and isinstance(event, dict) and 'Records' in event:
|
|
182
|
+
start_event_attrs['event.records'] = f"{len(event['Records'])}"
|
|
183
|
+
|
|
184
|
+
span.add_event("lambda.invocation.start", start_event_attrs)
|
|
180
185
|
|
|
181
186
|
# Execute handler
|
|
182
187
|
result = func(event, lambda_context)
|
|
183
188
|
|
|
184
|
-
# Process result
|
|
185
|
-
|
|
189
|
+
# Process result with standardized attributes
|
|
190
|
+
success = True
|
|
191
|
+
complete_event_attrs = {}
|
|
192
|
+
|
|
193
|
+
if isinstance(result, dict) and 'statusCode' in result:
|
|
194
|
+
span.set_attribute("http.status_code", result['statusCode'])
|
|
195
|
+
complete_event_attrs['status_code'] = result['statusCode']
|
|
196
|
+
|
|
197
|
+
# Set span status based on HTTP status code
|
|
198
|
+
if result['statusCode'] >= 400:
|
|
199
|
+
success = False
|
|
200
|
+
span.set_status(Status(StatusCode.ERROR, f"HTTP {result['statusCode']}"))
|
|
201
|
+
else:
|
|
202
|
+
span.set_status(Status(StatusCode.OK))
|
|
203
|
+
else:
|
|
204
|
+
span.set_status(Status(StatusCode.OK))
|
|
205
|
+
|
|
206
|
+
# Add completion event with success indicator
|
|
207
|
+
complete_event_attrs['success'] = success
|
|
208
|
+
span.add_event("lambda.invocation.complete", complete_event_attrs)
|
|
209
|
+
|
|
210
|
+
# Increment success counter with standardized metric
|
|
211
|
+
self.meter.GlobalMetrics.successful_invocations.add(1, {'function': span_name})
|
|
186
212
|
|
|
187
213
|
return result
|
|
188
214
|
|
|
189
215
|
except Exception as e:
|
|
190
|
-
# Increment error counter
|
|
216
|
+
# Increment error counter with standardized metric
|
|
191
217
|
self.meter.GlobalMetrics.error_invocations.add(1, {
|
|
192
218
|
'function': span_name,
|
|
193
219
|
'error': type(e).__name__
|
|
194
220
|
})
|
|
195
221
|
|
|
222
|
+
# Add failed completion event with error attribute (THIS IS THE KEY ADDITION)
|
|
223
|
+
span.add_event("lambda.invocation.complete", {
|
|
224
|
+
'success': False,
|
|
225
|
+
'error': type(e).__name__
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
# Record the exception in the span
|
|
229
|
+
span.record_exception(e)
|
|
230
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
231
|
+
|
|
196
232
|
# Log error
|
|
197
233
|
self.logger.logger.error(f"Lambda execution failed: {e}", exc_info=True)
|
|
198
234
|
raise
|
|
@@ -203,81 +239,68 @@ class RebrandlyOTEL:
|
|
|
203
239
|
from opentelemetry import context as otel_context
|
|
204
240
|
otel_context.detach(token)
|
|
205
241
|
|
|
206
|
-
# Record duration
|
|
242
|
+
# Record duration with standardized metric
|
|
207
243
|
duration = (datetime.now() - start_time).total_seconds() * 1000
|
|
208
244
|
self.meter.GlobalMetrics.duration.record(duration, {'function': span_name})
|
|
209
245
|
|
|
210
246
|
# Force flush if enabled
|
|
211
247
|
if auto_flush:
|
|
212
|
-
self.logger.logger.info(f"[OTEL] Lambda '{span_name}' completed in {duration:.2f}ms, flushing...")
|
|
248
|
+
self.logger.logger.info(f"[Rebrandly OTEL] Lambda '{span_name}' completed in {duration:.2f}ms, flushing...")
|
|
213
249
|
flush_success = self.force_flush(timeout_millis=1000)
|
|
214
250
|
if not flush_success:
|
|
215
|
-
self.logger.logger.warning("[OTEL] Force flush may not have completed fully")
|
|
251
|
+
self.logger.logger.warning("[Rebrandly OTEL] Force flush may not have completed fully")
|
|
216
252
|
|
|
217
253
|
return wrapper
|
|
218
254
|
return decorator
|
|
219
255
|
|
|
220
|
-
def _process_lambda_result(self, result, span_context, span_name):
|
|
221
|
-
"""Helper method to process Lambda result and update span accordingly"""
|
|
222
|
-
if isinstance(result, dict):
|
|
223
|
-
if 'statusCode' in result:
|
|
224
|
-
span_context.set_attribute("http.status_code", result['statusCode'])
|
|
225
|
-
# Set span status based on HTTP status code
|
|
226
|
-
if result['statusCode'] >= 400:
|
|
227
|
-
span_context.set_status(Status(StatusCode.ERROR, f"HTTP {result['statusCode']}"))
|
|
228
|
-
else:
|
|
229
|
-
span_context.set_status(Status(StatusCode.OK))
|
|
230
|
-
|
|
231
|
-
# Add completion event
|
|
232
|
-
span_context.add_event("lambda.invocation.complete",
|
|
233
|
-
attributes={"success": result.get('statusCode', 200) < 400})
|
|
234
|
-
else:
|
|
235
|
-
span_context.set_status(Status(StatusCode.OK))
|
|
236
|
-
span_context.add_event("lambda.invocation.complete", attributes={"success": True})
|
|
237
|
-
|
|
238
|
-
# Increment success counter
|
|
239
|
-
self.meter.GlobalMetrics.successful_invocations.add(1, {'function': span_name})
|
|
240
|
-
|
|
241
256
|
def aws_message_handler(self,
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
257
|
+
name: Optional[str] = None,
|
|
258
|
+
attributes: Optional[Dict[str, Any]] = None,
|
|
259
|
+
kind: SpanKind = SpanKind.CONSUMER,
|
|
260
|
+
auto_flush: bool = True):
|
|
246
261
|
"""
|
|
247
|
-
|
|
262
|
+
Decorator for AWS message handlers (SQS/SNS record processing).
|
|
263
|
+
Requires a record object parameter to the function.
|
|
248
264
|
"""
|
|
249
265
|
def decorator(func):
|
|
250
266
|
@functools.wraps(func)
|
|
251
267
|
def wrapper(record=None, *args, **kwargs):
|
|
252
268
|
# Determine span name
|
|
253
|
-
span_name = name or f"
|
|
269
|
+
span_name = name or f"message.{func.__name__}"
|
|
254
270
|
start_func = datetime.now()
|
|
255
271
|
|
|
256
272
|
# Build span attributes
|
|
257
273
|
span_attributes = attributes or {}
|
|
274
|
+
span_attributes['messaging.operation'] = 'process'
|
|
258
275
|
|
|
259
276
|
result = None
|
|
260
277
|
try:
|
|
261
|
-
# Increment invocations counter
|
|
262
|
-
print('XXX 2')
|
|
278
|
+
# Increment invocations counter with standardized metric
|
|
263
279
|
self.meter.GlobalMetrics.invocations.add(1, {'handler': span_name})
|
|
264
280
|
|
|
265
281
|
# Create span and execute function
|
|
266
282
|
span_function = self.span
|
|
267
|
-
if record is not None and 'MessageAttributes' in record:
|
|
283
|
+
if record is not None and ('MessageAttributes' in record or 'messageAttributes' in record):
|
|
268
284
|
span_function = self.aws_message_span
|
|
269
285
|
|
|
270
286
|
with span_function(span_name, message=record, attributes=span_attributes, kind=kind) as span_context:
|
|
287
|
+
# Add processing start event with standardized name
|
|
288
|
+
span_context.add_event("message.processing.start", {})
|
|
289
|
+
|
|
271
290
|
# Execute the actual handler function
|
|
272
291
|
result = func(record, *args, **kwargs)
|
|
273
292
|
|
|
274
|
-
#
|
|
293
|
+
# Process result
|
|
294
|
+
success = True
|
|
295
|
+
complete_event_attrs = {}
|
|
296
|
+
|
|
275
297
|
if result and isinstance(result, dict):
|
|
276
298
|
if 'statusCode' in result:
|
|
277
|
-
span_context.set_attribute("
|
|
299
|
+
span_context.set_attribute("http.status_code", result['statusCode'])
|
|
278
300
|
|
|
279
301
|
# Set span status based on status code
|
|
280
302
|
if result['statusCode'] >= 400:
|
|
303
|
+
success = False
|
|
281
304
|
span_context.set_status(
|
|
282
305
|
Status(StatusCode.ERROR, f"Handler returned {result['statusCode']}")
|
|
283
306
|
)
|
|
@@ -286,32 +309,49 @@ class RebrandlyOTEL:
|
|
|
286
309
|
|
|
287
310
|
# Add custom result attributes if present
|
|
288
311
|
if 'processed' in result:
|
|
289
|
-
|
|
312
|
+
complete_event_attrs['processed'] = result['processed']
|
|
313
|
+
span_context.set_attribute("message.processed", result['processed'])
|
|
290
314
|
if 'skipped' in result:
|
|
291
|
-
|
|
315
|
+
complete_event_attrs['skipped'] = result['skipped']
|
|
316
|
+
span_context.set_attribute("message.skipped", result['skipped'])
|
|
317
|
+
else:
|
|
318
|
+
span_context.set_status(Status(StatusCode.OK))
|
|
292
319
|
|
|
293
|
-
# Add completion event
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
})
|
|
320
|
+
# Add completion event with standardized name
|
|
321
|
+
complete_event_attrs['success'] = success
|
|
322
|
+
span_context.add_event("message.processing.complete", complete_event_attrs)
|
|
297
323
|
|
|
298
|
-
# Increment success counter
|
|
324
|
+
# Increment success counter with standardized metric
|
|
299
325
|
self.meter.GlobalMetrics.successful_invocations.add(1, {'handler': span_name})
|
|
300
326
|
|
|
301
327
|
return result
|
|
302
328
|
|
|
303
329
|
except Exception as e:
|
|
304
|
-
# Increment error counter
|
|
305
|
-
self.meter.GlobalMetrics.error_invocations.add(1, {
|
|
330
|
+
# Increment error counter with standardized metric
|
|
331
|
+
self.meter.GlobalMetrics.error_invocations.add(1, {
|
|
332
|
+
'handler': span_name,
|
|
333
|
+
'error': type(e).__name__
|
|
334
|
+
})
|
|
306
335
|
|
|
307
336
|
# Record the exception in the span
|
|
308
|
-
span_context
|
|
309
|
-
|
|
337
|
+
if 'span_context' in locals():
|
|
338
|
+
span_context.record_exception(e)
|
|
339
|
+
span_context.set_status(Status(StatusCode.ERROR, str(e)))
|
|
340
|
+
|
|
341
|
+
# Add failed processing event
|
|
342
|
+
span_context.add_event("message.processing.complete", {
|
|
343
|
+
'success': False,
|
|
344
|
+
'error': type(e).__name__
|
|
345
|
+
})
|
|
310
346
|
|
|
311
347
|
# Re-raise the exception
|
|
312
348
|
raise
|
|
313
349
|
|
|
314
350
|
finally:
|
|
351
|
+
# Record duration with standardized metric
|
|
352
|
+
duration = (datetime.now() - start_func).total_seconds() * 1000
|
|
353
|
+
self.meter.GlobalMetrics.duration.record(duration, {'handler': span_name})
|
|
354
|
+
|
|
315
355
|
if auto_flush:
|
|
316
356
|
self.force_flush(start_datetime=start_func)
|
|
317
357
|
|
|
@@ -324,6 +364,7 @@ class RebrandlyOTEL:
|
|
|
324
364
|
This is CRITICAL for Lambda functions to ensure data is sent before function freezes.
|
|
325
365
|
|
|
326
366
|
Args:
|
|
367
|
+
start_datetime: Optional start time for system metrics capture
|
|
327
368
|
timeout_millis: Maximum time to wait for flush in milliseconds
|
|
328
369
|
|
|
329
370
|
Returns:
|
|
@@ -334,14 +375,15 @@ class RebrandlyOTEL:
|
|
|
334
375
|
if start_datetime is not None:
|
|
335
376
|
end_func = datetime.now()
|
|
336
377
|
duration = (end_func - start_datetime).total_seconds() * 1000
|
|
337
|
-
cpu_percent = psutil.cpu_percent(interval=1)
|
|
378
|
+
cpu_percent = psutil.cpu_percent(interval=0.1) # Shorter interval for Lambda
|
|
338
379
|
memory = psutil.virtual_memory()
|
|
339
380
|
|
|
340
381
|
# Record metrics using standardized names
|
|
341
382
|
self.meter.GlobalMetrics.duration.record(duration, {'source': 'force_flush'})
|
|
342
383
|
self.meter.GlobalMetrics.memory_usage_bytes.set(memory.used)
|
|
343
384
|
self.meter.GlobalMetrics.cpu_usage_percentage.set(cpu_percent)
|
|
344
|
-
|
|
385
|
+
|
|
386
|
+
print(f"Function duration: {duration}ms, Memory usage: {memory.percent}%, CPU usage: {cpu_percent}%")
|
|
345
387
|
|
|
346
388
|
try:
|
|
347
389
|
# Flush traces
|
|
@@ -379,9 +421,8 @@ class RebrandlyOTEL:
|
|
|
379
421
|
self._meter.shutdown()
|
|
380
422
|
if self._logger:
|
|
381
423
|
self._logger.shutdown()
|
|
382
|
-
print("[OTEL] Shutdown completed")
|
|
383
424
|
except Exception as e:
|
|
384
|
-
print(f"[OTEL] Error during shutdown: {e}")
|
|
425
|
+
print(f"[Rebrandly OTEL] Error during shutdown: {e}")
|
|
385
426
|
|
|
386
427
|
def _detect_lambda_trigger(self, event: Any) -> str:
|
|
387
428
|
"""Detect Lambda trigger type from event."""
|
|
@@ -455,6 +496,7 @@ class RebrandlyOTEL:
|
|
|
455
496
|
from opentelemetry import trace, context as otel_context
|
|
456
497
|
|
|
457
498
|
combined_attributes = attributes or {}
|
|
499
|
+
combined_attributes['messaging.operation'] = 'process'
|
|
458
500
|
|
|
459
501
|
# Extract message attributes for linking/attributes
|
|
460
502
|
if message and isinstance(message, dict):
|
|
@@ -463,16 +505,11 @@ class RebrandlyOTEL:
|
|
|
463
505
|
sns_msg = message['Sns']
|
|
464
506
|
if 'MessageId' in sns_msg:
|
|
465
507
|
combined_attributes['messaging.message_id'] = sns_msg['MessageId']
|
|
508
|
+
if 'Subject' in sns_msg:
|
|
509
|
+
combined_attributes['messaging.sns.subject'] = sns_msg['Subject']
|
|
466
510
|
if 'TopicArn' in sns_msg:
|
|
467
511
|
combined_attributes['messaging.destination'] = sns_msg['TopicArn']
|
|
468
|
-
combined_attributes['messaging.system'] = '
|
|
469
|
-
|
|
470
|
-
# Check for trace context in SNS
|
|
471
|
-
if 'MessageAttributes' in sns_msg:
|
|
472
|
-
for key, value in sns_msg['MessageAttributes'].items():
|
|
473
|
-
if key == 'traceparent' and 'Value' in value:
|
|
474
|
-
combined_attributes['message.traceparent'] = value['Value']
|
|
475
|
-
combined_attributes['message.has_trace_context'] = True
|
|
512
|
+
combined_attributes['messaging.system'] = 'sns'
|
|
476
513
|
|
|
477
514
|
elif 'messageId' in message:
|
|
478
515
|
# SQS message
|
|
@@ -480,18 +517,11 @@ class RebrandlyOTEL:
|
|
|
480
517
|
if 'eventSource' in message:
|
|
481
518
|
combined_attributes['messaging.system'] = message['eventSource']
|
|
482
519
|
|
|
483
|
-
# Check for trace context in SQS
|
|
484
|
-
if 'MessageAttributes' in message or 'messageAttributes' in message:
|
|
485
|
-
attrs = message.get('MessageAttributes') or message.get('messageAttributes', {})
|
|
486
|
-
for key, value in attrs.items():
|
|
487
|
-
if key == 'traceparent':
|
|
488
|
-
tp_value = value.get('StringValue') or value.get('stringValue', '')
|
|
489
|
-
combined_attributes['message.traceparent'] = tp_value
|
|
490
|
-
combined_attributes['message.has_trace_context'] = True
|
|
491
520
|
|
|
492
521
|
if 'awsRegion' in message:
|
|
493
522
|
combined_attributes['cloud.region'] = message['awsRegion']
|
|
494
523
|
|
|
524
|
+
|
|
495
525
|
# Use the tracer's start_span method directly to ensure it works
|
|
496
526
|
# This creates a child span of whatever is currently active
|
|
497
527
|
with self.tracer.start_span(
|
|
@@ -2,10 +2,10 @@ rebrandly_otel/__init__.py,sha256=NUlroPhnHAKVC_q2BiyhtURXw23w1YY1BJd45gtc9qM,27
|
|
|
2
2
|
rebrandly_otel/logs.py,sha256=92jaxzI5hCHnKHu3lsSAa7K_SPHQgL46AlQUESsYNds,3724
|
|
3
3
|
rebrandly_otel/metrics.py,sha256=57jwrn8e1u66BOO7fcFrmtE3Rpt4VDixG9I78K-zrUU,8537
|
|
4
4
|
rebrandly_otel/otel_utils.py,sha256=uJmfz2NspSnTVJXGKoaLUl1CYb0ow6VHKjmg2d_rdwg,1704
|
|
5
|
-
rebrandly_otel/rebrandly_otel.py,sha256=
|
|
5
|
+
rebrandly_otel/rebrandly_otel.py,sha256=A_TGyCQq752EeEvcsMmBWaWx2Z12J1hfmyYTFpYJFbo,23232
|
|
6
6
|
rebrandly_otel/traces.py,sha256=v582WtJv3t4Bn92vlDyZouibHtgWNxdRo_XmQCmSOEA,7126
|
|
7
|
-
rebrandly_otel-0.1.
|
|
8
|
-
rebrandly_otel-0.1.
|
|
9
|
-
rebrandly_otel-0.1.
|
|
10
|
-
rebrandly_otel-0.1.
|
|
11
|
-
rebrandly_otel-0.1.
|
|
7
|
+
rebrandly_otel-0.1.16.dist-info/licenses/LICENSE,sha256=KMXHvTwP62S2q-SG7CFfMU_09rUwxiqlM0izaYGdcgY,1103
|
|
8
|
+
rebrandly_otel-0.1.16.dist-info/METADATA,sha256=4SxmMqhoIM6EyjZc8_JWCvHHxATQvuXcKztv5gEqc18,9628
|
|
9
|
+
rebrandly_otel-0.1.16.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
rebrandly_otel-0.1.16.dist-info/top_level.txt,sha256=26PSC1gjVUl8tTH5QfKbFevjVV4E2yojoukEfiTScvM,15
|
|
11
|
+
rebrandly_otel-0.1.16.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|