rebrandly-otel 0.1.9__tar.gz → 0.1.11__tar.gz
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-0.1.9 → rebrandly_otel-0.1.11}/PKG-INFO +1 -1
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/rebrandly_otel.egg-info/PKG-INFO +1 -1
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/setup.py +1 -1
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/src/rebrandly_otel.py +108 -94
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/src/traces.py +1 -0
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/LICENSE +0 -0
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/README.md +0 -0
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/rebrandly_otel.egg-info/SOURCES.txt +0 -0
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/rebrandly_otel.egg-info/dependency_links.txt +0 -0
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/rebrandly_otel.egg-info/top_level.txt +0 -0
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/setup.cfg +0 -0
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/src/__init__.py +0 -0
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/src/logs.py +0 -0
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/src/metrics.py +0 -0
- {rebrandly_otel-0.1.9 → rebrandly_otel-0.1.11}/src/otel_utils.py +0 -0
|
@@ -99,6 +99,9 @@ class RebrandlyOTEL:
|
|
|
99
99
|
return wrapper
|
|
100
100
|
return decorator
|
|
101
101
|
|
|
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
|
+
|
|
102
105
|
def lambda_handler(self,
|
|
103
106
|
name: Optional[str] = None,
|
|
104
107
|
attributes: Optional[Dict[str, Any]] = None,
|
|
@@ -107,22 +110,6 @@ class RebrandlyOTEL:
|
|
|
107
110
|
skip_aws_link: bool = True):
|
|
108
111
|
"""
|
|
109
112
|
Decorator specifically for Lambda handlers with automatic flushing.
|
|
110
|
-
|
|
111
|
-
Args:
|
|
112
|
-
name: Optional span name (defaults to 'lambda.{function_name}')
|
|
113
|
-
attributes: Additional span attributes
|
|
114
|
-
kind: Span kind (defaults to SERVER)
|
|
115
|
-
auto_flush: If True, automatically flush after handler completes
|
|
116
|
-
|
|
117
|
-
Usage:
|
|
118
|
-
@lambda_handler()
|
|
119
|
-
def my_handler(event, context): ...
|
|
120
|
-
|
|
121
|
-
@lambda_handler(name="custom_span_name")
|
|
122
|
-
def my_handler(event, context): ...
|
|
123
|
-
|
|
124
|
-
@lambda_handler(name="my_span", attributes={"env": "prod"})
|
|
125
|
-
def my_handler(event, context): ...
|
|
126
113
|
"""
|
|
127
114
|
def decorator(func):
|
|
128
115
|
@functools.wraps(func)
|
|
@@ -150,50 +137,90 @@ class RebrandlyOTEL:
|
|
|
150
137
|
# Increment invocations counter
|
|
151
138
|
self.meter.GlobalMetrics.invocations.add(1, {'function': span_name})
|
|
152
139
|
|
|
153
|
-
#
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
else
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
140
|
+
# Determine if we should extract context from AWS message
|
|
141
|
+
extracted_context = None
|
|
142
|
+
if not skip_aws_link and event is not None and 'Records' in event and len(event['Records']) > 0:
|
|
143
|
+
first_record = event['Records'][0]
|
|
144
|
+
|
|
145
|
+
# Check different message formats
|
|
146
|
+
if 'messageAttributes' in first_record or 'MessageAttributes' in first_record:
|
|
147
|
+
# SQS format
|
|
148
|
+
extracted_context = self.extract_context(first_record)
|
|
149
|
+
elif 'Sns' in first_record and 'MessageAttributes' in first_record['Sns']:
|
|
150
|
+
# SNS format
|
|
151
|
+
extracted_context = self.extract_context(first_record)
|
|
152
|
+
|
|
153
|
+
# Create span - use extracted context if available
|
|
154
|
+
if extracted_context:
|
|
155
|
+
token = self.attach_context(extracted_context)
|
|
156
|
+
try:
|
|
157
|
+
with self.span(span_name, attributes=span_attributes, kind=kind) as span_context:
|
|
158
|
+
# Add event type as span event
|
|
159
|
+
if event is not None:
|
|
160
|
+
span_context.add_event("lambda.invocation.start",
|
|
161
|
+
attributes={"event.type": type(event).__name__})
|
|
162
|
+
|
|
163
|
+
result = func(event, context) if event is not None else func()
|
|
164
|
+
|
|
165
|
+
# Handle result
|
|
166
|
+
self._process_lambda_result(result, span_context, span_name)
|
|
167
|
+
finally:
|
|
168
|
+
self.detach_context(token)
|
|
169
|
+
else:
|
|
170
|
+
# No context extraction needed
|
|
171
|
+
with self.span(span_name, attributes=span_attributes, kind=kind) as span_context:
|
|
172
|
+
# Add event type as span event
|
|
173
|
+
if event is not None:
|
|
174
|
+
span_context.add_event("lambda.invocation.start",
|
|
175
|
+
attributes={"event.type": type(event).__name__})
|
|
176
|
+
|
|
177
|
+
result = func(event, context) if event is not None else func()
|
|
178
|
+
|
|
179
|
+
# Handle result
|
|
180
|
+
self._process_lambda_result(result, span_context, span_name)
|
|
181
|
+
|
|
182
|
+
return result
|
|
183
183
|
|
|
184
184
|
except Exception as e:
|
|
185
185
|
# Increment error counter
|
|
186
186
|
self.meter.GlobalMetrics.error_invocations.add(1, {'function': span_name, 'error': type(e).__name__})
|
|
187
|
+
|
|
188
|
+
# Log error with context
|
|
189
|
+
self.logger.logger.error(f"Lambda execution failed: {e}", exc_info=True)
|
|
187
190
|
raise
|
|
188
191
|
|
|
189
192
|
finally:
|
|
193
|
+
# Record duration
|
|
194
|
+
duration = (datetime.now() - start_func).total_seconds() * 1000
|
|
195
|
+
self.meter.GlobalMetrics.duration.record(duration, {'function': span_name})
|
|
196
|
+
|
|
190
197
|
if auto_flush:
|
|
191
|
-
self.logger.logger.info(f"[OTEL] Lambda handler '{span_name}' completed, flushing telemetry...")
|
|
192
198
|
self.force_flush(start_datetime=start_func)
|
|
193
199
|
|
|
194
200
|
return wrapper
|
|
195
201
|
return decorator
|
|
196
202
|
|
|
203
|
+
def _process_lambda_result(self, result, span_context, span_name):
|
|
204
|
+
"""Helper method to process Lambda result and update span accordingly"""
|
|
205
|
+
if isinstance(result, dict):
|
|
206
|
+
if 'statusCode' in result:
|
|
207
|
+
span_context.set_attribute("http.status_code", result['statusCode'])
|
|
208
|
+
# Set span status based on HTTP status code
|
|
209
|
+
if result['statusCode'] >= 400:
|
|
210
|
+
span_context.set_status(Status(StatusCode.ERROR, f"HTTP {result['statusCode']}"))
|
|
211
|
+
else:
|
|
212
|
+
span_context.set_status(Status(StatusCode.OK))
|
|
213
|
+
|
|
214
|
+
# Add completion event
|
|
215
|
+
span_context.add_event("lambda.invocation.complete",
|
|
216
|
+
attributes={"success": result.get('statusCode', 200) < 400})
|
|
217
|
+
else:
|
|
218
|
+
span_context.set_status(Status(StatusCode.OK))
|
|
219
|
+
span_context.add_event("lambda.invocation.complete", attributes={"success": True})
|
|
220
|
+
|
|
221
|
+
# Increment success counter
|
|
222
|
+
self.meter.GlobalMetrics.successful_invocations.add(1, {'function': span_name})
|
|
223
|
+
|
|
197
224
|
def aws_message_handler(self,
|
|
198
225
|
name: Optional[str] = None,
|
|
199
226
|
attributes: Optional[Dict[str, Any]] = None,
|
|
@@ -269,7 +296,6 @@ class RebrandlyOTEL:
|
|
|
269
296
|
|
|
270
297
|
finally:
|
|
271
298
|
if auto_flush:
|
|
272
|
-
self.logger.logger.info(f"[OTEL] Lambda handler '{span_name}' completed, flushing telemetry...")
|
|
273
299
|
self.force_flush(start_datetime=start_func)
|
|
274
300
|
|
|
275
301
|
return wrapper
|
|
@@ -298,7 +324,7 @@ class RebrandlyOTEL:
|
|
|
298
324
|
self.meter.GlobalMetrics.duration.record(duration, {'source': 'force_flush'})
|
|
299
325
|
self.meter.GlobalMetrics.memory_usage_bytes.set(memory.used)
|
|
300
326
|
self.meter.GlobalMetrics.cpu_usage_percentage.set(cpu_percent)
|
|
301
|
-
self.logger.logger.info(f"
|
|
327
|
+
self.logger.logger.info(f"Function duration: {duration}ms, Memory usage: {memory.percent}%, CPU usage: {cpu_percent}%")
|
|
302
328
|
|
|
303
329
|
try:
|
|
304
330
|
# Flush traces
|
|
@@ -336,9 +362,9 @@ class RebrandlyOTEL:
|
|
|
336
362
|
self._meter.shutdown()
|
|
337
363
|
if self._logger:
|
|
338
364
|
self._logger.shutdown()
|
|
339
|
-
|
|
365
|
+
print("[OTEL] Shutdown completed")
|
|
340
366
|
except Exception as e:
|
|
341
|
-
|
|
367
|
+
print(f"[OTEL] Error during shutdown: {e}")
|
|
342
368
|
|
|
343
369
|
def _detect_lambda_trigger(self, event: Any) -> str:
|
|
344
370
|
"""Detect Lambda trigger type from event."""
|
|
@@ -407,43 +433,15 @@ class RebrandlyOTEL:
|
|
|
407
433
|
message: Dict[str, Any]=None,
|
|
408
434
|
attributes: Optional[Dict[str, Any]] = None,
|
|
409
435
|
kind: SpanKind = SpanKind.CONSUMER):
|
|
410
|
-
"""Create span from AWS message
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if message and isinstance(message, dict):
|
|
414
|
-
carrier = {}
|
|
436
|
+
"""Create span from AWS message - properly handling trace context."""
|
|
437
|
+
|
|
438
|
+
from opentelemetry import trace, context as otel_context
|
|
415
439
|
|
|
416
|
-
# Check for trace context in different possible locations
|
|
417
|
-
if 'MessageAttributes' in message:
|
|
418
|
-
# SQS format
|
|
419
|
-
for key, value in message.get('MessageAttributes', {}).items():
|
|
420
|
-
if isinstance(value, dict) and 'StringValue' in value:
|
|
421
|
-
carrier[key] = value['StringValue']
|
|
422
|
-
elif 'Sns' in message and 'MessageAttributes' in message['Sns']:
|
|
423
|
-
# SNS format - MessageAttributes are nested under 'Sns'
|
|
424
|
-
for key, value in message['Sns'].get('MessageAttributes', {}).items():
|
|
425
|
-
if isinstance(value, dict):
|
|
426
|
-
# SNS uses 'Value' instead of 'StringValue'
|
|
427
|
-
if 'Value' in value:
|
|
428
|
-
carrier[key] = value['Value']
|
|
429
|
-
elif 'StringValue' in value:
|
|
430
|
-
carrier[key] = value['StringValue']
|
|
431
|
-
elif 'messageAttributes' in message:
|
|
432
|
-
# Alternative format
|
|
433
|
-
for key, value in message.get('messageAttributes', {}).items():
|
|
434
|
-
if isinstance(value, dict) and 'stringValue' in value:
|
|
435
|
-
carrier[key] = value['stringValue']
|
|
436
|
-
|
|
437
|
-
# If we found trace context, attach it
|
|
438
|
-
if carrier:
|
|
439
|
-
token = self.attach_context(carrier)
|
|
440
|
-
|
|
441
|
-
# Create a span with the potentially extracted context
|
|
442
440
|
combined_attributes = attributes or {}
|
|
443
441
|
|
|
444
|
-
#
|
|
442
|
+
# Extract message attributes for linking/attributes
|
|
445
443
|
if message and isinstance(message, dict):
|
|
446
|
-
# Add
|
|
444
|
+
# Add message-specific attributes
|
|
447
445
|
if 'Sns' in message:
|
|
448
446
|
sns_msg = message['Sns']
|
|
449
447
|
if 'MessageId' in sns_msg:
|
|
@@ -451,24 +449,40 @@ class RebrandlyOTEL:
|
|
|
451
449
|
if 'TopicArn' in sns_msg:
|
|
452
450
|
combined_attributes['messaging.destination'] = sns_msg['TopicArn']
|
|
453
451
|
combined_attributes['messaging.system'] = 'aws_sns'
|
|
454
|
-
|
|
452
|
+
|
|
453
|
+
# Check for trace context in SNS
|
|
454
|
+
if 'MessageAttributes' in sns_msg:
|
|
455
|
+
for key, value in sns_msg['MessageAttributes'].items():
|
|
456
|
+
if key == 'traceparent' and 'Value' in value:
|
|
457
|
+
combined_attributes['message.traceparent'] = value['Value']
|
|
458
|
+
combined_attributes['message.has_trace_context'] = True
|
|
459
|
+
|
|
455
460
|
elif 'messageId' in message:
|
|
461
|
+
# SQS message
|
|
456
462
|
combined_attributes['messaging.message_id'] = message['messageId']
|
|
457
463
|
if 'eventSource' in message:
|
|
458
464
|
combined_attributes['messaging.system'] = message['eventSource']
|
|
459
465
|
|
|
460
|
-
|
|
466
|
+
# Check for trace context in SQS
|
|
467
|
+
if 'MessageAttributes' in message or 'messageAttributes' in message:
|
|
468
|
+
attrs = message.get('MessageAttributes') or message.get('messageAttributes', {})
|
|
469
|
+
for key, value in attrs.items():
|
|
470
|
+
if key == 'traceparent':
|
|
471
|
+
tp_value = value.get('StringValue') or value.get('stringValue', '')
|
|
472
|
+
combined_attributes['message.traceparent'] = tp_value
|
|
473
|
+
combined_attributes['message.has_trace_context'] = True
|
|
474
|
+
|
|
461
475
|
if 'awsRegion' in message:
|
|
462
476
|
combined_attributes['cloud.region'] = message['awsRegion']
|
|
463
477
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
478
|
+
# Use the tracer's start_span method directly to ensure it works
|
|
479
|
+
# This creates a child span of whatever is currently active
|
|
480
|
+
with self.tracer.start_span(
|
|
481
|
+
name=name,
|
|
482
|
+
attributes=combined_attributes,
|
|
483
|
+
kind=kind
|
|
484
|
+
) as span:
|
|
485
|
+
yield span
|
|
472
486
|
|
|
473
487
|
|
|
474
488
|
# Create Singleton instance
|
|
@@ -93,6 +93,7 @@ class RebrandlyTracer:
|
|
|
93
93
|
attributes: Optional[Dict[str, Any]] = None,
|
|
94
94
|
kind: trace.SpanKind = trace.SpanKind.INTERNAL) -> ContextManager[Span]:
|
|
95
95
|
"""Start a new span as the current span."""
|
|
96
|
+
# Ensure we use the tracer to create a child span of the current span
|
|
96
97
|
with self.tracer.start_as_current_span(
|
|
97
98
|
name,
|
|
98
99
|
attributes=attributes,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|