rebrandly-otel 0.1.10__py3-none-any.whl → 0.1.13__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 +104 -104
- rebrandly_otel/traces.py +1 -0
- {rebrandly_otel-0.1.10.dist-info → rebrandly_otel-0.1.13.dist-info}/METADATA +1 -1
- rebrandly_otel-0.1.13.dist-info/RECORD +11 -0
- rebrandly_otel-0.1.10.dist-info/RECORD +0 -11
- {rebrandly_otel-0.1.10.dist-info → rebrandly_otel-0.1.13.dist-info}/WHEEL +0 -0
- {rebrandly_otel-0.1.10.dist-info → rebrandly_otel-0.1.13.dist-info}/licenses/LICENSE +0 -0
- {rebrandly_otel-0.1.10.dist-info → rebrandly_otel-0.1.13.dist-info}/top_level.txt +0 -0
rebrandly_otel/rebrandly_otel.py
CHANGED
|
@@ -101,102 +101,115 @@ class RebrandlyOTEL:
|
|
|
101
101
|
|
|
102
102
|
# Fix for the lambda_handler method in rebrandly_otel.py
|
|
103
103
|
# Replace the lambda_handler method (around line 132) with this fixed version:
|
|
104
|
-
|
|
105
104
|
def lambda_handler(self,
|
|
106
105
|
name: Optional[str] = None,
|
|
107
106
|
attributes: Optional[Dict[str, Any]] = None,
|
|
108
|
-
kind: SpanKind = SpanKind.
|
|
107
|
+
kind: SpanKind = SpanKind.SERVER,
|
|
109
108
|
auto_flush: bool = True,
|
|
110
|
-
skip_aws_link: bool =
|
|
109
|
+
skip_aws_link: bool = False):
|
|
111
110
|
"""
|
|
112
111
|
Decorator specifically for Lambda handlers with automatic flushing.
|
|
113
112
|
"""
|
|
114
113
|
def decorator(func):
|
|
115
114
|
@functools.wraps(func)
|
|
116
|
-
def wrapper(event=None,
|
|
115
|
+
def wrapper(event=None, lambda_context=None):
|
|
117
116
|
# Determine span name
|
|
118
117
|
span_name = name or f"lambda.{func.__name__}"
|
|
119
|
-
|
|
118
|
+
start_time = datetime.now()
|
|
120
119
|
|
|
121
120
|
# Build span attributes
|
|
122
121
|
span_attributes = attributes or {}
|
|
123
|
-
|
|
124
122
|
span_attributes['faas.trigger'] = self._detect_lambda_trigger(event)
|
|
125
123
|
|
|
126
124
|
# Add Lambda-specific attributes if context is available
|
|
127
|
-
if
|
|
125
|
+
if lambda_context is not None:
|
|
128
126
|
span_attributes.update({
|
|
129
|
-
"faas.execution": getattr(
|
|
130
|
-
"faas.id": getattr(
|
|
131
|
-
"
|
|
132
|
-
"
|
|
127
|
+
"faas.execution": getattr(lambda_context, 'aws_request_id', 'unknown'),
|
|
128
|
+
"faas.id": getattr(lambda_context, 'function_arn', 'unknown'),
|
|
129
|
+
"faas.name": getattr(lambda_context, 'function_name', 'unknown'),
|
|
130
|
+
"faas.version": getattr(lambda_context, 'function_version', 'unknown')
|
|
133
131
|
})
|
|
134
132
|
|
|
133
|
+
# Handle context extraction from AWS events
|
|
134
|
+
token = None
|
|
135
|
+
if not skip_aws_link and event and isinstance(event, dict) and 'Records' in event:
|
|
136
|
+
first_record = event['Records'][0] if event['Records'] else None
|
|
137
|
+
if first_record:
|
|
138
|
+
carrier = {}
|
|
139
|
+
|
|
140
|
+
# Extract from SQS
|
|
141
|
+
if 'MessageAttributes' in first_record:
|
|
142
|
+
for key, value in first_record['MessageAttributes'].items():
|
|
143
|
+
if isinstance(value, dict) and 'StringValue' in value:
|
|
144
|
+
carrier[key] = value['StringValue']
|
|
145
|
+
|
|
146
|
+
# Extract from SNS
|
|
147
|
+
elif 'Sns' in first_record and 'MessageAttributes' in first_record['Sns']:
|
|
148
|
+
for key, value in first_record['Sns']['MessageAttributes'].items():
|
|
149
|
+
if isinstance(value, dict):
|
|
150
|
+
if 'Value' in value:
|
|
151
|
+
carrier[key] = value['Value']
|
|
152
|
+
elif 'StringValue' in value:
|
|
153
|
+
carrier[key] = value['StringValue']
|
|
154
|
+
|
|
155
|
+
# Attach extracted context
|
|
156
|
+
if carrier:
|
|
157
|
+
from opentelemetry import propagate, context as otel_context
|
|
158
|
+
extracted_context = propagate.extract(carrier)
|
|
159
|
+
token = otel_context.attach(extracted_context)
|
|
160
|
+
span_attributes['message.has_trace_context'] = True
|
|
161
|
+
|
|
135
162
|
result = None
|
|
136
163
|
try:
|
|
137
|
-
# Increment
|
|
164
|
+
# Increment invocation counter
|
|
138
165
|
self.meter.GlobalMetrics.invocations.add(1, {'function': span_name})
|
|
139
166
|
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
167
|
+
# Create and execute within span
|
|
168
|
+
with self.tracer.start_span(
|
|
169
|
+
name=span_name,
|
|
170
|
+
attributes=span_attributes,
|
|
171
|
+
kind=kind
|
|
172
|
+
) as span:
|
|
173
|
+
# Add invocation start event
|
|
174
|
+
span.add_event("lambda.invocation.start", {
|
|
175
|
+
'event.type': type(event).__name__ if event else 'None'
|
|
176
|
+
})
|
|
144
177
|
|
|
145
|
-
#
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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)
|
|
178
|
+
# Execute handler
|
|
179
|
+
result = func(event, lambda_context)
|
|
180
|
+
|
|
181
|
+
# Process result
|
|
182
|
+
self._process_lambda_result(result, span, span_name)
|
|
181
183
|
|
|
182
184
|
return result
|
|
183
185
|
|
|
184
186
|
except Exception as e:
|
|
185
187
|
# Increment error counter
|
|
186
|
-
self.meter.GlobalMetrics.error_invocations.add(1, {
|
|
188
|
+
self.meter.GlobalMetrics.error_invocations.add(1, {
|
|
189
|
+
'function': span_name,
|
|
190
|
+
'error': type(e).__name__
|
|
191
|
+
})
|
|
187
192
|
|
|
188
|
-
# Log error
|
|
193
|
+
# Log error
|
|
189
194
|
self.logger.logger.error(f"Lambda execution failed: {e}", exc_info=True)
|
|
190
195
|
raise
|
|
191
196
|
|
|
192
197
|
finally:
|
|
198
|
+
# Always detach context if we attached it
|
|
199
|
+
if token is not None:
|
|
200
|
+
from opentelemetry import context as otel_context
|
|
201
|
+
otel_context.detach(token)
|
|
202
|
+
|
|
193
203
|
# Record duration
|
|
194
|
-
duration = (datetime.now() -
|
|
204
|
+
duration = (datetime.now() - start_time).total_seconds() * 1000
|
|
195
205
|
self.meter.GlobalMetrics.duration.record(duration, {'function': span_name})
|
|
196
206
|
|
|
207
|
+
# Force flush if enabled
|
|
197
208
|
if auto_flush:
|
|
198
|
-
self.logger.logger.info(f"[OTEL] Lambda
|
|
199
|
-
self.force_flush(
|
|
209
|
+
self.logger.logger.info(f"[OTEL] Lambda '{span_name}' completed in {duration:.2f}ms, flushing...")
|
|
210
|
+
flush_success = self.force_flush(timeout_millis=1000)
|
|
211
|
+
if not flush_success:
|
|
212
|
+
self.logger.logger.warning("[OTEL] Force flush may not have completed fully")
|
|
200
213
|
|
|
201
214
|
return wrapper
|
|
202
215
|
return decorator
|
|
@@ -297,7 +310,6 @@ class RebrandlyOTEL:
|
|
|
297
310
|
|
|
298
311
|
finally:
|
|
299
312
|
if auto_flush:
|
|
300
|
-
self.logger.logger.info(f"[OTEL] Lambda handler '{span_name}' completed, flushing telemetry...")
|
|
301
313
|
self.force_flush(start_datetime=start_func)
|
|
302
314
|
|
|
303
315
|
return wrapper
|
|
@@ -326,7 +338,7 @@ class RebrandlyOTEL:
|
|
|
326
338
|
self.meter.GlobalMetrics.duration.record(duration, {'source': 'force_flush'})
|
|
327
339
|
self.meter.GlobalMetrics.memory_usage_bytes.set(memory.used)
|
|
328
340
|
self.meter.GlobalMetrics.cpu_usage_percentage.set(cpu_percent)
|
|
329
|
-
self.logger.logger.info(f"
|
|
341
|
+
self.logger.logger.info(f"Function duration: {duration}ms, Memory usage: {memory.percent}%, CPU usage: {cpu_percent}%")
|
|
330
342
|
|
|
331
343
|
try:
|
|
332
344
|
# Flush traces
|
|
@@ -364,9 +376,9 @@ class RebrandlyOTEL:
|
|
|
364
376
|
self._meter.shutdown()
|
|
365
377
|
if self._logger:
|
|
366
378
|
self._logger.shutdown()
|
|
367
|
-
|
|
379
|
+
print("[OTEL] Shutdown completed")
|
|
368
380
|
except Exception as e:
|
|
369
|
-
|
|
381
|
+
print(f"[OTEL] Error during shutdown: {e}")
|
|
370
382
|
|
|
371
383
|
def _detect_lambda_trigger(self, event: Any) -> str:
|
|
372
384
|
"""Detect Lambda trigger type from event."""
|
|
@@ -435,43 +447,15 @@ class RebrandlyOTEL:
|
|
|
435
447
|
message: Dict[str, Any]=None,
|
|
436
448
|
attributes: Optional[Dict[str, Any]] = None,
|
|
437
449
|
kind: SpanKind = SpanKind.CONSUMER):
|
|
438
|
-
"""Create span from AWS message
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
if message and isinstance(message, dict):
|
|
442
|
-
carrier = {}
|
|
450
|
+
"""Create span from AWS message - properly handling trace context."""
|
|
451
|
+
|
|
452
|
+
from opentelemetry import trace, context as otel_context
|
|
443
453
|
|
|
444
|
-
# Check for trace context in different possible locations
|
|
445
|
-
if 'MessageAttributes' in message:
|
|
446
|
-
# SQS format
|
|
447
|
-
for key, value in message.get('MessageAttributes', {}).items():
|
|
448
|
-
if isinstance(value, dict) and 'StringValue' in value:
|
|
449
|
-
carrier[key] = value['StringValue']
|
|
450
|
-
elif 'Sns' in message and 'MessageAttributes' in message['Sns']:
|
|
451
|
-
# SNS format - MessageAttributes are nested under 'Sns'
|
|
452
|
-
for key, value in message['Sns'].get('MessageAttributes', {}).items():
|
|
453
|
-
if isinstance(value, dict):
|
|
454
|
-
# SNS uses 'Value' instead of 'StringValue'
|
|
455
|
-
if 'Value' in value:
|
|
456
|
-
carrier[key] = value['Value']
|
|
457
|
-
elif 'StringValue' in value:
|
|
458
|
-
carrier[key] = value['StringValue']
|
|
459
|
-
elif 'messageAttributes' in message:
|
|
460
|
-
# Alternative format
|
|
461
|
-
for key, value in message.get('messageAttributes', {}).items():
|
|
462
|
-
if isinstance(value, dict) and 'stringValue' in value:
|
|
463
|
-
carrier[key] = value['stringValue']
|
|
464
|
-
|
|
465
|
-
# If we found trace context, attach it
|
|
466
|
-
if carrier:
|
|
467
|
-
token = self.attach_context(carrier)
|
|
468
|
-
|
|
469
|
-
# Create a span with the potentially extracted context
|
|
470
454
|
combined_attributes = attributes or {}
|
|
471
455
|
|
|
472
|
-
#
|
|
456
|
+
# Extract message attributes for linking/attributes
|
|
473
457
|
if message and isinstance(message, dict):
|
|
474
|
-
# Add
|
|
458
|
+
# Add message-specific attributes
|
|
475
459
|
if 'Sns' in message:
|
|
476
460
|
sns_msg = message['Sns']
|
|
477
461
|
if 'MessageId' in sns_msg:
|
|
@@ -479,24 +463,40 @@ class RebrandlyOTEL:
|
|
|
479
463
|
if 'TopicArn' in sns_msg:
|
|
480
464
|
combined_attributes['messaging.destination'] = sns_msg['TopicArn']
|
|
481
465
|
combined_attributes['messaging.system'] = 'aws_sns'
|
|
482
|
-
|
|
466
|
+
|
|
467
|
+
# Check for trace context in SNS
|
|
468
|
+
if 'MessageAttributes' in sns_msg:
|
|
469
|
+
for key, value in sns_msg['MessageAttributes'].items():
|
|
470
|
+
if key == 'traceparent' and 'Value' in value:
|
|
471
|
+
combined_attributes['message.traceparent'] = value['Value']
|
|
472
|
+
combined_attributes['message.has_trace_context'] = True
|
|
473
|
+
|
|
483
474
|
elif 'messageId' in message:
|
|
475
|
+
# SQS message
|
|
484
476
|
combined_attributes['messaging.message_id'] = message['messageId']
|
|
485
477
|
if 'eventSource' in message:
|
|
486
478
|
combined_attributes['messaging.system'] = message['eventSource']
|
|
487
479
|
|
|
488
|
-
|
|
480
|
+
# Check for trace context in SQS
|
|
481
|
+
if 'MessageAttributes' in message or 'messageAttributes' in message:
|
|
482
|
+
attrs = message.get('MessageAttributes') or message.get('messageAttributes', {})
|
|
483
|
+
for key, value in attrs.items():
|
|
484
|
+
if key == 'traceparent':
|
|
485
|
+
tp_value = value.get('StringValue') or value.get('stringValue', '')
|
|
486
|
+
combined_attributes['message.traceparent'] = tp_value
|
|
487
|
+
combined_attributes['message.has_trace_context'] = True
|
|
488
|
+
|
|
489
489
|
if 'awsRegion' in message:
|
|
490
490
|
combined_attributes['cloud.region'] = message['awsRegion']
|
|
491
491
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
492
|
+
# Use the tracer's start_span method directly to ensure it works
|
|
493
|
+
# This creates a child span of whatever is currently active
|
|
494
|
+
with self.tracer.start_span(
|
|
495
|
+
name=name,
|
|
496
|
+
attributes=combined_attributes,
|
|
497
|
+
kind=kind
|
|
498
|
+
) as span:
|
|
499
|
+
yield span
|
|
500
500
|
|
|
501
501
|
|
|
502
502
|
# Create Singleton instance
|
rebrandly_otel/traces.py
CHANGED
|
@@ -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,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
rebrandly_otel/__init__.py,sha256=AHcEKWq4KRafIF_x_Y8jiIQ5crNG1nbEwdnlGaEP6js,287
|
|
2
|
+
rebrandly_otel/logs.py,sha256=92jaxzI5hCHnKHu3lsSAa7K_SPHQgL46AlQUESsYNds,3724
|
|
3
|
+
rebrandly_otel/metrics.py,sha256=57jwrn8e1u66BOO7fcFrmtE3Rpt4VDixG9I78K-zrUU,8537
|
|
4
|
+
rebrandly_otel/otel_utils.py,sha256=uJmfz2NspSnTVJXGKoaLUl1CYb0ow6VHKjmg2d_rdwg,1704
|
|
5
|
+
rebrandly_otel/rebrandly_otel.py,sha256=cnWTDMH-z4n32HTxCbML0zedKhG5ZKPtkej5sL1epok,21438
|
|
6
|
+
rebrandly_otel/traces.py,sha256=v582WtJv3t4Bn92vlDyZouibHtgWNxdRo_XmQCmSOEA,7126
|
|
7
|
+
rebrandly_otel-0.1.13.dist-info/licenses/LICENSE,sha256=KMXHvTwP62S2q-SG7CFfMU_09rUwxiqlM0izaYGdcgY,1103
|
|
8
|
+
rebrandly_otel-0.1.13.dist-info/METADATA,sha256=Xu5Rxr80l_mB6Hx489zddCbWDqNKxcr6klUzujSV-4Y,9628
|
|
9
|
+
rebrandly_otel-0.1.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
rebrandly_otel-0.1.13.dist-info/top_level.txt,sha256=26PSC1gjVUl8tTH5QfKbFevjVV4E2yojoukEfiTScvM,15
|
|
11
|
+
rebrandly_otel-0.1.13.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
rebrandly_otel/__init__.py,sha256=AHcEKWq4KRafIF_x_Y8jiIQ5crNG1nbEwdnlGaEP6js,287
|
|
2
|
-
rebrandly_otel/logs.py,sha256=92jaxzI5hCHnKHu3lsSAa7K_SPHQgL46AlQUESsYNds,3724
|
|
3
|
-
rebrandly_otel/metrics.py,sha256=57jwrn8e1u66BOO7fcFrmtE3Rpt4VDixG9I78K-zrUU,8537
|
|
4
|
-
rebrandly_otel/otel_utils.py,sha256=uJmfz2NspSnTVJXGKoaLUl1CYb0ow6VHKjmg2d_rdwg,1704
|
|
5
|
-
rebrandly_otel/rebrandly_otel.py,sha256=KSzrGSdUgqXZO1C0RBrBLNcAh-9oR5wqSWLJc0-Itww,21626
|
|
6
|
-
rebrandly_otel/traces.py,sha256=EiZMWNerzAisn_2MEcE9u1zVkRlZZrbnY3PVzVWWOuI,7048
|
|
7
|
-
rebrandly_otel-0.1.10.dist-info/licenses/LICENSE,sha256=KMXHvTwP62S2q-SG7CFfMU_09rUwxiqlM0izaYGdcgY,1103
|
|
8
|
-
rebrandly_otel-0.1.10.dist-info/METADATA,sha256=27eO_Dw6gLu-4FLXTW9rMj7zVmpImBHwKhAUjebmDWw,9628
|
|
9
|
-
rebrandly_otel-0.1.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
-
rebrandly_otel-0.1.10.dist-info/top_level.txt,sha256=26PSC1gjVUl8tTH5QfKbFevjVV4E2yojoukEfiTScvM,15
|
|
11
|
-
rebrandly_otel-0.1.10.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|