rebrandly-otel 0.1.16__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,8 +1,5 @@
1
- # rebrandly_otel.py
2
- """
3
- Rebrandly OpenTelemetry SDK - Simplified instrumentation for Rebrandly services.
4
- Updated for consistency with Node.js SDK
5
- """
1
+
2
+ import json
6
3
  import time
7
4
  import psutil
8
5
  import functools
@@ -550,4 +547,4 @@ extract_context = otel.extract_context
550
547
  attach_context = otel.attach_context
551
548
  detach_context = otel.detach_context
552
549
  force_flush = otel.force_flush
553
- 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.16
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=A_TGyCQq752EeEvcsMmBWaWx2Z12J1hfmyYTFpYJFbo,23232
6
- rebrandly_otel/traces.py,sha256=v582WtJv3t4Bn92vlDyZouibHtgWNxdRo_XmQCmSOEA,7126
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,,