rebrandly-otel 0.1.11__tar.gz → 0.1.13__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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rebrandly_otel
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Summary: Python OTEL wrapper by Rebrandly
5
5
  Home-page: https://github.com/rebrandly/rebrandly-otel-python
6
6
  Author: Antonio Romano
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rebrandly_otel
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Summary: Python OTEL wrapper by Rebrandly
5
5
  Home-page: https://github.com/rebrandly/rebrandly-otel-python
6
6
  Author: Antonio Romano
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="rebrandly_otel",
8
- version="0.1.11",
8
+ version="0.1.13",
9
9
  author="Antonio Romano",
10
10
  author_email="antonio@rebrandly.com",
11
11
  description="Python OTEL wrapper by Rebrandly",
@@ -101,101 +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.CONSUMER,
107
+ kind: SpanKind = SpanKind.SERVER,
109
108
  auto_flush: bool = True,
110
- skip_aws_link: bool = True):
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, context=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
- start_func = datetime.now()
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 context is not None:
125
+ if lambda_context is not None:
128
126
  span_attributes.update({
129
- "faas.execution": getattr(context, 'aws_request_id', 'unknown'),
130
- "faas.id": getattr(context, 'function_name', 'unknown'),
131
- "cloud.provider": "aws",
132
- "cloud.platform": "aws_lambda"
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 invocations counter
164
+ # Increment invocation counter
138
165
  self.meter.GlobalMetrics.invocations.add(1, {'function': span_name})
139
166
 
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]
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
+ })
177
+
178
+ # Execute handler
179
+ result = func(event, lambda_context)
144
180
 
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
+ # 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, {'function': span_name, 'error': type(e).__name__})
188
+ self.meter.GlobalMetrics.error_invocations.add(1, {
189
+ 'function': span_name,
190
+ 'error': type(e).__name__
191
+ })
187
192
 
188
- # Log error with context
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() - start_func).total_seconds() * 1000
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.force_flush(start_datetime=start_func)
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")
199
213
 
200
214
  return wrapper
201
215
  return decorator
File without changes