rebrandly-otel 0.1.14__py3-none-any.whl → 0.1.17__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.

@@ -1,5 +1,6 @@
1
1
  # src/__init__.py
2
2
  from .rebrandly_otel import *
3
+ from .flask_support import *
3
4
 
4
5
  # Explicitly define what's available
5
6
  __all__ = [
@@ -12,5 +13,6 @@ __all__ = [
12
13
  'logger',
13
14
  'force_flush',
14
15
  'aws_message_handler',
15
- 'shutdown'
16
+ 'shutdown',
17
+ 'setup_flask'
16
18
  ]
@@ -0,0 +1,170 @@
1
+ # flask_integration.py
2
+ """Flask integration for Rebrandly OTEL SDK."""
3
+ from typing import TYPE_CHECKING
4
+ from opentelemetry.trace import Status, StatusCode, SpanKind
5
+
6
+ if TYPE_CHECKING:
7
+ from .rebrandly_otel import RebrandlyOTEL
8
+
9
+ def setup_flask(otel: 'RebrandlyOTEL', app):
10
+ """
11
+ Setup Flask application with OTEL instrumentation.
12
+
13
+ Example:
14
+ from flask import Flask
15
+ from rebrandly_otel import otel
16
+ from rebrandly_otel.flask_integration import setup_flask
17
+
18
+ app = Flask(__name__)
19
+ setup_flask(otel, app)
20
+ """
21
+ app.before_request(lambda: app_before_request(otel))
22
+ app.after_request(lambda response: app_after_request(otel, response))
23
+ app.register_error_handler(Exception, lambda e: flask_error_handler(otel, e))
24
+ return app
25
+
26
+ def app_before_request(otel: 'RebrandlyOTEL'):
27
+ """
28
+ Setup tracing for incoming Flask request.
29
+ To be used with Flask's before_request hook.
30
+ """
31
+ from flask import request
32
+
33
+ # Extract trace context from headers
34
+ headers = dict(request.headers)
35
+ token = otel.attach_context(headers)
36
+ request.trace_token = token
37
+
38
+ # Start span for request using start_as_current_span to make it the active span
39
+ span = otel.tracer.tracer.start_as_current_span(
40
+ f"{request.method} {request.path}",
41
+ attributes={
42
+ "http.method": request.method,
43
+ "http.url": request.url,
44
+ "http.path": request.path,
45
+ "http.scheme": request.scheme,
46
+ "http.host": request.host,
47
+ "http.user_agent": request.user_agent.string if request.user_agent else '',
48
+ "http.remote_addr": request.remote_addr,
49
+ "http.target": request.path,
50
+ "span.kind": "server"
51
+ },
52
+ kind=SpanKind.SERVER
53
+ )
54
+ # Store both the span context manager and the span itself
55
+ request.span_context = span
56
+ request.span = span.__enter__() # This activates the span and returns the span object
57
+
58
+ # Log request start
59
+ otel.logger.logger.info(f"Request started: {request.method} {request.path}",
60
+ extra={"http.method": request.method, "http.path": request.path})
61
+
62
+ def app_after_request(otel: 'RebrandlyOTEL', response):
63
+ """
64
+ Cleanup tracing after Flask request completes.
65
+ To be used with Flask's after_request hook.
66
+ """
67
+ from flask import request
68
+
69
+ # Check if we have a span and it's still recording
70
+ if hasattr(request, 'span') and request.span.is_recording():
71
+ request.span.set_attribute("http.status_code", response.status_code)
72
+
73
+ # Set span status based on HTTP status code
74
+ if response.status_code >= 400:
75
+ request.span.set_status(Status(StatusCode.ERROR, f"HTTP {response.status_code}"))
76
+ # Record error metric
77
+ otel.meter.GlobalMetrics.error_invocations.add(1, {
78
+ "endpoint": request.path,
79
+ "method": request.method,
80
+ "status_code": response.status_code
81
+ })
82
+ else:
83
+ request.span.set_status(Status(StatusCode.OK))
84
+ # Record success metric
85
+ otel.meter.GlobalMetrics.successful_invocations.add(1, {
86
+ "endpoint": request.path,
87
+ "method": request.method
88
+ })
89
+
90
+ # Properly close the span context manager
91
+ if hasattr(request, 'span_context'):
92
+ request.span_context.__exit__(None, None, None)
93
+ else:
94
+ # Fallback if we don't have the context manager
95
+ request.span.end()
96
+
97
+ # Detach context
98
+ if hasattr(request, 'trace_token'):
99
+ otel.detach_context(request.trace_token)
100
+
101
+ # Log request completion
102
+ otel.logger.logger.info(f"Request completed: {response.status_code}",
103
+ extra={"http.status_code": response.status_code})
104
+
105
+ # Record general invocation metric
106
+ otel.meter.GlobalMetrics.invocations.add(1, {
107
+ "endpoint": request.path,
108
+ "method": request.method
109
+ })
110
+
111
+ return response
112
+
113
+ def flask_error_handler(otel: 'RebrandlyOTEL', exception):
114
+ """
115
+ Handle Flask exceptions and record them in the current span.
116
+ To be used with Flask's errorhandler decorator.
117
+ """
118
+ from flask import request, jsonify
119
+ from werkzeug.exceptions import HTTPException
120
+
121
+ # Determine the status code
122
+ if isinstance(exception, HTTPException):
123
+ status_code = exception.code
124
+ elif hasattr(exception, 'status_code'):
125
+ status_code = exception.status_code
126
+ elif hasattr(exception, 'code'):
127
+ status_code = exception.code if isinstance(exception.code, int) else 500
128
+ else:
129
+ status_code = 500
130
+
131
+ # Record exception in span if available and still recording
132
+ if hasattr(request, 'span') and request.span.is_recording():
133
+ request.span.set_attribute("http.status_code", status_code)
134
+ request.span.set_attribute("error.type", type(exception).__name__)
135
+
136
+ request.span.record_exception(exception)
137
+ request.span.set_status(Status(StatusCode.ERROR, str(exception)))
138
+ request.span.add_event("exception", {
139
+ "exception.type": type(exception).__name__,
140
+ "exception.message": str(exception),
141
+ "http.status_code": status_code
142
+ })
143
+
144
+ # Only close the span if it's still recording (not already ended)
145
+ if hasattr(request, 'span_context'):
146
+ request.span_context.__exit__(type(exception), exception, None)
147
+ else:
148
+ request.span.end()
149
+
150
+ # Log the error with status code
151
+ otel.logger.logger.error(f"Unhandled exception: {exception} (status: {status_code})",
152
+ exc_info=True,
153
+ extra={
154
+ "exception.type": type(exception).__name__,
155
+ "http.status_code": status_code
156
+ })
157
+
158
+ # Record error metric with status code
159
+ otel.meter.GlobalMetrics.error_invocations.add(1, {
160
+ "endpoint": request.path if hasattr(request, 'path') else 'unknown',
161
+ "method": request.method if hasattr(request, 'method') else 'unknown',
162
+ "error": type(exception).__name__,
163
+ "status_code": status_code
164
+ })
165
+
166
+ # Return error response with the determined status code
167
+ return jsonify({
168
+ "error": str(exception),
169
+ "type": type(exception).__name__
170
+ }), status_code
@@ -25,6 +25,17 @@ def create_resource(name: str = None, version: str = None) -> Resource:
25
25
  )
26
26
  return resource
27
27
 
28
+ def get_package_version():
29
+ try:
30
+ from importlib.metadata import version, PackageNotFoundError # Python 3.8+
31
+ return version('rebrandly_otel')
32
+ except ImportError:
33
+ try:
34
+ from importlib_metadata import version, PackageNotFoundError
35
+ return version('rebrandly_otel')
36
+ except:
37
+ return '?.0.1'
38
+
28
39
 
29
40
  def get_service_name(service_name: str = None) -> str:
30
41
  if service_name is None:
@@ -34,7 +45,7 @@ def get_service_name(service_name: str = None) -> str:
34
45
 
35
46
  def get_service_version(service_version: str = None) -> str:
36
47
  if service_version is None:
37
- return os.environ.get('OTEL_SERVICE_VERSION', '1.0.0')
48
+ return os.environ.get('OTEL_SERVICE_VERSION', get_package_version())
38
49
  return service_version
39
50
 
40
51
 
@@ -1,7 +1,5 @@
1
- # rebrandly_otel.py
2
- """
3
- Rebrandly OpenTelemetry SDK - Simplified instrumentation for Rebrandly services.
4
- """
1
+
2
+ import json
5
3
  import time
6
4
  import psutil
7
5
  import functools
@@ -99,8 +97,6 @@ class RebrandlyOTEL:
99
97
  return wrapper
100
98
  return decorator
101
99
 
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
100
  def lambda_handler(self,
105
101
  name: Optional[str] = None,
106
102
  attributes: Optional[Dict[str, Any]] = None,
@@ -132,6 +128,7 @@ class RebrandlyOTEL:
132
128
 
133
129
  # Handle context extraction from AWS events
134
130
  token = None
131
+
135
132
  if not skip_aws_link and event and isinstance(event, dict) and 'Records' in event:
136
133
  first_record = event['Records'][0] if event['Records'] else None
137
134
  if first_record:
@@ -160,11 +157,10 @@ class RebrandlyOTEL:
160
157
  from opentelemetry import propagate, context as otel_context
161
158
  extracted_context = propagate.extract(carrier)
162
159
  token = otel_context.attach(extracted_context)
163
- span_attributes['message.has_trace_context'] = True
164
160
 
165
161
  result = None
166
162
  try:
167
- # Increment invocation counter
163
+ # Increment invocation counter with standardized metric name
168
164
  self.meter.GlobalMetrics.invocations.add(1, {'function': span_name})
169
165
 
170
166
  # Create and execute within span
@@ -173,26 +169,63 @@ class RebrandlyOTEL:
173
169
  attributes=span_attributes,
174
170
  kind=kind
175
171
  ) as span:
176
- # Add invocation start event
177
- span.add_event("lambda.invocation.start", {
172
+ # Add invocation start event with standardized attributes
173
+ start_event_attrs = {
178
174
  'event.type': type(event).__name__ if event else 'None'
179
- })
175
+ }
176
+
177
+ # Add records count if present
178
+ if event and isinstance(event, dict) and 'Records' in event:
179
+ start_event_attrs['event.records'] = f"{len(event['Records'])}"
180
+
181
+ span.add_event("lambda.invocation.start", start_event_attrs)
180
182
 
181
183
  # Execute handler
182
184
  result = func(event, lambda_context)
183
185
 
184
- # Process result
185
- self._process_lambda_result(result, span, span_name)
186
+ # Process result with standardized attributes
187
+ success = True
188
+ complete_event_attrs = {}
189
+
190
+ if isinstance(result, dict) and 'statusCode' in result:
191
+ span.set_attribute("http.status_code", result['statusCode'])
192
+ complete_event_attrs['status_code'] = result['statusCode']
193
+
194
+ # Set span status based on HTTP status code
195
+ if result['statusCode'] >= 400:
196
+ success = False
197
+ span.set_status(Status(StatusCode.ERROR, f"HTTP {result['statusCode']}"))
198
+ else:
199
+ span.set_status(Status(StatusCode.OK))
200
+ else:
201
+ span.set_status(Status(StatusCode.OK))
202
+
203
+ # Add completion event with success indicator
204
+ complete_event_attrs['success'] = success
205
+ span.add_event("lambda.invocation.complete", complete_event_attrs)
206
+
207
+ # Increment success counter with standardized metric
208
+ self.meter.GlobalMetrics.successful_invocations.add(1, {'function': span_name})
186
209
 
187
210
  return result
188
211
 
189
212
  except Exception as e:
190
- # Increment error counter
213
+ # Increment error counter with standardized metric
191
214
  self.meter.GlobalMetrics.error_invocations.add(1, {
192
215
  'function': span_name,
193
216
  'error': type(e).__name__
194
217
  })
195
218
 
219
+ # Add failed completion event with error attribute (THIS IS THE KEY ADDITION)
220
+ span.add_event("lambda.invocation.complete", {
221
+ 'success': False,
222
+ 'error': type(e).__name__
223
+ })
224
+
225
+ # Record the exception in the span
226
+ span.record_exception(e)
227
+ span.set_status(Status(StatusCode.ERROR, str(e)))
228
+
196
229
  # Log error
197
230
  self.logger.logger.error(f"Lambda execution failed: {e}", exc_info=True)
198
231
  raise
@@ -203,81 +236,68 @@ class RebrandlyOTEL:
203
236
  from opentelemetry import context as otel_context
204
237
  otel_context.detach(token)
205
238
 
206
- # Record duration
239
+ # Record duration with standardized metric
207
240
  duration = (datetime.now() - start_time).total_seconds() * 1000
208
241
  self.meter.GlobalMetrics.duration.record(duration, {'function': span_name})
209
242
 
210
243
  # Force flush if enabled
211
244
  if auto_flush:
212
- self.logger.logger.info(f"[OTEL] Lambda '{span_name}' completed in {duration:.2f}ms, flushing...")
245
+ self.logger.logger.info(f"[Rebrandly OTEL] Lambda '{span_name}' completed in {duration:.2f}ms, flushing...")
213
246
  flush_success = self.force_flush(timeout_millis=1000)
214
247
  if not flush_success:
215
- self.logger.logger.warning("[OTEL] Force flush may not have completed fully")
248
+ self.logger.logger.warning("[Rebrandly OTEL] Force flush may not have completed fully")
216
249
 
217
250
  return wrapper
218
251
  return decorator
219
252
 
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
253
  def aws_message_handler(self,
242
- name: Optional[str] = None,
243
- attributes: Optional[Dict[str, Any]] = None,
244
- kind: SpanKind = SpanKind.CONSUMER,
245
- auto_flush: bool = True):
254
+ name: Optional[str] = None,
255
+ attributes: Optional[Dict[str, Any]] = None,
256
+ kind: SpanKind = SpanKind.CONSUMER,
257
+ auto_flush: bool = True):
246
258
  """
247
- require a record object parameter to the function
259
+ Decorator for AWS message handlers (SQS/SNS record processing).
260
+ Requires a record object parameter to the function.
248
261
  """
249
262
  def decorator(func):
250
263
  @functools.wraps(func)
251
264
  def wrapper(record=None, *args, **kwargs):
252
265
  # Determine span name
253
- span_name = name or f"lambda.{func.__name__}"
266
+ span_name = name or f"message.{func.__name__}"
254
267
  start_func = datetime.now()
255
268
 
256
269
  # Build span attributes
257
270
  span_attributes = attributes or {}
271
+ span_attributes['messaging.operation'] = 'process'
258
272
 
259
273
  result = None
260
274
  try:
261
- # Increment invocations counter
262
- print('XXX 2')
275
+ # Increment invocations counter with standardized metric
263
276
  self.meter.GlobalMetrics.invocations.add(1, {'handler': span_name})
264
277
 
265
278
  # Create span and execute function
266
279
  span_function = self.span
267
- if record is not None and 'MessageAttributes' in record:
280
+ if record is not None and ('MessageAttributes' in record or 'messageAttributes' in record):
268
281
  span_function = self.aws_message_span
269
282
 
270
283
  with span_function(span_name, message=record, attributes=span_attributes, kind=kind) as span_context:
284
+ # Add processing start event with standardized name
285
+ span_context.add_event("message.processing.start", {})
286
+
271
287
  # Execute the actual handler function
272
288
  result = func(record, *args, **kwargs)
273
289
 
274
- # Add result attributes if applicable
290
+ # Process result
291
+ success = True
292
+ complete_event_attrs = {}
293
+
275
294
  if result and isinstance(result, dict):
276
295
  if 'statusCode' in result:
277
- span_context.set_attribute("handler.status_code", result['statusCode'])
296
+ span_context.set_attribute("http.status_code", result['statusCode'])
278
297
 
279
298
  # Set span status based on status code
280
299
  if result['statusCode'] >= 400:
300
+ success = False
281
301
  span_context.set_status(
282
302
  Status(StatusCode.ERROR, f"Handler returned {result['statusCode']}")
283
303
  )
@@ -286,32 +306,49 @@ class RebrandlyOTEL:
286
306
 
287
307
  # Add custom result attributes if present
288
308
  if 'processed' in result:
289
- span_context.set_attribute("handler.processed", result['processed'])
309
+ complete_event_attrs['processed'] = result['processed']
310
+ span_context.set_attribute("message.processed", result['processed'])
290
311
  if 'skipped' in result:
291
- span_context.set_attribute("handler.skipped", result['skipped'])
312
+ complete_event_attrs['skipped'] = result['skipped']
313
+ span_context.set_attribute("message.skipped", result['skipped'])
314
+ else:
315
+ span_context.set_status(Status(StatusCode.OK))
292
316
 
293
- # Add completion event
294
- span_context.add_event("lambda.invocation.complete", attributes={
295
- "handler.success": True
296
- })
317
+ # Add completion event with standardized name
318
+ complete_event_attrs['success'] = success
319
+ span_context.add_event("message.processing.complete", complete_event_attrs)
297
320
 
298
- # Increment success counter
321
+ # Increment success counter with standardized metric
299
322
  self.meter.GlobalMetrics.successful_invocations.add(1, {'handler': span_name})
300
323
 
301
324
  return result
302
325
 
303
326
  except Exception as e:
304
- # Increment error counter
305
- self.meter.GlobalMetrics.error_invocations.add(1, {'handler': span_name, 'error': type(e).__name__})
327
+ # Increment error counter with standardized metric
328
+ self.meter.GlobalMetrics.error_invocations.add(1, {
329
+ 'handler': span_name,
330
+ 'error': type(e).__name__
331
+ })
306
332
 
307
333
  # Record the exception in the span
308
- span_context.record_exception(e)
309
- span_context.set_status(Status(StatusCode.ERROR, str(e)))
334
+ if 'span_context' in locals():
335
+ span_context.record_exception(e)
336
+ span_context.set_status(Status(StatusCode.ERROR, str(e)))
337
+
338
+ # Add failed processing event
339
+ span_context.add_event("message.processing.complete", {
340
+ 'success': False,
341
+ 'error': type(e).__name__
342
+ })
310
343
 
311
344
  # Re-raise the exception
312
345
  raise
313
346
 
314
347
  finally:
348
+ # Record duration with standardized metric
349
+ duration = (datetime.now() - start_func).total_seconds() * 1000
350
+ self.meter.GlobalMetrics.duration.record(duration, {'handler': span_name})
351
+
315
352
  if auto_flush:
316
353
  self.force_flush(start_datetime=start_func)
317
354
 
@@ -324,6 +361,7 @@ class RebrandlyOTEL:
324
361
  This is CRITICAL for Lambda functions to ensure data is sent before function freezes.
325
362
 
326
363
  Args:
364
+ start_datetime: Optional start time for system metrics capture
327
365
  timeout_millis: Maximum time to wait for flush in milliseconds
328
366
 
329
367
  Returns:
@@ -334,14 +372,15 @@ class RebrandlyOTEL:
334
372
  if start_datetime is not None:
335
373
  end_func = datetime.now()
336
374
  duration = (end_func - start_datetime).total_seconds() * 1000
337
- cpu_percent = psutil.cpu_percent(interval=1)
375
+ cpu_percent = psutil.cpu_percent(interval=0.1) # Shorter interval for Lambda
338
376
  memory = psutil.virtual_memory()
339
377
 
340
378
  # Record metrics using standardized names
341
379
  self.meter.GlobalMetrics.duration.record(duration, {'source': 'force_flush'})
342
380
  self.meter.GlobalMetrics.memory_usage_bytes.set(memory.used)
343
381
  self.meter.GlobalMetrics.cpu_usage_percentage.set(cpu_percent)
344
- self.logger.logger.info(f"Function duration: {duration}ms, Memory usage: {memory.percent}%, CPU usage: {cpu_percent}%")
382
+
383
+ print(f"Function duration: {duration}ms, Memory usage: {memory.percent}%, CPU usage: {cpu_percent}%")
345
384
 
346
385
  try:
347
386
  # Flush traces
@@ -379,9 +418,8 @@ class RebrandlyOTEL:
379
418
  self._meter.shutdown()
380
419
  if self._logger:
381
420
  self._logger.shutdown()
382
- print("[OTEL] Shutdown completed")
383
421
  except Exception as e:
384
- print(f"[OTEL] Error during shutdown: {e}")
422
+ print(f"[Rebrandly OTEL] Error during shutdown: {e}")
385
423
 
386
424
  def _detect_lambda_trigger(self, event: Any) -> str:
387
425
  """Detect Lambda trigger type from event."""
@@ -455,6 +493,7 @@ class RebrandlyOTEL:
455
493
  from opentelemetry import trace, context as otel_context
456
494
 
457
495
  combined_attributes = attributes or {}
496
+ combined_attributes['messaging.operation'] = 'process'
458
497
 
459
498
  # Extract message attributes for linking/attributes
460
499
  if message and isinstance(message, dict):
@@ -463,16 +502,11 @@ class RebrandlyOTEL:
463
502
  sns_msg = message['Sns']
464
503
  if 'MessageId' in sns_msg:
465
504
  combined_attributes['messaging.message_id'] = sns_msg['MessageId']
505
+ if 'Subject' in sns_msg:
506
+ combined_attributes['messaging.sns.subject'] = sns_msg['Subject']
466
507
  if 'TopicArn' in sns_msg:
467
508
  combined_attributes['messaging.destination'] = sns_msg['TopicArn']
468
- combined_attributes['messaging.system'] = 'aws_sns'
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
509
+ combined_attributes['messaging.system'] = 'sns'
476
510
 
477
511
  elif 'messageId' in message:
478
512
  # SQS message
@@ -480,18 +514,11 @@ class RebrandlyOTEL:
480
514
  if 'eventSource' in message:
481
515
  combined_attributes['messaging.system'] = message['eventSource']
482
516
 
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
517
 
492
518
  if 'awsRegion' in message:
493
519
  combined_attributes['cloud.region'] = message['awsRegion']
494
520
 
521
+
495
522
  # Use the tracer's start_span method directly to ensure it works
496
523
  # This creates a child span of whatever is currently active
497
524
  with self.tracer.start_span(
@@ -520,4 +547,4 @@ extract_context = otel.extract_context
520
547
  attach_context = otel.attach_context
521
548
  detach_context = otel.detach_context
522
549
  force_flush = otel.force_flush
523
- shutdown = otel.shutdown
550
+ shutdown = otel.shutdown
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rebrandly_otel
3
- Version: 0.1.14
3
+ Version: 0.1.17
4
4
  Summary: Python OTEL wrapper by Rebrandly
5
5
  Home-page: https://github.com/rebrandly/rebrandly-otel-python
6
6
  Author: Antonio Romano
@@ -119,6 +119,21 @@ meter.GlobalMetrics.invocations.add(1, {'function': 'my_function'})
119
119
  meter.GlobalMetrics.duration.record(150.5, {'source': 'api'})
120
120
  ```
121
121
 
122
+ ### Custom Metrics
123
+
124
+ You can create the custom metrics you need using the default open telemetry metrics
125
+
126
+ ```python
127
+ from src.rebrandly_otel import meter
128
+
129
+ sqs_counter = meter.meter.create_counter(
130
+ name="sqs_sender_counter",
131
+ description="Number of messages sent",
132
+ unit="1"
133
+ )
134
+ sqs_counter.add(1)
135
+ ```
136
+
122
137
  ## Tracing Features
123
138
 
124
139
  ### Automatic Context Propagation
@@ -319,6 +334,47 @@ def process_message(record):
319
334
  logger.info(f"Message data: {body}")
320
335
  ```
321
336
 
337
+ ###
338
+ Flask
339
+
340
+ ```python
341
+
342
+ from flask import Flask, jsonify
343
+ from src.rebrandly_otel import otel, logger, app_before_request, app_after_request, flask_error_handler
344
+ from datetime import datetime
345
+
346
+ app = Flask(__name__)
347
+
348
+ # Register the centralized OTEL handlers
349
+ app.before_request(app_before_request)
350
+ app.after_request(app_after_request)
351
+ app.register_error_handler(Exception, flask_error_handler)
352
+
353
+ @app.route('/health')
354
+ def health():
355
+ logger.info("Health check requested")
356
+ return jsonify({"status": "healthy"}), 200
357
+
358
+ @app.route('/process', methods=['POST', 'GET'])
359
+ def process():
360
+ with otel.span("process_request"):
361
+ logger.info("Processing POST request")
362
+
363
+ # Simulate processing
364
+ result = {"processed": True, "timestamp": datetime.now().isoformat()}
365
+
366
+ logger.info(f"Returning result: {result}")
367
+ return jsonify(result), 200
368
+
369
+ @app.route('/error')
370
+ def error():
371
+ logger.error("Error endpoint called")
372
+ raise Exception("Simulated error")
373
+
374
+ if __name__ == '__main__':
375
+ app.run(debug=True)
376
+ ```
377
+
322
378
  ### More examples
323
379
  You can find More examples [here](examples)
324
380
 
@@ -0,0 +1,12 @@
1
+ rebrandly_otel/__init__.py,sha256=MtIlcsWV1m1NAkrraS9SviDXaHybveOAV1RQi5lm_kM,323
2
+ rebrandly_otel/flask_support.py,sha256=BX3DPF9TYRNounfQEt0GV3oV6rMUJtHXrRRioF31TXA,6424
3
+ rebrandly_otel/logs.py,sha256=92jaxzI5hCHnKHu3lsSAa7K_SPHQgL46AlQUESsYNds,3724
4
+ rebrandly_otel/metrics.py,sha256=57jwrn8e1u66BOO7fcFrmtE3Rpt4VDixG9I78K-zrUU,8537
5
+ rebrandly_otel/otel_utils.py,sha256=tKelaETEeZxxvddKDpY8ESsGS77CcBbQku4oQjmiJx0,2078
6
+ rebrandly_otel/rebrandly_otel.py,sha256=DUhO_79jVq3rwm7HmKzw-hIx3snBW31PAxgWu14jIGo,23096
7
+ rebrandly_otel/traces.py,sha256=v582WtJv3t4Bn92vlDyZouibHtgWNxdRo_XmQCmSOEA,7126
8
+ rebrandly_otel-0.1.17.dist-info/licenses/LICENSE,sha256=KMXHvTwP62S2q-SG7CFfMU_09rUwxiqlM0izaYGdcgY,1103
9
+ rebrandly_otel-0.1.17.dist-info/METADATA,sha256=NieFA0UNJfoLZ2pnqbMJrQ9NY58OYrftgRG35bQE9AU,10971
10
+ rebrandly_otel-0.1.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
11
+ rebrandly_otel-0.1.17.dist-info/top_level.txt,sha256=26PSC1gjVUl8tTH5QfKbFevjVV4E2yojoukEfiTScvM,15
12
+ rebrandly_otel-0.1.17.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- rebrandly_otel/__init__.py,sha256=NUlroPhnHAKVC_q2BiyhtURXw23w1YY1BJd45gtc9qM,275
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=KwUWW1kiWZrB9B2xwioBsA_c-Xm4YoH51_HyX6HBvQY,21778
6
- rebrandly_otel/traces.py,sha256=v582WtJv3t4Bn92vlDyZouibHtgWNxdRo_XmQCmSOEA,7126
7
- rebrandly_otel-0.1.14.dist-info/licenses/LICENSE,sha256=KMXHvTwP62S2q-SG7CFfMU_09rUwxiqlM0izaYGdcgY,1103
8
- rebrandly_otel-0.1.14.dist-info/METADATA,sha256=tfOB74T9zcU5aXOj3Rw6bHChrd9-wexzkq7FqQUWptY,9628
9
- rebrandly_otel-0.1.14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
- rebrandly_otel-0.1.14.dist-info/top_level.txt,sha256=26PSC1gjVUl8tTH5QfKbFevjVV4E2yojoukEfiTScvM,15
11
- rebrandly_otel-0.1.14.dist-info/RECORD,,