devinspector 2.0.6__tar.gz → 2.1.0__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.
- devinspector-2.1.0/PKG-INFO +27 -0
- devinspector-2.1.0/devinspector/__init__.py +3 -0
- {devinspector-2.0.6 → devinspector-2.1.0}/devinspector/core.py +71 -1
- devinspector-2.1.0/devinspector/fastapi.py +73 -0
- devinspector-2.1.0/devinspector/flask.py +88 -0
- devinspector-2.1.0/devinspector.egg-info/PKG-INFO +27 -0
- devinspector-2.1.0/pyproject.toml +40 -0
- devinspector-2.0.6/PKG-INFO +0 -15
- devinspector-2.0.6/devinspector/__init__.py +0 -3
- devinspector-2.0.6/devinspector/fastapi.py +0 -39
- devinspector-2.0.6/devinspector/flask.py +0 -55
- devinspector-2.0.6/devinspector.egg-info/PKG-INFO +0 -15
- devinspector-2.0.6/pyproject.toml +0 -23
- {devinspector-2.0.6 → devinspector-2.1.0}/README.md +0 -0
- {devinspector-2.0.6 → devinspector-2.1.0}/devinspector.egg-info/SOURCES.txt +0 -0
- {devinspector-2.0.6 → devinspector-2.1.0}/devinspector.egg-info/dependency_links.txt +0 -0
- {devinspector-2.0.6 → devinspector-2.1.0}/devinspector.egg-info/requires.txt +0 -0
- {devinspector-2.0.6 → devinspector-2.1.0}/devinspector.egg-info/top_level.txt +0 -0
- {devinspector-2.0.6 → devinspector-2.1.0}/setup.cfg +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devinspector
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: SDK Python de monitoramento e auditoria em tempo real | DevInspector
|
|
5
|
+
Author-email: DevInspector <suporte@devinspector.com.br>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://devinspector.com.br
|
|
8
|
+
Keywords: monitoring,error-tracking,apm,audit,devinspector,fastapi,flask
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Topic :: System :: Monitoring
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Provides-Extra: fastapi
|
|
24
|
+
Requires-Dist: fastapi; extra == "fastapi"
|
|
25
|
+
Requires-Dist: starlette; extra == "fastapi"
|
|
26
|
+
Provides-Extra: flask
|
|
27
|
+
Requires-Dist: flask; extra == "flask"
|
|
@@ -5,9 +5,15 @@ import queue
|
|
|
5
5
|
import threading
|
|
6
6
|
import traceback
|
|
7
7
|
import urllib.request
|
|
8
|
+
import asyncio
|
|
9
|
+
import functools
|
|
10
|
+
import contextvars
|
|
8
11
|
from datetime import datetime, timezone
|
|
9
12
|
from typing import Any, Dict, Optional
|
|
10
13
|
|
|
14
|
+
# Variável de contexto para armazenar dados da requisição atual (APM)
|
|
15
|
+
# Substitui o papel do AsyncLocalStorage do Node.js
|
|
16
|
+
request_context = contextvars.ContextVar("request_context", default=None)
|
|
11
17
|
|
|
12
18
|
class AuditCore:
|
|
13
19
|
def __init__(self):
|
|
@@ -49,6 +55,9 @@ class AuditCore:
|
|
|
49
55
|
status_code: int,
|
|
50
56
|
duration_ms: float,
|
|
51
57
|
user_agent: str = "",
|
|
58
|
+
route: str = "",
|
|
59
|
+
db_queries_count: int = 0,
|
|
60
|
+
slow_query_ms: float = 0,
|
|
52
61
|
) -> None:
|
|
53
62
|
payload = {
|
|
54
63
|
"type": "request_metric",
|
|
@@ -61,6 +70,9 @@ class AuditCore:
|
|
|
61
70
|
"metadata": {
|
|
62
71
|
"environment": self.environment,
|
|
63
72
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
73
|
+
"route": route or url,
|
|
74
|
+
"dbQueriesCount": db_queries_count,
|
|
75
|
+
"slowQueryMs": round(slow_query_ms, 2),
|
|
64
76
|
},
|
|
65
77
|
}
|
|
66
78
|
self._enqueue(payload)
|
|
@@ -211,4 +223,62 @@ class AuditCore:
|
|
|
211
223
|
|
|
212
224
|
|
|
213
225
|
# Singleton Global
|
|
214
|
-
audit = AuditCore()
|
|
226
|
+
audit = AuditCore()
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def audit_operation(name: str, threshold_ms: int = 300):
|
|
230
|
+
"""
|
|
231
|
+
Decorador para monitorar funções de banco de dados ou chamadas externas.
|
|
232
|
+
Alimenta o request_context atual com a contagem de queries e lentidão.
|
|
233
|
+
Suporta tanto funções síncronas (def) quanto assíncronas (async def).
|
|
234
|
+
"""
|
|
235
|
+
def decorator(func):
|
|
236
|
+
@functools.wraps(func)
|
|
237
|
+
async def async_wrapper(*args, **kwargs):
|
|
238
|
+
ctx = request_context.get()
|
|
239
|
+
if ctx is not None:
|
|
240
|
+
ctx["queriesCount"] = ctx.get("queriesCount", 0) + 1
|
|
241
|
+
|
|
242
|
+
start = time.perf_counter()
|
|
243
|
+
try:
|
|
244
|
+
result = await func(*args, **kwargs)
|
|
245
|
+
duration_ms = (time.perf_counter() - start) * 1000
|
|
246
|
+
|
|
247
|
+
if duration_ms > threshold_ms and ctx is not None:
|
|
248
|
+
ctx["slowQueries"] = max(ctx.get("slowQueries", 0), duration_ms)
|
|
249
|
+
audit.capture_message(
|
|
250
|
+
f"Slow Query detectada em [{name}]",
|
|
251
|
+
{"durationMs": round(duration_ms), "operationName": name}
|
|
252
|
+
)
|
|
253
|
+
return result
|
|
254
|
+
except Exception as e:
|
|
255
|
+
duration_ms = (time.perf_counter() - start) * 1000
|
|
256
|
+
audit.capture_exception(e, {"operationName": name, "durationMs": round(duration_ms)})
|
|
257
|
+
raise
|
|
258
|
+
|
|
259
|
+
@functools.wraps(func)
|
|
260
|
+
def sync_wrapper(*args, **kwargs):
|
|
261
|
+
ctx = request_context.get()
|
|
262
|
+
if ctx is not None:
|
|
263
|
+
ctx["queriesCount"] = ctx.get("queriesCount", 0) + 1
|
|
264
|
+
|
|
265
|
+
start = time.perf_counter()
|
|
266
|
+
try:
|
|
267
|
+
result = func(*args, **kwargs)
|
|
268
|
+
duration_ms = (time.perf_counter() - start) * 1000
|
|
269
|
+
|
|
270
|
+
if duration_ms > threshold_ms and ctx is not None:
|
|
271
|
+
ctx["slowQueries"] = max(ctx.get("slowQueries", 0), duration_ms)
|
|
272
|
+
audit.capture_message(
|
|
273
|
+
f"Slow Query detectada em [{name}]",
|
|
274
|
+
{"durationMs": round(duration_ms), "operationName": name}
|
|
275
|
+
)
|
|
276
|
+
return result
|
|
277
|
+
except Exception as e:
|
|
278
|
+
duration_ms = (time.perf_counter() - start) * 1000
|
|
279
|
+
audit.capture_exception(e, {"operationName": name, "durationMs": round(duration_ms)})
|
|
280
|
+
raise
|
|
281
|
+
|
|
282
|
+
# Retorna o wrapper correto dependendo se a função original é async ou não
|
|
283
|
+
return async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper
|
|
284
|
+
return decorator
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
3
|
+
from starlette.requests import Request
|
|
4
|
+
from .core import audit, request_context
|
|
5
|
+
|
|
6
|
+
class DevInspectorMiddleware(BaseHTTPMiddleware):
|
|
7
|
+
def __init__(self, app, slow_threshold_ms: int = 300):
|
|
8
|
+
super().__init__(app)
|
|
9
|
+
self.slow_threshold_ms = slow_threshold_ms
|
|
10
|
+
|
|
11
|
+
async def dispatch(self, request: Request, call_next):
|
|
12
|
+
# Ignora rotas estáticas (adicione outras se necessário)
|
|
13
|
+
if request.url.path.startswith("/uploads"):
|
|
14
|
+
return await call_next(request)
|
|
15
|
+
|
|
16
|
+
start_time = time.perf_counter()
|
|
17
|
+
status_code = 500
|
|
18
|
+
|
|
19
|
+
# 1. Inicia o contexto da requisição para o APM isolar as contagens
|
|
20
|
+
token = request_context.set({
|
|
21
|
+
"queriesCount": 0,
|
|
22
|
+
"slowQueries": 0
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
response = await call_next(request)
|
|
27
|
+
status_code = response.status_code
|
|
28
|
+
return response
|
|
29
|
+
except Exception as exc:
|
|
30
|
+
if hasattr(exc, "status_code"):
|
|
31
|
+
status_code = exc.status_code
|
|
32
|
+
|
|
33
|
+
audit.capture_error(
|
|
34
|
+
exc,
|
|
35
|
+
metadata={
|
|
36
|
+
"path": request.url.path,
|
|
37
|
+
"method": request.method,
|
|
38
|
+
"statusCode": status_code,
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
raise exc
|
|
42
|
+
finally:
|
|
43
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
44
|
+
user_agent = request.headers.get("user-agent", "")
|
|
45
|
+
|
|
46
|
+
# 2. Resgata os dados do APM que foram injetados pelas operações do banco
|
|
47
|
+
ctx = request_context.get()
|
|
48
|
+
db_queries_count = ctx.get("queriesCount", 0) if ctx else 0
|
|
49
|
+
slow_query_ms = ctx.get("slowQueries", 0) if ctx else 0
|
|
50
|
+
|
|
51
|
+
is_error = status_code >= 400
|
|
52
|
+
is_slow = duration_ms >= self.slow_threshold_ms
|
|
53
|
+
has_slow_query = slow_query_ms > 0
|
|
54
|
+
|
|
55
|
+
# 3. 🎯 FILTRO INTELIGENTE: Só envia se for Erro, Lenta ou tiver Query Lenta
|
|
56
|
+
if is_error or is_slow or has_slow_query:
|
|
57
|
+
# Tenta extrair a rota mapeada (ex: /users/{id}) em vez da URL crua (/users/123)
|
|
58
|
+
route_path = request.scope.get("route")
|
|
59
|
+
route_name = route_path.path if hasattr(route_path, "path") else request.url.path
|
|
60
|
+
|
|
61
|
+
audit.capture_request(
|
|
62
|
+
method=request.method,
|
|
63
|
+
url=str(request.url),
|
|
64
|
+
status_code=status_code,
|
|
65
|
+
duration_ms=duration_ms,
|
|
66
|
+
user_agent=user_agent,
|
|
67
|
+
route=route_name,
|
|
68
|
+
db_queries_count=db_queries_count,
|
|
69
|
+
slow_query_ms=slow_query_ms,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# 4. Limpa o contexto para evitar vazamento de memória em outras requisições
|
|
73
|
+
request_context.reset(token)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from flask import Flask, g, request
|
|
3
|
+
from .core import audit, request_context
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DevInspectorFlask:
|
|
7
|
+
def __init__(self, app: Flask = None, slow_threshold_ms: int = 300):
|
|
8
|
+
self.slow_threshold_ms = slow_threshold_ms
|
|
9
|
+
if app is not None:
|
|
10
|
+
self.init_app(app)
|
|
11
|
+
|
|
12
|
+
def init_app(self, app: Flask):
|
|
13
|
+
app.before_request(self._before_request)
|
|
14
|
+
app.after_request(self._after_request)
|
|
15
|
+
app.teardown_request(self._teardown_request)
|
|
16
|
+
|
|
17
|
+
def _before_request(self):
|
|
18
|
+
# Ignora rotas estáticas (adicione outras se necessário)
|
|
19
|
+
if request.path.startswith("/uploads"):
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
g._devinspector_start_time = time.perf_counter()
|
|
23
|
+
|
|
24
|
+
# 1. Inicia o contexto da requisição para isolar as contagens do APM
|
|
25
|
+
g._devinspector_ctx_token = request_context.set({
|
|
26
|
+
"queriesCount": 0,
|
|
27
|
+
"slowQueries": 0
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
def _after_request(self, response):
|
|
31
|
+
self._record_metric(response.status_code)
|
|
32
|
+
return response
|
|
33
|
+
|
|
34
|
+
def _teardown_request(self, exception=None):
|
|
35
|
+
if exception:
|
|
36
|
+
status_code = 500
|
|
37
|
+
if hasattr(exception, "code"):
|
|
38
|
+
status_code = exception.code
|
|
39
|
+
|
|
40
|
+
audit.capture_error(
|
|
41
|
+
exception,
|
|
42
|
+
metadata={
|
|
43
|
+
"path": request.path,
|
|
44
|
+
"method": request.method,
|
|
45
|
+
"statusCode": status_code,
|
|
46
|
+
},
|
|
47
|
+
)
|
|
48
|
+
if not getattr(g, "_devinspector_metric_recorded", False):
|
|
49
|
+
self._record_metric(status_code)
|
|
50
|
+
|
|
51
|
+
# 4. Limpa o contexto ao final da requisição para evitar vazamento
|
|
52
|
+
if hasattr(g, "_devinspector_ctx_token"):
|
|
53
|
+
request_context.reset(g._devinspector_ctx_token)
|
|
54
|
+
|
|
55
|
+
def _record_metric(self, status_code: int):
|
|
56
|
+
start_time = getattr(g, "_devinspector_start_time", None)
|
|
57
|
+
if start_time is None:
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
61
|
+
user_agent = request.headers.get("User-Agent", "")
|
|
62
|
+
|
|
63
|
+
# 2. Resgata os dados do APM que foram injetados pelas operações do banco
|
|
64
|
+
ctx = request_context.get()
|
|
65
|
+
db_queries_count = ctx.get("queriesCount", 0) if ctx else 0
|
|
66
|
+
slow_query_ms = ctx.get("slowQueries", 0) if ctx else 0
|
|
67
|
+
|
|
68
|
+
is_error = status_code >= 400
|
|
69
|
+
is_slow = duration_ms >= self.slow_threshold_ms
|
|
70
|
+
has_slow_query = slow_query_ms > 0
|
|
71
|
+
|
|
72
|
+
# 3. 🎯 FILTRO INTELIGENTE: Só envia se for Erro, Lenta ou tiver Query Lenta
|
|
73
|
+
if is_error or is_slow or has_slow_query:
|
|
74
|
+
# Extrai a rota com parâmetros (ex: /users/<id>) em vez da URL crua
|
|
75
|
+
route_name = request.url_rule.rule if request.url_rule else request.path
|
|
76
|
+
|
|
77
|
+
audit.capture_request(
|
|
78
|
+
method=request.method,
|
|
79
|
+
url=request.url,
|
|
80
|
+
status_code=status_code,
|
|
81
|
+
duration_ms=duration_ms,
|
|
82
|
+
user_agent=user_agent,
|
|
83
|
+
route=route_name,
|
|
84
|
+
db_queries_count=db_queries_count,
|
|
85
|
+
slow_query_ms=slow_query_ms,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
g._devinspector_metric_recorded = True
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devinspector
|
|
3
|
+
Version: 2.1.0
|
|
4
|
+
Summary: SDK Python de monitoramento e auditoria em tempo real | DevInspector
|
|
5
|
+
Author-email: DevInspector <suporte@devinspector.com.br>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://devinspector.com.br
|
|
8
|
+
Keywords: monitoring,error-tracking,apm,audit,devinspector,fastapi,flask
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Topic :: System :: Monitoring
|
|
21
|
+
Requires-Python: >=3.8
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
Provides-Extra: fastapi
|
|
24
|
+
Requires-Dist: fastapi; extra == "fastapi"
|
|
25
|
+
Requires-Dist: starlette; extra == "fastapi"
|
|
26
|
+
Provides-Extra: flask
|
|
27
|
+
Requires-Dist: flask; extra == "flask"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "devinspector"
|
|
7
|
+
version = "2.1.0"
|
|
8
|
+
description = "SDK Python de monitoramento e auditoria em tempo real | DevInspector"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "DevInspector", email = "suporte@devinspector.com.br"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["monitoring", "error-tracking", "apm", "audit", "devinspector", "fastapi", "flask"]
|
|
16
|
+
dependencies = []
|
|
17
|
+
|
|
18
|
+
# Classificadores ajudam os desenvolvedores a encontrarem seu pacote no PyPI
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 5 - Production/Stable",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.8",
|
|
26
|
+
"Programming Language :: Python :: 3.9",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Programming Language :: Python :: 3.12",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
31
|
+
"Topic :: System :: Monitoring",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
fastapi = ["fastapi", "starlette"]
|
|
36
|
+
flask = ["flask"]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://devinspector.com.br"
|
|
40
|
+
# Documentation = "https://docs.devinspector.com.br" # Descomente se tiver
|
devinspector-2.0.6/PKG-INFO
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: devinspector
|
|
3
|
-
Version: 2.0.6
|
|
4
|
-
Summary: SDK Python de monitoramento e auditoria em tempo real | DevInspector
|
|
5
|
-
Author-email: DevInspector <suporte@devinspector.com.br>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://devinspector.com.br
|
|
8
|
-
Keywords: monitoring,error-tracking,audit,devinspector,fastapi,flask
|
|
9
|
-
Requires-Python: >=3.8
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
Provides-Extra: fastapi
|
|
12
|
-
Requires-Dist: fastapi; extra == "fastapi"
|
|
13
|
-
Requires-Dist: starlette; extra == "fastapi"
|
|
14
|
-
Provides-Extra: flask
|
|
15
|
-
Requires-Dist: flask; extra == "flask"
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
|
3
|
-
from starlette.requests import Request
|
|
4
|
-
from .core import audit
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class DevInspectorMiddleware(BaseHTTPMiddleware):
|
|
8
|
-
async def dispatch(self, request: Request, call_next):
|
|
9
|
-
start_time = time.perf_counter()
|
|
10
|
-
status_code = 500
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
response = await call_next(request)
|
|
14
|
-
status_code = response.status_code
|
|
15
|
-
return response
|
|
16
|
-
except Exception as exc:
|
|
17
|
-
if hasattr(exc, "status_code"):
|
|
18
|
-
status_code = exc.status_code
|
|
19
|
-
|
|
20
|
-
audit.capture_error(
|
|
21
|
-
exc,
|
|
22
|
-
metadata={
|
|
23
|
-
"path": request.url.path,
|
|
24
|
-
"method": request.method,
|
|
25
|
-
"statusCode": status_code,
|
|
26
|
-
},
|
|
27
|
-
)
|
|
28
|
-
raise exc
|
|
29
|
-
finally:
|
|
30
|
-
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
31
|
-
user_agent = request.headers.get("user-agent", "")
|
|
32
|
-
|
|
33
|
-
audit.capture_request(
|
|
34
|
-
method=request.method,
|
|
35
|
-
url=str(request.url),
|
|
36
|
-
status_code=status_code,
|
|
37
|
-
duration_ms=duration_ms,
|
|
38
|
-
user_agent=user_agent,
|
|
39
|
-
)
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
from flask import Flask, g, request
|
|
3
|
-
from .core import audit
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class DevInspectorFlask:
|
|
7
|
-
def __init__(self, app: Flask = None):
|
|
8
|
-
if app is not None:
|
|
9
|
-
self.init_app(app)
|
|
10
|
-
|
|
11
|
-
def init_app(self, app: Flask):
|
|
12
|
-
app.before_request(self._before_request)
|
|
13
|
-
app.after_request(self._after_request)
|
|
14
|
-
app.teardown_request(self._teardown_request)
|
|
15
|
-
|
|
16
|
-
def _before_request(self):
|
|
17
|
-
g._devinspector_start_time = time.perf_counter()
|
|
18
|
-
|
|
19
|
-
def _after_request(self, response):
|
|
20
|
-
self._record_metric(response.status_code)
|
|
21
|
-
return response
|
|
22
|
-
|
|
23
|
-
def _teardown_request(self, exception=None):
|
|
24
|
-
if exception:
|
|
25
|
-
status_code = 500
|
|
26
|
-
if hasattr(exception, "code"):
|
|
27
|
-
status_code = exception.code
|
|
28
|
-
|
|
29
|
-
audit.capture_error(
|
|
30
|
-
exception,
|
|
31
|
-
metadata={
|
|
32
|
-
"path": request.path,
|
|
33
|
-
"method": request.method,
|
|
34
|
-
"statusCode": status_code,
|
|
35
|
-
},
|
|
36
|
-
)
|
|
37
|
-
if not getattr(g, "_devinspector_metric_recorded", False):
|
|
38
|
-
self._record_metric(status_code)
|
|
39
|
-
|
|
40
|
-
def _record_metric(self, status_code: int):
|
|
41
|
-
start_time = getattr(g, "_devinspector_start_time", None)
|
|
42
|
-
if start_time is None:
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
duration_ms = (time.perf_counter() - start_time) * 1000
|
|
46
|
-
user_agent = request.headers.get("User-Agent", "")
|
|
47
|
-
|
|
48
|
-
audit.capture_request(
|
|
49
|
-
method=request.method,
|
|
50
|
-
url=request.url,
|
|
51
|
-
status_code=status_code,
|
|
52
|
-
duration_ms=duration_ms,
|
|
53
|
-
user_agent=user_agent,
|
|
54
|
-
)
|
|
55
|
-
g._devinspector_metric_recorded = True
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: devinspector
|
|
3
|
-
Version: 2.0.6
|
|
4
|
-
Summary: SDK Python de monitoramento e auditoria em tempo real | DevInspector
|
|
5
|
-
Author-email: DevInspector <suporte@devinspector.com.br>
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://devinspector.com.br
|
|
8
|
-
Keywords: monitoring,error-tracking,audit,devinspector,fastapi,flask
|
|
9
|
-
Requires-Python: >=3.8
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
Provides-Extra: fastapi
|
|
12
|
-
Requires-Dist: fastapi; extra == "fastapi"
|
|
13
|
-
Requires-Dist: starlette; extra == "fastapi"
|
|
14
|
-
Provides-Extra: flask
|
|
15
|
-
Requires-Dist: flask; extra == "flask"
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=61.0"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "devinspector"
|
|
7
|
-
version = "2.0.6"
|
|
8
|
-
description = "SDK Python de monitoramento e auditoria em tempo real | DevInspector"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.8"
|
|
11
|
-
license = {text = "MIT"}
|
|
12
|
-
authors = [
|
|
13
|
-
{name = "DevInspector", email = "suporte@devinspector.com.br"}
|
|
14
|
-
]
|
|
15
|
-
keywords = ["monitoring", "error-tracking", "audit", "devinspector", "fastapi", "flask"]
|
|
16
|
-
dependencies = []
|
|
17
|
-
|
|
18
|
-
[project.optional-dependencies]
|
|
19
|
-
fastapi = ["fastapi", "starlette"]
|
|
20
|
-
flask = ["flask"]
|
|
21
|
-
|
|
22
|
-
[project.urls]
|
|
23
|
-
Homepage = "https://devinspector.com.br"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|