rebrandly-otel 0.1.17__tar.gz → 0.1.18__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.17 → rebrandly_otel-0.1.18}/PKG-INFO +1 -16
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/README.md +0 -15
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/rebrandly_otel.egg-info/PKG-INFO +1 -16
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/rebrandly_otel.egg-info/SOURCES.txt +1 -0
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/setup.py +1 -1
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/src/__init__.py +4 -2
- rebrandly_otel-0.1.18/src/fastapi_support.py +198 -0
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/src/flask_support.py +35 -52
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/src/metrics.py +7 -34
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/src/rebrandly_otel.py +2 -34
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/LICENSE +0 -0
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/rebrandly_otel.egg-info/dependency_links.txt +0 -0
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/rebrandly_otel.egg-info/top_level.txt +0 -0
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/setup.cfg +0 -0
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/src/logs.py +0 -0
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/src/otel_utils.py +0 -0
- {rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/src/traces.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rebrandly_otel
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.18
|
|
4
4
|
Summary: Python OTEL wrapper by Rebrandly
|
|
5
5
|
Home-page: https://github.com/rebrandly/rebrandly-otel-python
|
|
6
6
|
Author: Antonio Romano
|
|
@@ -102,22 +102,9 @@ The SDK automatically registers and tracks the following metrics:
|
|
|
102
102
|
|
|
103
103
|
### Standard Metrics
|
|
104
104
|
|
|
105
|
-
- **`invocations`** (Counter): Total number of function invocations
|
|
106
|
-
- **`successful_invocations`** (Counter): Number of successful completions
|
|
107
|
-
- **`error_invocations`** (Counter): Number of failed invocations
|
|
108
|
-
- **`duration`** (Histogram): Execution duration in milliseconds
|
|
109
105
|
- **`cpu_usage_percentage`** (Gauge): CPU utilization percentage
|
|
110
106
|
- **`memory_usage_bytes`** (Gauge): Memory usage in bytes
|
|
111
107
|
|
|
112
|
-
### Global Metrics Access
|
|
113
|
-
|
|
114
|
-
```python
|
|
115
|
-
from rebrandly_otel import meter
|
|
116
|
-
|
|
117
|
-
# Access pre-configured metrics
|
|
118
|
-
meter.GlobalMetrics.invocations.add(1, {'function': 'my_function'})
|
|
119
|
-
meter.GlobalMetrics.duration.record(150.5, {'source': 'api'})
|
|
120
|
-
```
|
|
121
108
|
|
|
122
109
|
### Custom Metrics
|
|
123
110
|
|
|
@@ -195,10 +182,8 @@ Automatically detects and labels Lambda triggers:
|
|
|
195
182
|
### Automatic Metrics
|
|
196
183
|
|
|
197
184
|
For Lambda functions, the SDK automatically captures:
|
|
198
|
-
- Function duration
|
|
199
185
|
- Memory usage
|
|
200
186
|
- CPU utilization
|
|
201
|
-
- Invocation counts by status
|
|
202
187
|
|
|
203
188
|
### Context Extraction
|
|
204
189
|
|
|
@@ -81,22 +81,9 @@ The SDK automatically registers and tracks the following metrics:
|
|
|
81
81
|
|
|
82
82
|
### Standard Metrics
|
|
83
83
|
|
|
84
|
-
- **`invocations`** (Counter): Total number of function invocations
|
|
85
|
-
- **`successful_invocations`** (Counter): Number of successful completions
|
|
86
|
-
- **`error_invocations`** (Counter): Number of failed invocations
|
|
87
|
-
- **`duration`** (Histogram): Execution duration in milliseconds
|
|
88
84
|
- **`cpu_usage_percentage`** (Gauge): CPU utilization percentage
|
|
89
85
|
- **`memory_usage_bytes`** (Gauge): Memory usage in bytes
|
|
90
86
|
|
|
91
|
-
### Global Metrics Access
|
|
92
|
-
|
|
93
|
-
```python
|
|
94
|
-
from rebrandly_otel import meter
|
|
95
|
-
|
|
96
|
-
# Access pre-configured metrics
|
|
97
|
-
meter.GlobalMetrics.invocations.add(1, {'function': 'my_function'})
|
|
98
|
-
meter.GlobalMetrics.duration.record(150.5, {'source': 'api'})
|
|
99
|
-
```
|
|
100
87
|
|
|
101
88
|
### Custom Metrics
|
|
102
89
|
|
|
@@ -174,10 +161,8 @@ Automatically detects and labels Lambda triggers:
|
|
|
174
161
|
### Automatic Metrics
|
|
175
162
|
|
|
176
163
|
For Lambda functions, the SDK automatically captures:
|
|
177
|
-
- Function duration
|
|
178
164
|
- Memory usage
|
|
179
165
|
- CPU utilization
|
|
180
|
-
- Invocation counts by status
|
|
181
166
|
|
|
182
167
|
### Context Extraction
|
|
183
168
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rebrandly_otel
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.18
|
|
4
4
|
Summary: Python OTEL wrapper by Rebrandly
|
|
5
5
|
Home-page: https://github.com/rebrandly/rebrandly-otel-python
|
|
6
6
|
Author: Antonio Romano
|
|
@@ -102,22 +102,9 @@ The SDK automatically registers and tracks the following metrics:
|
|
|
102
102
|
|
|
103
103
|
### Standard Metrics
|
|
104
104
|
|
|
105
|
-
- **`invocations`** (Counter): Total number of function invocations
|
|
106
|
-
- **`successful_invocations`** (Counter): Number of successful completions
|
|
107
|
-
- **`error_invocations`** (Counter): Number of failed invocations
|
|
108
|
-
- **`duration`** (Histogram): Execution duration in milliseconds
|
|
109
105
|
- **`cpu_usage_percentage`** (Gauge): CPU utilization percentage
|
|
110
106
|
- **`memory_usage_bytes`** (Gauge): Memory usage in bytes
|
|
111
107
|
|
|
112
|
-
### Global Metrics Access
|
|
113
|
-
|
|
114
|
-
```python
|
|
115
|
-
from rebrandly_otel import meter
|
|
116
|
-
|
|
117
|
-
# Access pre-configured metrics
|
|
118
|
-
meter.GlobalMetrics.invocations.add(1, {'function': 'my_function'})
|
|
119
|
-
meter.GlobalMetrics.duration.record(150.5, {'source': 'api'})
|
|
120
|
-
```
|
|
121
108
|
|
|
122
109
|
### Custom Metrics
|
|
123
110
|
|
|
@@ -195,10 +182,8 @@ Automatically detects and labels Lambda triggers:
|
|
|
195
182
|
### Automatic Metrics
|
|
196
183
|
|
|
197
184
|
For Lambda functions, the SDK automatically captures:
|
|
198
|
-
- Function duration
|
|
199
185
|
- Memory usage
|
|
200
186
|
- CPU utilization
|
|
201
|
-
- Invocation counts by status
|
|
202
187
|
|
|
203
188
|
### Context Extraction
|
|
204
189
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# src/__init__.py
|
|
2
2
|
from .rebrandly_otel import *
|
|
3
|
-
from .flask_support import
|
|
3
|
+
from .flask_support import setup_flask
|
|
4
|
+
from .fastapi_support import setup_fastapi
|
|
4
5
|
|
|
5
6
|
# Explicitly define what's available
|
|
6
7
|
__all__ = [
|
|
@@ -14,5 +15,6 @@ __all__ = [
|
|
|
14
15
|
'force_flush',
|
|
15
16
|
'aws_message_handler',
|
|
16
17
|
'shutdown',
|
|
17
|
-
'setup_flask'
|
|
18
|
+
'setup_flask',
|
|
19
|
+
'setup_fastapi'
|
|
18
20
|
]
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# fastapi_integration.py
|
|
2
|
+
"""FastAPI integration for Rebrandly OTEL SDK."""
|
|
3
|
+
import json
|
|
4
|
+
from rebrandly_otel import Status, StatusCode, SpanKind
|
|
5
|
+
from fastapi import HTTPException, Depends
|
|
6
|
+
from starlette.requests import Request
|
|
7
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
8
|
+
from fastapi.responses import JSONResponse
|
|
9
|
+
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
def setup_fastapi(otel , app):
|
|
13
|
+
"""
|
|
14
|
+
Setup FastAPI application with OTEL instrumentation.
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
from fastapi import FastAPI
|
|
18
|
+
from rebrandly_otel import otel
|
|
19
|
+
from rebrandly_otel.fastapi_integration import setup_fastapi
|
|
20
|
+
|
|
21
|
+
app = FastAPI()
|
|
22
|
+
setup_fastapi(otel, app)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
# Add middleware
|
|
26
|
+
add_otel_middleware(otel, app)
|
|
27
|
+
|
|
28
|
+
# Add exception handlers
|
|
29
|
+
app.add_exception_handler(HTTPException, lambda request, exc: fastapi_exception_handler(otel, request, exc))
|
|
30
|
+
app.add_exception_handler(Exception, lambda request, exc: fastapi_exception_handler(otel, request, exc))
|
|
31
|
+
|
|
32
|
+
return app
|
|
33
|
+
|
|
34
|
+
def add_otel_middleware(otel, app):
|
|
35
|
+
"""
|
|
36
|
+
Add OTEL middleware to FastAPI application.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
class OTELMiddleware(BaseHTTPMiddleware):
|
|
40
|
+
def __init__(self, app):
|
|
41
|
+
super().__init__(app)
|
|
42
|
+
self.otel = otel
|
|
43
|
+
|
|
44
|
+
async def dispatch(self, request: Request, call_next):
|
|
45
|
+
# Extract trace context from headers
|
|
46
|
+
headers = dict(request.headers)
|
|
47
|
+
token = self.otel.attach_context(headers)
|
|
48
|
+
|
|
49
|
+
# Start span for request
|
|
50
|
+
span_name = f"{request.method} {request.url.path}"
|
|
51
|
+
|
|
52
|
+
# Use start_as_current_span for proper context propagation
|
|
53
|
+
with self.otel.tracer.tracer.start_as_current_span(
|
|
54
|
+
span_name,
|
|
55
|
+
attributes={
|
|
56
|
+
# Required HTTP attributes per semantic conventions
|
|
57
|
+
"http.request.method": request.method,
|
|
58
|
+
"http.request.headers": json.dumps(request.headers, default=str),
|
|
59
|
+
"http.response.status_code": None, # Will be set after response
|
|
60
|
+
"url.full": str(request.url),
|
|
61
|
+
"url.scheme": request.url.scheme,
|
|
62
|
+
"url.path": request.url.path,
|
|
63
|
+
"url.query": request.url.query if request.url.query else None,
|
|
64
|
+
"user_agent.original": request.headers.get("user-agent"),
|
|
65
|
+
"http.route": None, # Will be set after routing
|
|
66
|
+
"network.protocol.version": "1.1", # FastAPI/Starlette typically uses HTTP/1.1
|
|
67
|
+
"server.address": request.url.hostname,
|
|
68
|
+
"server.port": request.url.port or (443 if request.url.scheme == 'https' else 80),
|
|
69
|
+
"client.address": request.client.host if request.client else None,
|
|
70
|
+
},
|
|
71
|
+
kind=SpanKind.SERVER
|
|
72
|
+
) as span:
|
|
73
|
+
# Log request start
|
|
74
|
+
self.otel.logger.logger.info(f"Request started: {request.method} {request.url.path}",
|
|
75
|
+
extra={"http.method": request.method, "http.path": request.url.path})
|
|
76
|
+
|
|
77
|
+
# Store span in request state for access in routes
|
|
78
|
+
request.state.span = span
|
|
79
|
+
request.state.trace_token = token
|
|
80
|
+
|
|
81
|
+
start_time = time.time()
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# Process request
|
|
85
|
+
response = await call_next(request)
|
|
86
|
+
|
|
87
|
+
# After routing, update span name and route if available
|
|
88
|
+
if hasattr(request, 'scope') and 'path' in request.scope:
|
|
89
|
+
route = request.scope.get('path', request.url.path)
|
|
90
|
+
span.update_name(f"{request.method} {route}")
|
|
91
|
+
span.set_attribute("http.route", route)
|
|
92
|
+
|
|
93
|
+
# Set response attributes using new semantic conventions
|
|
94
|
+
span.set_attribute("http.response.status_code", response.status_code)
|
|
95
|
+
span.set_attribute("http.status_code", response.status_code) # Deprecated
|
|
96
|
+
|
|
97
|
+
# Set span status based on HTTP status code
|
|
98
|
+
if response.status_code >= 400:
|
|
99
|
+
span.set_status(Status(StatusCode.ERROR, f"HTTP {response.status_code}"))
|
|
100
|
+
else:
|
|
101
|
+
span.set_status(Status(StatusCode.OK))
|
|
102
|
+
|
|
103
|
+
# Log request completion
|
|
104
|
+
self.otel.logger.logger.info(f"Request completed: {response.status_code}",
|
|
105
|
+
extra={"http.status_code": response.status_code})
|
|
106
|
+
otel.force_flush(timeout_millis=100)
|
|
107
|
+
return response
|
|
108
|
+
|
|
109
|
+
except Exception as e:
|
|
110
|
+
# Record exception
|
|
111
|
+
span.record_exception(e)
|
|
112
|
+
span.set_status(Status(StatusCode.ERROR, str(e)))
|
|
113
|
+
span.add_event("exception", {
|
|
114
|
+
"exception.type": type(e).__name__,
|
|
115
|
+
"exception.message": str(e)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
# Log error
|
|
119
|
+
self.otel.logger.logger.error(f"Unhandled exception: {e}",
|
|
120
|
+
exc_info=True,
|
|
121
|
+
extra={"exception.type": type(e).__name__})
|
|
122
|
+
|
|
123
|
+
raise
|
|
124
|
+
|
|
125
|
+
finally:
|
|
126
|
+
# Detach context
|
|
127
|
+
self.otel.detach_context(token)
|
|
128
|
+
|
|
129
|
+
# Add middleware to app
|
|
130
|
+
app.add_middleware(OTELMiddleware)
|
|
131
|
+
|
|
132
|
+
def fastapi_exception_handler(otel, request, exc):
|
|
133
|
+
"""
|
|
134
|
+
Handle FastAPI exceptions and record them in the current span.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
# Determine the status code
|
|
138
|
+
if isinstance(exc, HTTPException):
|
|
139
|
+
status_code = exc.status_code
|
|
140
|
+
error_detail = exc.detail
|
|
141
|
+
elif hasattr(exc, 'status_code'):
|
|
142
|
+
status_code = exc.status_code
|
|
143
|
+
error_detail = str(exc)
|
|
144
|
+
elif hasattr(exc, 'code'):
|
|
145
|
+
status_code = exc.code if isinstance(exc.code, int) else 500
|
|
146
|
+
error_detail = str(exc)
|
|
147
|
+
else:
|
|
148
|
+
status_code = 500
|
|
149
|
+
error_detail = str(exc)
|
|
150
|
+
|
|
151
|
+
# Record exception in span if available and still recording
|
|
152
|
+
if hasattr(request.state, 'span') and request.state.span.is_recording():
|
|
153
|
+
# Update both new and old attribute names for compatibility
|
|
154
|
+
request.state.span.set_attribute("http.response.status_code", status_code)
|
|
155
|
+
request.state.span.set_attribute("error.type", type(exc).__name__)
|
|
156
|
+
|
|
157
|
+
request.state.span.record_exception(exc)
|
|
158
|
+
request.state.span.set_status(Status(StatusCode.ERROR, str(exc)))
|
|
159
|
+
request.state.span.add_event("exception", {
|
|
160
|
+
"exception.type": type(exc).__name__,
|
|
161
|
+
"exception.message": str(exc)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
# Log the error
|
|
165
|
+
otel.logger.logger.error(f"Unhandled exception: {exc} (status: {status_code})",
|
|
166
|
+
exc_info=True,
|
|
167
|
+
extra={
|
|
168
|
+
"exception.type": type(exc).__name__,
|
|
169
|
+
"http.status_code": status_code
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
# Return error response
|
|
173
|
+
return JSONResponse(
|
|
174
|
+
status_code=status_code,
|
|
175
|
+
content={
|
|
176
|
+
"error": error_detail,
|
|
177
|
+
"type": type(exc).__name__
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Optional: Dependency injection helper for accessing the span in routes
|
|
182
|
+
def get_current_span(request: Request):
|
|
183
|
+
"""
|
|
184
|
+
FastAPI dependency to get the current span in route handlers.
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
from fastapi import Depends
|
|
188
|
+
from rebrandly_otel.fastapi_integration import get_current_span
|
|
189
|
+
|
|
190
|
+
@app.get("/example")
|
|
191
|
+
async def example(span = Depends(get_current_span)):
|
|
192
|
+
if span:
|
|
193
|
+
span.add_event("custom_event", {"key": "value"})
|
|
194
|
+
return {"status": "ok"}
|
|
195
|
+
"""
|
|
196
|
+
if hasattr(request.state, 'span'):
|
|
197
|
+
return request.state.span
|
|
198
|
+
return None
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
# flask_integration.py
|
|
2
2
|
"""Flask integration for Rebrandly OTEL SDK."""
|
|
3
|
-
from typing import TYPE_CHECKING
|
|
4
|
-
from opentelemetry.trace import Status, StatusCode, SpanKind
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import json
|
|
5
|
+
from rebrandly_otel import Status, StatusCode, SpanKind
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
from flask import request, jsonify
|
|
8
|
+
from werkzeug.exceptions import HTTPException
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def setup_flask(otel, app):
|
|
10
12
|
"""
|
|
11
13
|
Setup Flask application with OTEL instrumentation.
|
|
12
|
-
|
|
14
|
+
|
|
13
15
|
Example:
|
|
14
16
|
from flask import Flask
|
|
15
17
|
from rebrandly_otel import otel
|
|
@@ -23,31 +25,39 @@ def setup_flask(otel: 'RebrandlyOTEL', app):
|
|
|
23
25
|
app.register_error_handler(Exception, lambda e: flask_error_handler(otel, e))
|
|
24
26
|
return app
|
|
25
27
|
|
|
26
|
-
def app_before_request(otel
|
|
28
|
+
def app_before_request(otel):
|
|
27
29
|
"""
|
|
28
30
|
Setup tracing for incoming Flask request.
|
|
29
31
|
To be used with Flask's before_request hook.
|
|
30
32
|
"""
|
|
31
|
-
from flask import request
|
|
32
33
|
|
|
33
34
|
# Extract trace context from headers
|
|
34
35
|
headers = dict(request.headers)
|
|
35
36
|
token = otel.attach_context(headers)
|
|
36
37
|
request.trace_token = token
|
|
37
38
|
|
|
39
|
+
# Determine span name - use route if available, otherwise just method
|
|
40
|
+
# Route will be available after request routing is done
|
|
41
|
+
span_name = f"{request.method} {request.path}"
|
|
42
|
+
|
|
38
43
|
# Start span for request using start_as_current_span to make it the active span
|
|
39
44
|
span = otel.tracer.tracer.start_as_current_span(
|
|
40
|
-
|
|
45
|
+
span_name,
|
|
41
46
|
attributes={
|
|
42
|
-
|
|
43
|
-
"http.
|
|
44
|
-
"http.
|
|
45
|
-
"http.
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
47
|
+
# Required HTTP attributes per semantic conventions
|
|
48
|
+
"http.request.method": request.method,
|
|
49
|
+
"http.request.headers": json.dumps(request.headers, default=str),
|
|
50
|
+
"http.response.status_code": None, # Will be set in after_request
|
|
51
|
+
"url.full": request.url,
|
|
52
|
+
"url.scheme": request.scheme,
|
|
53
|
+
"url.path": request.path,
|
|
54
|
+
"url.query": request.query_string.decode('utf-8') if request.query_string else None,
|
|
55
|
+
"user_agent.original": request.user_agent.string if request.user_agent else None,
|
|
56
|
+
"http.route": request.path, # Flask doesn't expose route pattern easily
|
|
57
|
+
"network.protocol.version": request.environ.get('SERVER_PROTOCOL', 'HTTP/1.1').split('/')[-1],
|
|
58
|
+
"server.address": request.host.split(':')[0],
|
|
59
|
+
"server.port": request.host.split(':')[1] if ':' in request.host else (443 if request.scheme == 'https' else 80),
|
|
60
|
+
"client.address": request.remote_addr,
|
|
51
61
|
},
|
|
52
62
|
kind=SpanKind.SERVER
|
|
53
63
|
)
|
|
@@ -59,33 +69,22 @@ def app_before_request(otel: 'RebrandlyOTEL'):
|
|
|
59
69
|
otel.logger.logger.info(f"Request started: {request.method} {request.path}",
|
|
60
70
|
extra={"http.method": request.method, "http.path": request.path})
|
|
61
71
|
|
|
62
|
-
def app_after_request(otel
|
|
72
|
+
def app_after_request(otel, response):
|
|
63
73
|
"""
|
|
64
74
|
Cleanup tracing after Flask request completes.
|
|
65
75
|
To be used with Flask's after_request hook.
|
|
66
76
|
"""
|
|
67
|
-
from flask import request
|
|
68
77
|
|
|
69
78
|
# Check if we have a span and it's still recording
|
|
70
79
|
if hasattr(request, 'span') and request.span.is_recording():
|
|
71
|
-
|
|
80
|
+
# Update both new and old attribute names for compatibility
|
|
81
|
+
request.span.set_attribute("http.response.status_code", response.status_code)
|
|
72
82
|
|
|
73
83
|
# Set span status based on HTTP status code
|
|
74
84
|
if response.status_code >= 400:
|
|
75
85
|
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
86
|
else:
|
|
83
87
|
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
88
|
|
|
90
89
|
# Properly close the span context manager
|
|
91
90
|
if hasattr(request, 'span_context'):
|
|
@@ -102,21 +101,14 @@ def app_after_request(otel: 'RebrandlyOTEL', response):
|
|
|
102
101
|
otel.logger.logger.info(f"Request completed: {response.status_code}",
|
|
103
102
|
extra={"http.status_code": response.status_code})
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
otel.meter.GlobalMetrics.invocations.add(1, {
|
|
107
|
-
"endpoint": request.path,
|
|
108
|
-
"method": request.method
|
|
109
|
-
})
|
|
110
|
-
|
|
104
|
+
otel.force_flush(timeout_millis=100)
|
|
111
105
|
return response
|
|
112
106
|
|
|
113
|
-
def flask_error_handler(otel
|
|
107
|
+
def flask_error_handler(otel, exception):
|
|
114
108
|
"""
|
|
115
109
|
Handle Flask exceptions and record them in the current span.
|
|
116
110
|
To be used with Flask's errorhandler decorator.
|
|
117
111
|
"""
|
|
118
|
-
from flask import request, jsonify
|
|
119
|
-
from werkzeug.exceptions import HTTPException
|
|
120
112
|
|
|
121
113
|
# Determine the status code
|
|
122
114
|
if isinstance(exception, HTTPException):
|
|
@@ -130,15 +122,14 @@ def flask_error_handler(otel: 'RebrandlyOTEL', exception):
|
|
|
130
122
|
|
|
131
123
|
# Record exception in span if available and still recording
|
|
132
124
|
if hasattr(request, 'span') and request.span.is_recording():
|
|
133
|
-
request.span.set_attribute("http.status_code", status_code)
|
|
125
|
+
request.span.set_attribute("http.response.status_code", status_code)
|
|
134
126
|
request.span.set_attribute("error.type", type(exception).__name__)
|
|
135
127
|
|
|
136
128
|
request.span.record_exception(exception)
|
|
137
129
|
request.span.set_status(Status(StatusCode.ERROR, str(exception)))
|
|
138
130
|
request.span.add_event("exception", {
|
|
139
131
|
"exception.type": type(exception).__name__,
|
|
140
|
-
"exception.message": str(exception)
|
|
141
|
-
"http.status_code": status_code
|
|
132
|
+
"exception.message": str(exception)
|
|
142
133
|
})
|
|
143
134
|
|
|
144
135
|
# Only close the span if it's still recording (not already ended)
|
|
@@ -155,14 +146,6 @@ def flask_error_handler(otel: 'RebrandlyOTEL', exception):
|
|
|
155
146
|
"http.status_code": status_code
|
|
156
147
|
})
|
|
157
148
|
|
|
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
149
|
# Return error response with the determined status code
|
|
167
150
|
return jsonify({
|
|
168
151
|
"error": str(exception),
|
|
@@ -34,51 +34,24 @@ class RebrandlyMeter:
|
|
|
34
34
|
|
|
35
35
|
# Standardized metric definitions aligned with Node.js
|
|
36
36
|
DEFAULT_METRICS = {
|
|
37
|
-
|
|
38
|
-
name='invocations',
|
|
39
|
-
description='Number of invocations',
|
|
40
|
-
unit='1',
|
|
41
|
-
type=MetricType.COUNTER
|
|
42
|
-
),
|
|
43
|
-
'successful_invocations': MetricDefinition(
|
|
44
|
-
name='successful_invocations',
|
|
45
|
-
description='Number of successful invocations',
|
|
46
|
-
unit='1',
|
|
47
|
-
type=MetricType.COUNTER
|
|
48
|
-
),
|
|
49
|
-
'error_invocations': MetricDefinition(
|
|
50
|
-
name='error_invocations',
|
|
51
|
-
description='Number of error invocations',
|
|
52
|
-
unit='1',
|
|
53
|
-
type=MetricType.COUNTER
|
|
54
|
-
),
|
|
55
|
-
'duration': MetricDefinition(
|
|
56
|
-
name='duration',
|
|
57
|
-
description='Duration of execution in milliseconds',
|
|
58
|
-
unit='ms',
|
|
59
|
-
type=MetricType.HISTOGRAM
|
|
60
|
-
),
|
|
37
|
+
## PROCESS
|
|
61
38
|
'cpu_usage_percentage': MetricDefinition(
|
|
62
|
-
name='
|
|
63
|
-
description='
|
|
64
|
-
unit='
|
|
39
|
+
name='process.cpu.utilization',
|
|
40
|
+
description='Difference in process.cpu.time since the last measurement, divided by the elapsed time and number of CPUs available to the process.',
|
|
41
|
+
unit='1',
|
|
65
42
|
type=MetricType.GAUGE
|
|
66
43
|
),
|
|
67
44
|
'memory_usage_bytes': MetricDefinition(
|
|
68
|
-
name='
|
|
69
|
-
description='
|
|
45
|
+
name='process.memory.used',
|
|
46
|
+
description='The amount of physical memory in use.',
|
|
70
47
|
unit='By',
|
|
71
48
|
type=MetricType.GAUGE
|
|
72
|
-
)
|
|
49
|
+
)
|
|
73
50
|
}
|
|
74
51
|
|
|
75
52
|
class GlobalMetrics:
|
|
76
53
|
def __init__(self, rebrandly_meter):
|
|
77
54
|
self.__rebrandly_meter = rebrandly_meter
|
|
78
|
-
self.invocations: Counter = self.__rebrandly_meter.get_metric('invocations')
|
|
79
|
-
self.successful_invocations: Counter = self.__rebrandly_meter.get_metric('successful_invocations')
|
|
80
|
-
self.error_invocations: Counter = self.__rebrandly_meter.get_metric('error_invocations')
|
|
81
|
-
self.duration: Histogram = self.__rebrandly_meter.get_metric('duration')
|
|
82
55
|
self.cpu_usage_percentage: Gauge = self.__rebrandly_meter.get_metric('cpu_usage_percentage')
|
|
83
56
|
self.memory_usage_bytes: Gauge = self.__rebrandly_meter.get_metric('memory_usage_bytes')
|
|
84
57
|
|
|
@@ -160,8 +160,6 @@ class RebrandlyOTEL:
|
|
|
160
160
|
|
|
161
161
|
result = None
|
|
162
162
|
try:
|
|
163
|
-
# Increment invocation counter with standardized metric name
|
|
164
|
-
self.meter.GlobalMetrics.invocations.add(1, {'function': span_name})
|
|
165
163
|
|
|
166
164
|
# Create and execute within span
|
|
167
165
|
with self.tracer.start_span(
|
|
@@ -204,17 +202,9 @@ class RebrandlyOTEL:
|
|
|
204
202
|
complete_event_attrs['success'] = success
|
|
205
203
|
span.add_event("lambda.invocation.complete", complete_event_attrs)
|
|
206
204
|
|
|
207
|
-
# Increment success counter with standardized metric
|
|
208
|
-
self.meter.GlobalMetrics.successful_invocations.add(1, {'function': span_name})
|
|
209
|
-
|
|
210
205
|
return result
|
|
211
206
|
|
|
212
207
|
except Exception as e:
|
|
213
|
-
# Increment error counter with standardized metric
|
|
214
|
-
self.meter.GlobalMetrics.error_invocations.add(1, {
|
|
215
|
-
'function': span_name,
|
|
216
|
-
'error': type(e).__name__
|
|
217
|
-
})
|
|
218
208
|
|
|
219
209
|
# Add failed completion event with error attribute (THIS IS THE KEY ADDITION)
|
|
220
210
|
span.add_event("lambda.invocation.complete", {
|
|
@@ -236,13 +226,9 @@ class RebrandlyOTEL:
|
|
|
236
226
|
from opentelemetry import context as otel_context
|
|
237
227
|
otel_context.detach(token)
|
|
238
228
|
|
|
239
|
-
# Record duration with standardized metric
|
|
240
|
-
duration = (datetime.now() - start_time).total_seconds() * 1000
|
|
241
|
-
self.meter.GlobalMetrics.duration.record(duration, {'function': span_name})
|
|
242
|
-
|
|
243
229
|
# Force flush if enabled
|
|
244
230
|
if auto_flush:
|
|
245
|
-
self.logger.logger.info(f"[Rebrandly OTEL] Lambda '{span_name}'
|
|
231
|
+
self.logger.logger.info(f"[Rebrandly OTEL] Lambda '{span_name}', flushing...")
|
|
246
232
|
flush_success = self.force_flush(timeout_millis=1000)
|
|
247
233
|
if not flush_success:
|
|
248
234
|
self.logger.logger.warning("[Rebrandly OTEL] Force flush may not have completed fully")
|
|
@@ -272,9 +258,6 @@ class RebrandlyOTEL:
|
|
|
272
258
|
|
|
273
259
|
result = None
|
|
274
260
|
try:
|
|
275
|
-
# Increment invocations counter with standardized metric
|
|
276
|
-
self.meter.GlobalMetrics.invocations.add(1, {'handler': span_name})
|
|
277
|
-
|
|
278
261
|
# Create span and execute function
|
|
279
262
|
span_function = self.span
|
|
280
263
|
if record is not None and ('MessageAttributes' in record or 'messageAttributes' in record):
|
|
@@ -318,18 +301,9 @@ class RebrandlyOTEL:
|
|
|
318
301
|
complete_event_attrs['success'] = success
|
|
319
302
|
span_context.add_event("message.processing.complete", complete_event_attrs)
|
|
320
303
|
|
|
321
|
-
# Increment success counter with standardized metric
|
|
322
|
-
self.meter.GlobalMetrics.successful_invocations.add(1, {'handler': span_name})
|
|
323
|
-
|
|
324
304
|
return result
|
|
325
305
|
|
|
326
306
|
except Exception as e:
|
|
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
|
-
})
|
|
332
|
-
|
|
333
307
|
# Record the exception in the span
|
|
334
308
|
if 'span_context' in locals():
|
|
335
309
|
span_context.record_exception(e)
|
|
@@ -345,10 +319,6 @@ class RebrandlyOTEL:
|
|
|
345
319
|
raise
|
|
346
320
|
|
|
347
321
|
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
|
-
|
|
352
322
|
if auto_flush:
|
|
353
323
|
self.force_flush(start_datetime=start_func)
|
|
354
324
|
|
|
@@ -371,16 +341,14 @@ class RebrandlyOTEL:
|
|
|
371
341
|
|
|
372
342
|
if start_datetime is not None:
|
|
373
343
|
end_func = datetime.now()
|
|
374
|
-
duration = (end_func - start_datetime).total_seconds() * 1000
|
|
375
344
|
cpu_percent = psutil.cpu_percent(interval=0.1) # Shorter interval for Lambda
|
|
376
345
|
memory = psutil.virtual_memory()
|
|
377
346
|
|
|
378
347
|
# Record metrics using standardized names
|
|
379
|
-
self.meter.GlobalMetrics.duration.record(duration, {'source': 'force_flush'})
|
|
380
348
|
self.meter.GlobalMetrics.memory_usage_bytes.set(memory.used)
|
|
381
349
|
self.meter.GlobalMetrics.cpu_usage_percentage.set(cpu_percent)
|
|
382
350
|
|
|
383
|
-
print(f"Function
|
|
351
|
+
print(f"Function Memory usage: {memory.percent}%, CPU usage: {cpu_percent}%")
|
|
384
352
|
|
|
385
353
|
try:
|
|
386
354
|
# Flush traces
|
|
File without changes
|
{rebrandly_otel-0.1.17 → rebrandly_otel-0.1.18}/rebrandly_otel.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|