rebrandly-otel 0.1.17__py3-none-any.whl → 0.1.19__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/__init__.py +4 -2
- rebrandly_otel/fastapi_support.py +198 -0
- rebrandly_otel/flask_support.py +35 -52
- rebrandly_otel/logs.py +7 -3
- rebrandly_otel/metrics.py +13 -36
- rebrandly_otel/otel_utils.py +31 -7
- rebrandly_otel/rebrandly_otel.py +4 -35
- rebrandly_otel/traces.py +10 -6
- {rebrandly_otel-0.1.17.dist-info → rebrandly_otel-0.1.19.dist-info}/METADATA +88 -16
- rebrandly_otel-0.1.19.dist-info/RECORD +13 -0
- rebrandly_otel-0.1.17.dist-info/RECORD +0 -12
- {rebrandly_otel-0.1.17.dist-info → rebrandly_otel-0.1.19.dist-info}/WHEEL +0 -0
- {rebrandly_otel-0.1.17.dist-info → rebrandly_otel-0.1.19.dist-info}/licenses/LICENSE +0 -0
- {rebrandly_otel-0.1.17.dist-info → rebrandly_otel-0.1.19.dist-info}/top_level.txt +0 -0
rebrandly_otel/__init__.py
CHANGED
|
@@ -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
|
rebrandly_otel/flask_support.py
CHANGED
|
@@ -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),
|
rebrandly_otel/logs.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# logs.py
|
|
2
2
|
"""Logging implementation for Rebrandly OTEL SDK."""
|
|
3
3
|
import logging
|
|
4
|
+
import sys
|
|
4
5
|
from typing import Optional
|
|
5
6
|
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
|
|
6
7
|
from opentelemetry.sdk._logs.export import (
|
|
@@ -34,12 +35,15 @@ class RebrandlyLogger:
|
|
|
34
35
|
self._provider.add_log_record_processor(SimpleLogRecordProcessor(console_exporter))
|
|
35
36
|
|
|
36
37
|
# Add OTLP exporter if configured
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
otel_endpoint = get_otlp_endpoint()
|
|
39
|
+
if otel_endpoint:
|
|
40
|
+
otlp_exporter = OTLPLogExporter(
|
|
41
|
+
timeout=5,
|
|
42
|
+
endpoint=otel_endpoint
|
|
43
|
+
)
|
|
39
44
|
batch_processor = BatchLogRecordProcessor(otlp_exporter, export_timeout_millis=get_millis_batch_time())
|
|
40
45
|
self._provider.add_log_record_processor(batch_processor)
|
|
41
46
|
|
|
42
|
-
# Set as global provider
|
|
43
47
|
set_logger_provider(self._provider)
|
|
44
48
|
|
|
45
49
|
# Configure standard logging
|
rebrandly_otel/metrics.py
CHANGED
|
@@ -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
|
|
|
@@ -105,8 +78,12 @@ class RebrandlyMeter:
|
|
|
105
78
|
readers.append(console_reader)
|
|
106
79
|
|
|
107
80
|
# Add OTLP exporter if configured
|
|
108
|
-
|
|
109
|
-
|
|
81
|
+
otel_endpoint = get_otlp_endpoint()
|
|
82
|
+
if otel_endpoint is not None:
|
|
83
|
+
otlp_exporter = OTLPMetricExporter(
|
|
84
|
+
endpoint=otel_endpoint,
|
|
85
|
+
timeout=5
|
|
86
|
+
)
|
|
110
87
|
otlp_reader = PeriodicExportingMetricReader(otlp_exporter, export_interval_millis=get_millis_batch_time())
|
|
111
88
|
readers.append(otlp_reader)
|
|
112
89
|
|
rebrandly_otel/otel_utils.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
5
|
import sys
|
|
6
|
+
import grpc
|
|
6
7
|
|
|
7
8
|
from opentelemetry.sdk.resources import Resource
|
|
8
9
|
from opentelemetry.semconv.attributes import service_attributes
|
|
@@ -33,8 +34,9 @@ def get_package_version():
|
|
|
33
34
|
try:
|
|
34
35
|
from importlib_metadata import version, PackageNotFoundError
|
|
35
36
|
return version('rebrandly_otel')
|
|
36
|
-
except:
|
|
37
|
-
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(f"[OTEL Utils] Warning: Could not get package version: {e}")
|
|
39
|
+
return '0.1.0'
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
def get_service_name(service_name: str = None) -> str:
|
|
@@ -49,10 +51,31 @@ def get_service_version(service_version: str = None) -> str:
|
|
|
49
51
|
return service_version
|
|
50
52
|
|
|
51
53
|
|
|
52
|
-
def get_otlp_endpoint(otlp_endpoint: str = None) -> str:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
def get_otlp_endpoint(otlp_endpoint: str = None) -> str | None:
|
|
55
|
+
endpoint = otlp_endpoint or os.environ.get('OTEL_EXPORTER_OTLP_ENDPOINT', None)
|
|
56
|
+
|
|
57
|
+
if endpoint is not None:
|
|
58
|
+
try:
|
|
59
|
+
from urllib.parse import urlparse
|
|
60
|
+
|
|
61
|
+
# Parse the endpoint
|
|
62
|
+
parsed = urlparse(endpoint if '://' in endpoint else f'http://{endpoint}')
|
|
63
|
+
host = parsed.hostname
|
|
64
|
+
port = parsed.port
|
|
65
|
+
|
|
66
|
+
# Test gRPC connection
|
|
67
|
+
channel = grpc.insecure_channel(f'{host}:{port}')
|
|
68
|
+
try:
|
|
69
|
+
# Wait for the channel to be ready
|
|
70
|
+
grpc.channel_ready_future(channel).result(timeout=3)
|
|
71
|
+
return endpoint
|
|
72
|
+
finally:
|
|
73
|
+
channel.close()
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f"[OTEL] Failed to connect to OTLP endpoint {endpoint}: {e}")
|
|
77
|
+
return None
|
|
78
|
+
return endpoint
|
|
56
79
|
|
|
57
80
|
def is_otel_debug() -> bool:
|
|
58
81
|
return os.environ.get('OTEL_DEBUG', 'false').lower() == 'true'
|
|
@@ -61,5 +84,6 @@ def is_otel_debug() -> bool:
|
|
|
61
84
|
def get_millis_batch_time():
|
|
62
85
|
try:
|
|
63
86
|
return int(os.environ.get('BATCH_EXPORT_TIME_MILLIS', 100))
|
|
64
|
-
except:
|
|
87
|
+
except Exception as e:
|
|
88
|
+
print(f"[OTEL Utils] Warning: Invalid BATCH_EXPORT_TIME_MILLIS value, using default 5000ms: {e}")
|
|
65
89
|
return 5000
|
rebrandly_otel/rebrandly_otel.py
CHANGED
|
@@ -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,12 +258,9 @@ 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
|
-
if record is not None and ('MessageAttributes' in record or 'messageAttributes' in record):
|
|
263
|
+
if record is not None and (('MessageAttributes' in record or 'messageAttributes' in record) or ('Sns' in record and 'MessageAttributes' in record['Sns'])):
|
|
281
264
|
span_function = self.aws_message_span
|
|
282
265
|
|
|
283
266
|
with span_function(span_name, message=record, attributes=span_attributes, kind=kind) as span_context:
|
|
@@ -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
|
|
@@ -402,6 +370,7 @@ class RebrandlyOTEL:
|
|
|
402
370
|
time.sleep(0.1)
|
|
403
371
|
|
|
404
372
|
except Exception as e:
|
|
373
|
+
print(f"[Rebrandly OTEL] Error during force flush: {e}")
|
|
405
374
|
success = False
|
|
406
375
|
|
|
407
376
|
return success
|
rebrandly_otel/traces.py
CHANGED
|
@@ -32,8 +32,12 @@ class RebrandlyTracer:
|
|
|
32
32
|
self._provider.add_span_processor(SimpleSpanProcessor(console_exporter))
|
|
33
33
|
|
|
34
34
|
# Add OTLP exporter if configured
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
otel_endpoint = get_otlp_endpoint()
|
|
36
|
+
if otel_endpoint is not None:
|
|
37
|
+
otlp_exporter = OTLPSpanExporter(
|
|
38
|
+
endpoint=otel_endpoint,
|
|
39
|
+
timeout=5
|
|
40
|
+
)
|
|
37
41
|
|
|
38
42
|
# Use batch processor for production
|
|
39
43
|
batch_processor = BatchSpanProcessor(otlp_exporter, export_timeout_millis=get_millis_batch_time())
|
|
@@ -139,14 +143,14 @@ class RebrandlyTracer:
|
|
|
139
143
|
if target_span and hasattr(target_span, 'record_exception'):
|
|
140
144
|
if exception is not None:
|
|
141
145
|
target_span.record_exception(exception)
|
|
142
|
-
|
|
146
|
+
target_span.set_status(trace.Status(trace.StatusCode.ERROR, str(exception)))
|
|
143
147
|
|
|
144
148
|
|
|
145
149
|
def record_span_success(self, span: Optional[Span] = None, msg: Optional[str] = None):
|
|
146
|
-
"""Record
|
|
150
|
+
"""Record success on a span."""
|
|
147
151
|
target_span = span or self.get_current_span()
|
|
148
|
-
if target_span and hasattr(target_span, '
|
|
149
|
-
target_span.set_status(trace.Status(trace.StatusCode.OK)
|
|
152
|
+
if target_span and hasattr(target_span, 'set_status'):
|
|
153
|
+
target_span.set_status(trace.Status(trace.StatusCode.OK))
|
|
150
154
|
|
|
151
155
|
|
|
152
156
|
def add_event(self, name: str, attributes: Optional[Dict[str, Any]] = None, span: Optional[Span] = None):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rebrandly_otel
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.19
|
|
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
|
|
|
@@ -375,6 +360,93 @@ if __name__ == '__main__':
|
|
|
375
360
|
app.run(debug=True)
|
|
376
361
|
```
|
|
377
362
|
|
|
363
|
+
###
|
|
364
|
+
FastAPI
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
|
|
368
|
+
# main_fastapi.py
|
|
369
|
+
from fastapi import FastAPI, HTTPException, Depends
|
|
370
|
+
from contextlib import asynccontextmanager
|
|
371
|
+
from src.rebrandly_otel import otel, logger, force_flush
|
|
372
|
+
from src.fastapi_support import setup_fastapi, get_current_span
|
|
373
|
+
from datetime import datetime
|
|
374
|
+
from typing import Optional
|
|
375
|
+
import uvicorn
|
|
376
|
+
|
|
377
|
+
@asynccontextmanager
|
|
378
|
+
async def lifespan(app: FastAPI):
|
|
379
|
+
# Startup
|
|
380
|
+
logger.info("FastAPI application starting up")
|
|
381
|
+
yield
|
|
382
|
+
# Shutdown
|
|
383
|
+
logger.info("FastAPI application shutting down")
|
|
384
|
+
force_flush()
|
|
385
|
+
|
|
386
|
+
app = FastAPI(title="FastAPI OTEL Example", lifespan=lifespan)
|
|
387
|
+
|
|
388
|
+
# Setup FastAPI with OTEL
|
|
389
|
+
setup_fastapi(otel, app)
|
|
390
|
+
|
|
391
|
+
@app.get("/health")
|
|
392
|
+
async def health():
|
|
393
|
+
"""Health check endpoint."""
|
|
394
|
+
logger.info("Health check requested")
|
|
395
|
+
return {"status": "healthy"}
|
|
396
|
+
|
|
397
|
+
@app.post("/process")
|
|
398
|
+
@app.get("/process")
|
|
399
|
+
async def process(span = Depends(get_current_span)):
|
|
400
|
+
"""Process endpoint with custom span."""
|
|
401
|
+
with otel.span("process_request"):
|
|
402
|
+
logger.info("Processing request")
|
|
403
|
+
|
|
404
|
+
# You can also use the injected span directly
|
|
405
|
+
if span:
|
|
406
|
+
span.add_event("custom_processing_event", {
|
|
407
|
+
"timestamp": datetime.now().isoformat()
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
# Simulate some processing
|
|
411
|
+
result = {
|
|
412
|
+
"processed": True,
|
|
413
|
+
"timestamp": datetime.now().isoformat()
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
logger.info(f"Returning result: {result}")
|
|
417
|
+
return result
|
|
418
|
+
|
|
419
|
+
@app.get("/error")
|
|
420
|
+
async def error():
|
|
421
|
+
"""Endpoint that raises an error."""
|
|
422
|
+
logger.error("Error endpoint called")
|
|
423
|
+
raise HTTPException(status_code=400, detail="Simulated error")
|
|
424
|
+
|
|
425
|
+
@app.get("/exception")
|
|
426
|
+
async def exception():
|
|
427
|
+
"""Endpoint that raises an unhandled exception."""
|
|
428
|
+
logger.error("Exception endpoint called")
|
|
429
|
+
raise ValueError("Simulated unhandled exception")
|
|
430
|
+
|
|
431
|
+
@app.get("/items/{item_id}")
|
|
432
|
+
async def get_item(item_id: int, q: Optional[str] = None):
|
|
433
|
+
"""Example endpoint with path and query parameters."""
|
|
434
|
+
with otel.span("fetch_item", attributes={"item_id": item_id, "query": q}):
|
|
435
|
+
logger.info(f"Fetching item {item_id} with query: {q}")
|
|
436
|
+
|
|
437
|
+
if item_id == 999:
|
|
438
|
+
raise HTTPException(status_code=404, detail="Item not found")
|
|
439
|
+
|
|
440
|
+
return {
|
|
441
|
+
"item_id": item_id,
|
|
442
|
+
"name": f"Item {item_id}",
|
|
443
|
+
"query": q
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if __name__ == "__main__":
|
|
447
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
448
|
+
```
|
|
449
|
+
|
|
378
450
|
### More examples
|
|
379
451
|
You can find More examples [here](examples)
|
|
380
452
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
rebrandly_otel/__init__.py,sha256=tkZQJo5hR4FJ4dIRc-3b_YGxGo-uq-DsiSz8shdac-k,397
|
|
2
|
+
rebrandly_otel/fastapi_support.py,sha256=RuBBZJuzr3osBDrkHZ0oQPV70pmvnqxTfBBDVFBFQlo,8019
|
|
3
|
+
rebrandly_otel/flask_support.py,sha256=cUVMGTjN6N8xZD4Zyng2LRWhNj62C5nmTH91SnYBp2A,6072
|
|
4
|
+
rebrandly_otel/logs.py,sha256=5byeN-cDmBRpeZDw9IBz_vuiJm3wsGEbcAk5pwYHNAU,3791
|
|
5
|
+
rebrandly_otel/metrics.py,sha256=8aAqdr3SAcX_rVivTl_aHeD_BRByt-Qnfij_51Y7Fn0,7561
|
|
6
|
+
rebrandly_otel/otel_utils.py,sha256=ej6ayM1e9muHx1RNaEBZJTBIL_G0QAtDkqkS1VsoTyo,3026
|
|
7
|
+
rebrandly_otel/rebrandly_otel.py,sha256=hyNMFQzjZdP6oaXd0x1VKSKZg4-IuCtO6XeAiRZc7oo,21299
|
|
8
|
+
rebrandly_otel/traces.py,sha256=JY_3RWbzpUxzEx3GqTVgggsyA2DB4oR-zDftIFFJha4,7174
|
|
9
|
+
rebrandly_otel-0.1.19.dist-info/licenses/LICENSE,sha256=KMXHvTwP62S2q-SG7CFfMU_09rUwxiqlM0izaYGdcgY,1103
|
|
10
|
+
rebrandly_otel-0.1.19.dist-info/METADATA,sha256=DUACwLo6mJSEjir0pfY8kszp-G36JDnz5lh3uivRECg,12854
|
|
11
|
+
rebrandly_otel-0.1.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
12
|
+
rebrandly_otel-0.1.19.dist-info/top_level.txt,sha256=26PSC1gjVUl8tTH5QfKbFevjVV4E2yojoukEfiTScvM,15
|
|
13
|
+
rebrandly_otel-0.1.19.dist-info/RECORD,,
|
|
@@ -1,12 +0,0 @@
|
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|