devinspector 2.0.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.
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: devinspector
3
+ Version: 2.0.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,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"
File without changes
@@ -0,0 +1,3 @@
1
+ from .core import AuditCore, audit
2
+
3
+ __all__ = ["audit", "AuditCore"]
@@ -0,0 +1,208 @@
1
+ import sys
2
+ import json
3
+ import time
4
+ import queue
5
+ import threading
6
+ import traceback
7
+ import urllib.request
8
+ from datetime import datetime, timezone
9
+ from typing import Any, Dict, Optional
10
+
11
+
12
+ class AuditCore:
13
+ def __init__(self):
14
+ self.api_key: str = "PENDING_API_KEY"
15
+ self.endpoint: str = "https://api.devinspector.com.br/api/ingest/track"
16
+ self.environment: str = "production"
17
+ self.initialized: bool = False
18
+ self._listeners_attached: bool = False
19
+
20
+ # Fila e thread em background para envio assíncrono
21
+ self._queue: queue.Queue = queue.Queue(maxsize=10000)
22
+ self._shutdown_event = threading.Event()
23
+ self._worker_thread = threading.Thread(target=self._worker, daemon=True)
24
+ self._worker_thread.start()
25
+
26
+ def init(
27
+ self,
28
+ api_key: str,
29
+ endpoint: Optional[str] = None,
30
+ environment: Optional[str] = None,
31
+ ) -> None:
32
+ if not api_key:
33
+ print("[AuditSDK] API Key não fornecida no init().")
34
+ return
35
+
36
+ self.api_key = api_key
37
+ if endpoint:
38
+ self.endpoint = endpoint
39
+ if environment:
40
+ self.environment = environment
41
+
42
+ self.initialized = True
43
+ self._listen_global_errors()
44
+
45
+ def capture_request(
46
+ self,
47
+ method: str,
48
+ url: str,
49
+ status_code: int,
50
+ duration_ms: float,
51
+ user_agent: str = "",
52
+ ) -> None:
53
+ payload = {
54
+ "type": "request_metric",
55
+ "method": method,
56
+ "url": url,
57
+ "statusCode": status_code,
58
+ "durationMs": round(duration_ms, 2),
59
+ "browser": user_agent,
60
+ "metadata": {
61
+ "environment": self.environment,
62
+ "timestamp": datetime.now(timezone.utc).isoformat(),
63
+ },
64
+ }
65
+ self._enqueue(payload)
66
+
67
+ def capture_error(
68
+ self, error: Exception, metadata: Optional[Dict[str, Any]] = None
69
+ ) -> None:
70
+ raw_message = str(error) or "Erro Desconhecido"
71
+ raw_stack = "".join(traceback.format_tb(error.__traceback__))
72
+
73
+ meta = metadata.copy() if metadata else {}
74
+ meta.update(
75
+ {
76
+ "environment": self.environment,
77
+ "timestamp": datetime.now(timezone.utc).isoformat(),
78
+ }
79
+ )
80
+
81
+ payload = {
82
+ "type": "error",
83
+ "message": self._truncate(raw_message, 500),
84
+ "stackTrace": self._truncate(raw_stack, 10000),
85
+ "url": "",
86
+ "browser": f"Python/{sys.version.split()[0]}",
87
+ "metadata": self._truncate(meta, 5000),
88
+ }
89
+ self._enqueue(payload)
90
+
91
+ def capture_exception(
92
+ self, error: Exception, metadata: Optional[Dict[str, Any]] = None
93
+ ) -> None:
94
+ self.capture_error(error, metadata)
95
+
96
+ def capture_message(
97
+ self, message: str, metadata: Optional[Dict[str, Any]] = None
98
+ ) -> None:
99
+ meta = metadata.copy() if metadata else {}
100
+ meta.update(
101
+ {
102
+ "level": "info",
103
+ "environment": self.environment,
104
+ "timestamp": datetime.now(timezone.utc).isoformat(),
105
+ }
106
+ )
107
+
108
+ payload = {
109
+ "type": "message",
110
+ "message": self._truncate(message, 500),
111
+ "stackTrace": "",
112
+ "url": "",
113
+ "browser": f"Python/{sys.version.split()[0]}",
114
+ "metadata": self._truncate(meta, 5000),
115
+ }
116
+ self._enqueue(payload)
117
+
118
+ def _listen_global_errors(self) -> None:
119
+ if self._listeners_attached:
120
+ return
121
+ self._listeners_attached = True
122
+
123
+ original_excepthook = sys.excepthook
124
+
125
+ def custom_excepthook(exc_type, exc_value, exc_traceback):
126
+ if issubclass(exc_type, KeyboardInterrupt):
127
+ original_excepthook(exc_type, exc_value, exc_traceback)
128
+ return
129
+
130
+ self.capture_error(
131
+ exc_value,
132
+ metadata={"type": "uncaught_exception", "exc_type": exc_type.__name__},
133
+ )
134
+ original_excepthook(exc_type, exc_value, exc_traceback)
135
+
136
+ sys.excepthook = custom_excepthook
137
+
138
+ # Captura exceções em Threads secundárias (Python 3.8+)
139
+ if hasattr(threading, "excepthook"):
140
+
141
+ def custom_thread_excepthook(args):
142
+ self.capture_error(
143
+ args.exc_value,
144
+ metadata={
145
+ "type": "uncaught_thread_exception",
146
+ "thread": args.thread.name,
147
+ },
148
+ )
149
+
150
+ threading.excepthook = custom_thread_excepthook
151
+
152
+ def _truncate(self, value: Any, max_length: int = 5000) -> Any:
153
+ if isinstance(value, str):
154
+ return (
155
+ value[:max_length] + "... [truncated]"
156
+ if len(value) > max_length
157
+ else value
158
+ )
159
+
160
+ if isinstance(value, dict):
161
+ return {
162
+ str(k): self._truncate(v, max_length) for k, v in value.items()
163
+ }
164
+
165
+ return value
166
+
167
+ def _enqueue(self, payload: dict) -> None:
168
+ try:
169
+ self._queue.put_nowait(payload)
170
+ except queue.Full:
171
+ pass
172
+
173
+ def _worker(self) -> None:
174
+ while not self._shutdown_event.is_set():
175
+ try:
176
+ payload = self._queue.get(timeout=0.5)
177
+ self._send(payload)
178
+ self._queue.task_done()
179
+ except queue.Empty:
180
+ continue
181
+
182
+ def _send(self, payload: dict) -> None:
183
+ active_key = (
184
+ self.api_key
185
+ if self.api_key and self.api_key != "PENDING_API_KEY"
186
+ else "dev-fallback-key"
187
+ )
188
+ data_bytes = json.dumps(payload).encode("utf-8")
189
+
190
+ req = urllib.request.Request(
191
+ self.endpoint,
192
+ data=data_bytes,
193
+ headers={
194
+ "Content-Type": "application/json",
195
+ "x-api-key": active_key,
196
+ },
197
+ method="POST",
198
+ )
199
+
200
+ try:
201
+ with urllib.request.urlopen(req, timeout=5):
202
+ pass
203
+ except Exception as err:
204
+ print(f"[DevInspector] Falha ao enviar requisição para o painel: {err}")
205
+
206
+
207
+ # Singleton Global
208
+ audit = AuditCore()
@@ -0,0 +1,35 @@
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
+ audit.capture_error(
18
+ exc,
19
+ metadata={
20
+ "path": request.url.path,
21
+ "method": request.method,
22
+ },
23
+ )
24
+ raise exc
25
+ finally:
26
+ duration_ms = (time.perf_counter() - start_time) * 1000
27
+ user_agent = request.headers.get("user-agent", "")
28
+
29
+ audit.capture_request(
30
+ method=request.method,
31
+ url=str(request.url),
32
+ status_code=status_code,
33
+ duration_ms=duration_ms,
34
+ user_agent=user_agent,
35
+ )
@@ -0,0 +1,51 @@
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
+ audit.capture_error(
26
+ exception,
27
+ metadata={
28
+ "path": request.path,
29
+ "method": request.method,
30
+ },
31
+ )
32
+ # Garante que métrica de erro (500) seja registrada caso falhe antes do after_request
33
+ if not getattr(g, "_devinspector_metric_recorded", False):
34
+ self._record_metric(500)
35
+
36
+ def _record_metric(self, status_code: int):
37
+ start_time = getattr(g, "_devinspector_start_time", None)
38
+ if start_time is None:
39
+ return
40
+
41
+ duration_ms = (time.perf_counter() - start_time) * 1000
42
+ user_agent = request.headers.get("User-Agent", "")
43
+
44
+ audit.capture_request(
45
+ method=request.method,
46
+ url=request.url,
47
+ status_code=status_code,
48
+ duration_ms=duration_ms,
49
+ user_agent=user_agent,
50
+ )
51
+ g._devinspector_metric_recorded = True
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: devinspector
3
+ Version: 2.0.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,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"
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ devinspector/__init__.py
4
+ devinspector/core.py
5
+ devinspector/fastapi.py
6
+ devinspector/flask.py
7
+ devinspector.egg-info/PKG-INFO
8
+ devinspector.egg-info/SOURCES.txt
9
+ devinspector.egg-info/dependency_links.txt
10
+ devinspector.egg-info/requires.txt
11
+ devinspector.egg-info/top_level.txt
@@ -0,0 +1,7 @@
1
+
2
+ [fastapi]
3
+ fastapi
4
+ starlette
5
+
6
+ [flask]
7
+ flask
@@ -0,0 +1 @@
1
+ devinspector
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "devinspector"
7
+ version = "2.0.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", "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"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+