django-cfg 1.4.120__py3-none-any.whl → 1.5.1__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 django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +8 -4
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +33 -71
- django_cfg/apps/grpc/__init__.py +9 -0
- django_cfg/apps/grpc/admin/__init__.py +11 -0
- django_cfg/apps/grpc/admin/config.py +89 -0
- django_cfg/apps/grpc/admin/grpc_request_log.py +252 -0
- django_cfg/apps/grpc/apps.py +28 -0
- django_cfg/apps/grpc/auth/__init__.py +9 -0
- django_cfg/apps/grpc/auth/jwt_auth.py +295 -0
- django_cfg/apps/grpc/interceptors/__init__.py +19 -0
- django_cfg/apps/grpc/interceptors/errors.py +241 -0
- django_cfg/apps/grpc/interceptors/logging.py +270 -0
- django_cfg/apps/grpc/interceptors/metrics.py +306 -0
- django_cfg/apps/grpc/interceptors/request_logger.py +515 -0
- django_cfg/apps/grpc/management/__init__.py +1 -0
- django_cfg/apps/grpc/management/commands/__init__.py +0 -0
- django_cfg/apps/grpc/management/commands/rungrpc.py +302 -0
- django_cfg/apps/grpc/managers/__init__.py +10 -0
- django_cfg/apps/grpc/managers/grpc_request_log.py +310 -0
- django_cfg/apps/grpc/migrations/0001_initial.py +69 -0
- django_cfg/apps/grpc/migrations/0002_rename_django_cfg__service_4c4a8e_idx_django_cfg__service_584308_idx_and_more.py +38 -0
- django_cfg/apps/grpc/migrations/__init__.py +0 -0
- django_cfg/apps/grpc/models/__init__.py +9 -0
- django_cfg/apps/grpc/models/grpc_request_log.py +219 -0
- django_cfg/apps/grpc/serializers/__init__.py +23 -0
- django_cfg/apps/grpc/serializers/health.py +18 -0
- django_cfg/apps/grpc/serializers/requests.py +18 -0
- django_cfg/apps/grpc/serializers/services.py +50 -0
- django_cfg/apps/grpc/serializers/stats.py +22 -0
- django_cfg/apps/grpc/services/__init__.py +16 -0
- django_cfg/apps/grpc/services/base.py +375 -0
- django_cfg/apps/grpc/services/discovery.py +415 -0
- django_cfg/apps/grpc/urls.py +23 -0
- django_cfg/apps/grpc/utils/__init__.py +13 -0
- django_cfg/apps/grpc/utils/proto_gen.py +423 -0
- django_cfg/apps/grpc/views/__init__.py +9 -0
- django_cfg/apps/grpc/views/monitoring.py +497 -0
- django_cfg/apps/maintenance/admin/api_key_admin.py +7 -8
- django_cfg/apps/maintenance/admin/site_admin.py +5 -4
- django_cfg/apps/payments/admin/balance_admin.py +26 -36
- django_cfg/apps/payments/admin/payment_admin.py +65 -85
- django_cfg/apps/payments/admin/withdrawal_admin.py +65 -100
- django_cfg/apps/tasks/admin/task_log.py +20 -47
- django_cfg/apps/urls.py +7 -1
- django_cfg/config.py +106 -0
- django_cfg/core/base/config_model.py +6 -0
- django_cfg/core/builders/apps_builder.py +3 -0
- django_cfg/core/generation/integration_generators/grpc_generator.py +318 -0
- django_cfg/core/generation/orchestrator.py +10 -0
- django_cfg/models/api/grpc/__init__.py +59 -0
- django_cfg/models/api/grpc/config.py +364 -0
- django_cfg/modules/base.py +15 -0
- django_cfg/modules/django_admin/base/pydantic_admin.py +2 -2
- django_cfg/modules/django_admin/utils/__init__.py +41 -3
- django_cfg/modules/django_admin/utils/badges/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{badges.py → badges/status_badges.py} +3 -3
- django_cfg/modules/django_admin/utils/displays/__init__.py +13 -0
- django_cfg/modules/django_admin/utils/{displays.py → displays/data_displays.py} +2 -2
- django_cfg/modules/django_admin/utils/html/__init__.py +26 -0
- django_cfg/modules/django_admin/utils/html/badges.py +47 -0
- django_cfg/modules/django_admin/utils/html/base.py +167 -0
- django_cfg/modules/django_admin/utils/html/code.py +87 -0
- django_cfg/modules/django_admin/utils/html/composition.py +198 -0
- django_cfg/modules/django_admin/utils/html/formatting.py +231 -0
- django_cfg/modules/django_admin/utils/html/keyvalue.py +219 -0
- django_cfg/modules/django_admin/utils/html/markdown_integration.py +108 -0
- django_cfg/modules/django_admin/utils/html/progress.py +127 -0
- django_cfg/modules/django_admin/utils/html_builder.py +55 -408
- django_cfg/modules/django_admin/utils/markdown/__init__.py +21 -0
- django_cfg/modules/django_unfold/navigation.py +28 -0
- django_cfg/pyproject.toml +3 -5
- django_cfg/registry/modules.py +6 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.1.dist-info}/METADATA +10 -1
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.1.dist-info}/RECORD +79 -30
- django_cfg/modules/django_admin/utils/CODE_BLOCK_DOCS.md +0 -396
- /django_cfg/modules/django_admin/utils/{mermaid_plugin.py → markdown/mermaid_plugin.py} +0 -0
- /django_cfg/modules/django_admin/utils/{markdown_renderer.py → markdown/renderer.py} +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.1.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.1.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.120.dist-info → django_cfg-1.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logging Interceptor for gRPC.
|
|
3
|
+
|
|
4
|
+
Provides comprehensive logging for gRPC requests and responses.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import time
|
|
11
|
+
from typing import Callable
|
|
12
|
+
|
|
13
|
+
import grpc
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class LoggingInterceptor(grpc.ServerInterceptor):
|
|
19
|
+
"""
|
|
20
|
+
gRPC interceptor for request/response logging.
|
|
21
|
+
|
|
22
|
+
Features:
|
|
23
|
+
- Logs all incoming requests
|
|
24
|
+
- Logs response status and timing
|
|
25
|
+
- Logs errors and exceptions
|
|
26
|
+
- Structured logging with metadata
|
|
27
|
+
- Performance tracking
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
```python
|
|
31
|
+
# In Django settings (auto-configured in dev mode)
|
|
32
|
+
GRPC_FRAMEWORK = {
|
|
33
|
+
"SERVER_INTERCEPTORS": [
|
|
34
|
+
"django_cfg.apps.grpc.interceptors.LoggingInterceptor",
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Log Format:
|
|
40
|
+
[gRPC] METHOD | STATUS | TIME | DETAILS
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def intercept_service(
|
|
44
|
+
self,
|
|
45
|
+
continuation: Callable,
|
|
46
|
+
handler_call_details: grpc.HandlerCallDetails,
|
|
47
|
+
) -> grpc.RpcMethodHandler:
|
|
48
|
+
"""
|
|
49
|
+
Intercept gRPC service call for logging.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
continuation: Function to invoke the next interceptor or handler
|
|
53
|
+
handler_call_details: Details about the RPC call
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
RPC method handler with logging
|
|
57
|
+
"""
|
|
58
|
+
method_name = handler_call_details.method
|
|
59
|
+
peer = self._extract_peer(handler_call_details.invocation_metadata)
|
|
60
|
+
|
|
61
|
+
# Log incoming request
|
|
62
|
+
logger.info(f"[gRPC] ➡️ {method_name} | peer={peer}")
|
|
63
|
+
|
|
64
|
+
# Get handler and wrap it
|
|
65
|
+
handler = continuation(handler_call_details)
|
|
66
|
+
|
|
67
|
+
if handler is None:
|
|
68
|
+
logger.warning(f"[gRPC] ⚠️ {method_name} | No handler found")
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
# Wrap handler methods to log responses
|
|
72
|
+
return self._wrap_handler(handler, method_name, peer)
|
|
73
|
+
|
|
74
|
+
def _wrap_handler(
|
|
75
|
+
self,
|
|
76
|
+
handler: grpc.RpcMethodHandler,
|
|
77
|
+
method_name: str,
|
|
78
|
+
peer: str,
|
|
79
|
+
) -> grpc.RpcMethodHandler:
|
|
80
|
+
"""
|
|
81
|
+
Wrap handler to add logging.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
handler: Original RPC method handler
|
|
85
|
+
method_name: gRPC method name
|
|
86
|
+
peer: Client peer information
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Wrapped RPC method handler
|
|
90
|
+
"""
|
|
91
|
+
def wrap_unary_unary(behavior):
|
|
92
|
+
def wrapper(request, context):
|
|
93
|
+
start_time = time.time()
|
|
94
|
+
try:
|
|
95
|
+
response = behavior(request, context)
|
|
96
|
+
duration = (time.time() - start_time) * 1000 # ms
|
|
97
|
+
logger.info(
|
|
98
|
+
f"[gRPC] ✅ {method_name} | "
|
|
99
|
+
f"status=OK | "
|
|
100
|
+
f"time={duration:.2f}ms | "
|
|
101
|
+
f"peer={peer}"
|
|
102
|
+
)
|
|
103
|
+
return response
|
|
104
|
+
except Exception as e:
|
|
105
|
+
duration = (time.time() - start_time) * 1000 # ms
|
|
106
|
+
logger.error(
|
|
107
|
+
f"[gRPC] ❌ {method_name} | "
|
|
108
|
+
f"status=ERROR | "
|
|
109
|
+
f"time={duration:.2f}ms | "
|
|
110
|
+
f"error={type(e).__name__}: {str(e)} | "
|
|
111
|
+
f"peer={peer}",
|
|
112
|
+
exc_info=True
|
|
113
|
+
)
|
|
114
|
+
raise
|
|
115
|
+
return wrapper
|
|
116
|
+
|
|
117
|
+
def wrap_unary_stream(behavior):
|
|
118
|
+
def wrapper(request, context):
|
|
119
|
+
start_time = time.time()
|
|
120
|
+
message_count = 0
|
|
121
|
+
try:
|
|
122
|
+
for response in behavior(request, context):
|
|
123
|
+
message_count += 1
|
|
124
|
+
yield response
|
|
125
|
+
duration = (time.time() - start_time) * 1000 # ms
|
|
126
|
+
logger.info(
|
|
127
|
+
f"[gRPC] ✅ {method_name} (stream) | "
|
|
128
|
+
f"status=OK | "
|
|
129
|
+
f"messages={message_count} | "
|
|
130
|
+
f"time={duration:.2f}ms | "
|
|
131
|
+
f"peer={peer}"
|
|
132
|
+
)
|
|
133
|
+
except Exception as e:
|
|
134
|
+
duration = (time.time() - start_time) * 1000 # ms
|
|
135
|
+
logger.error(
|
|
136
|
+
f"[gRPC] ❌ {method_name} (stream) | "
|
|
137
|
+
f"status=ERROR | "
|
|
138
|
+
f"messages={message_count} | "
|
|
139
|
+
f"time={duration:.2f}ms | "
|
|
140
|
+
f"error={type(e).__name__}: {str(e)} | "
|
|
141
|
+
f"peer={peer}",
|
|
142
|
+
exc_info=True
|
|
143
|
+
)
|
|
144
|
+
raise
|
|
145
|
+
return wrapper
|
|
146
|
+
|
|
147
|
+
def wrap_stream_unary(behavior):
|
|
148
|
+
def wrapper(request_iterator, context):
|
|
149
|
+
start_time = time.time()
|
|
150
|
+
message_count = 0
|
|
151
|
+
try:
|
|
152
|
+
# Count messages
|
|
153
|
+
requests = []
|
|
154
|
+
for req in request_iterator:
|
|
155
|
+
message_count += 1
|
|
156
|
+
requests.append(req)
|
|
157
|
+
|
|
158
|
+
# Process
|
|
159
|
+
response = behavior(iter(requests), context)
|
|
160
|
+
duration = (time.time() - start_time) * 1000 # ms
|
|
161
|
+
logger.info(
|
|
162
|
+
f"[gRPC] ✅ {method_name} (client stream) | "
|
|
163
|
+
f"status=OK | "
|
|
164
|
+
f"messages={message_count} | "
|
|
165
|
+
f"time={duration:.2f}ms | "
|
|
166
|
+
f"peer={peer}"
|
|
167
|
+
)
|
|
168
|
+
return response
|
|
169
|
+
except Exception as e:
|
|
170
|
+
duration = (time.time() - start_time) * 1000 # ms
|
|
171
|
+
logger.error(
|
|
172
|
+
f"[gRPC] ❌ {method_name} (client stream) | "
|
|
173
|
+
f"status=ERROR | "
|
|
174
|
+
f"messages={message_count} | "
|
|
175
|
+
f"time={duration:.2f}ms | "
|
|
176
|
+
f"error={type(e).__name__}: {str(e)} | "
|
|
177
|
+
f"peer={peer}",
|
|
178
|
+
exc_info=True
|
|
179
|
+
)
|
|
180
|
+
raise
|
|
181
|
+
return wrapper
|
|
182
|
+
|
|
183
|
+
def wrap_stream_stream(behavior):
|
|
184
|
+
def wrapper(request_iterator, context):
|
|
185
|
+
start_time = time.time()
|
|
186
|
+
in_count = 0
|
|
187
|
+
out_count = 0
|
|
188
|
+
try:
|
|
189
|
+
# Count input messages
|
|
190
|
+
requests = []
|
|
191
|
+
for req in request_iterator:
|
|
192
|
+
in_count += 1
|
|
193
|
+
requests.append(req)
|
|
194
|
+
|
|
195
|
+
# Process and count output
|
|
196
|
+
for response in behavior(iter(requests), context):
|
|
197
|
+
out_count += 1
|
|
198
|
+
yield response
|
|
199
|
+
|
|
200
|
+
duration = (time.time() - start_time) * 1000 # ms
|
|
201
|
+
logger.info(
|
|
202
|
+
f"[gRPC] ✅ {method_name} (bidi stream) | "
|
|
203
|
+
f"status=OK | "
|
|
204
|
+
f"in={in_count} out={out_count} | "
|
|
205
|
+
f"time={duration:.2f}ms | "
|
|
206
|
+
f"peer={peer}"
|
|
207
|
+
)
|
|
208
|
+
except Exception as e:
|
|
209
|
+
duration = (time.time() - start_time) * 1000 # ms
|
|
210
|
+
logger.error(
|
|
211
|
+
f"[gRPC] ❌ {method_name} (bidi stream) | "
|
|
212
|
+
f"status=ERROR | "
|
|
213
|
+
f"in={in_count} out={out_count} | "
|
|
214
|
+
f"time={duration:.2f}ms | "
|
|
215
|
+
f"error={type(e).__name__}: {str(e)} | "
|
|
216
|
+
f"peer={peer}",
|
|
217
|
+
exc_info=True
|
|
218
|
+
)
|
|
219
|
+
raise
|
|
220
|
+
return wrapper
|
|
221
|
+
|
|
222
|
+
# Return wrapped handler based on type
|
|
223
|
+
if handler.unary_unary:
|
|
224
|
+
return grpc.unary_unary_rpc_method_handler(
|
|
225
|
+
wrap_unary_unary(handler.unary_unary),
|
|
226
|
+
request_deserializer=handler.request_deserializer,
|
|
227
|
+
response_serializer=handler.response_serializer,
|
|
228
|
+
)
|
|
229
|
+
elif handler.unary_stream:
|
|
230
|
+
return grpc.unary_stream_rpc_method_handler(
|
|
231
|
+
wrap_unary_stream(handler.unary_stream),
|
|
232
|
+
request_deserializer=handler.request_deserializer,
|
|
233
|
+
response_serializer=handler.response_serializer,
|
|
234
|
+
)
|
|
235
|
+
elif handler.stream_unary:
|
|
236
|
+
return grpc.stream_unary_rpc_method_handler(
|
|
237
|
+
wrap_stream_unary(handler.stream_unary),
|
|
238
|
+
request_deserializer=handler.request_deserializer,
|
|
239
|
+
response_serializer=handler.response_serializer,
|
|
240
|
+
)
|
|
241
|
+
elif handler.stream_stream:
|
|
242
|
+
return grpc.stream_stream_rpc_method_handler(
|
|
243
|
+
wrap_stream_stream(handler.stream_stream),
|
|
244
|
+
request_deserializer=handler.request_deserializer,
|
|
245
|
+
response_serializer=handler.response_serializer,
|
|
246
|
+
)
|
|
247
|
+
else:
|
|
248
|
+
return handler
|
|
249
|
+
|
|
250
|
+
def _extract_peer(self, metadata: tuple) -> str:
|
|
251
|
+
"""
|
|
252
|
+
Extract peer information from metadata.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
metadata: gRPC invocation metadata
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Peer identifier string
|
|
259
|
+
"""
|
|
260
|
+
if not metadata:
|
|
261
|
+
return "unknown"
|
|
262
|
+
|
|
263
|
+
# Convert to dict for easier access
|
|
264
|
+
metadata_dict = dict(metadata)
|
|
265
|
+
|
|
266
|
+
# Try to get user-agent or return unknown
|
|
267
|
+
return metadata_dict.get("user-agent", "unknown")
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
__all__ = ["LoggingInterceptor"]
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Metrics Interceptor for gRPC.
|
|
3
|
+
|
|
4
|
+
Tracks request counts, response times, and error rates.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import time
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
from typing import Callable
|
|
13
|
+
|
|
14
|
+
import grpc
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class MetricsCollector:
|
|
20
|
+
"""
|
|
21
|
+
Thread-safe metrics collector for gRPC.
|
|
22
|
+
|
|
23
|
+
Tracks:
|
|
24
|
+
- Request counts per method
|
|
25
|
+
- Response times per method
|
|
26
|
+
- Error counts per method
|
|
27
|
+
- Total requests/errors
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
"""Initialize metrics collector."""
|
|
32
|
+
self.request_counts = defaultdict(int)
|
|
33
|
+
self.error_counts = defaultdict(int)
|
|
34
|
+
self.response_times = defaultdict(list)
|
|
35
|
+
self.total_requests = 0
|
|
36
|
+
self.total_errors = 0
|
|
37
|
+
|
|
38
|
+
def record_request(self, method: str):
|
|
39
|
+
"""Record a request."""
|
|
40
|
+
self.request_counts[method] += 1
|
|
41
|
+
self.total_requests += 1
|
|
42
|
+
|
|
43
|
+
def record_error(self, method: str):
|
|
44
|
+
"""Record an error."""
|
|
45
|
+
self.error_counts[method] += 1
|
|
46
|
+
self.total_errors += 1
|
|
47
|
+
|
|
48
|
+
def record_response_time(self, method: str, duration_ms: float):
|
|
49
|
+
"""Record response time."""
|
|
50
|
+
self.response_times[method].append(duration_ms)
|
|
51
|
+
|
|
52
|
+
def get_stats(self, method: str = None) -> dict:
|
|
53
|
+
"""
|
|
54
|
+
Get statistics for a method or all methods.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
method: Specific method name, or None for all
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dictionary with statistics
|
|
61
|
+
"""
|
|
62
|
+
if method:
|
|
63
|
+
times = self.response_times.get(method, [])
|
|
64
|
+
return {
|
|
65
|
+
"requests": self.request_counts.get(method, 0),
|
|
66
|
+
"errors": self.error_counts.get(method, 0),
|
|
67
|
+
"avg_time_ms": sum(times) / len(times) if times else 0,
|
|
68
|
+
"min_time_ms": min(times) if times else 0,
|
|
69
|
+
"max_time_ms": max(times) if times else 0,
|
|
70
|
+
}
|
|
71
|
+
else:
|
|
72
|
+
return {
|
|
73
|
+
"total_requests": self.total_requests,
|
|
74
|
+
"total_errors": self.total_errors,
|
|
75
|
+
"error_rate": (
|
|
76
|
+
self.total_errors / self.total_requests
|
|
77
|
+
if self.total_requests > 0
|
|
78
|
+
else 0
|
|
79
|
+
),
|
|
80
|
+
"methods": {
|
|
81
|
+
method: self.get_stats(method)
|
|
82
|
+
for method in self.request_counts.keys()
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def reset(self):
|
|
87
|
+
"""Reset all metrics."""
|
|
88
|
+
self.request_counts.clear()
|
|
89
|
+
self.error_counts.clear()
|
|
90
|
+
self.response_times.clear()
|
|
91
|
+
self.total_requests = 0
|
|
92
|
+
self.total_errors = 0
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# Global metrics collector instance
|
|
96
|
+
_metrics = MetricsCollector()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def get_metrics(method: str = None) -> dict:
|
|
100
|
+
"""
|
|
101
|
+
Get metrics for a method or all methods.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
method: Specific method name, or None for all
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Dictionary with metrics
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
```python
|
|
111
|
+
from django_cfg.apps.grpc.interceptors.metrics import get_metrics
|
|
112
|
+
|
|
113
|
+
# Get all metrics
|
|
114
|
+
all_stats = get_metrics()
|
|
115
|
+
|
|
116
|
+
# Get metrics for specific method
|
|
117
|
+
stats = get_metrics("/myapp.MyService/MyMethod")
|
|
118
|
+
print(f"Requests: {stats['requests']}")
|
|
119
|
+
print(f"Avg time: {stats['avg_time_ms']:.2f}ms")
|
|
120
|
+
```
|
|
121
|
+
"""
|
|
122
|
+
return _metrics.get_stats(method)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def reset_metrics():
|
|
126
|
+
"""
|
|
127
|
+
Reset all metrics.
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
```python
|
|
131
|
+
from django_cfg.apps.grpc.interceptors.metrics import reset_metrics
|
|
132
|
+
reset_metrics()
|
|
133
|
+
```
|
|
134
|
+
"""
|
|
135
|
+
_metrics.reset()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class MetricsInterceptor(grpc.ServerInterceptor):
|
|
139
|
+
"""
|
|
140
|
+
gRPC interceptor for metrics collection.
|
|
141
|
+
|
|
142
|
+
Features:
|
|
143
|
+
- Tracks request counts
|
|
144
|
+
- Tracks response times
|
|
145
|
+
- Tracks error rates
|
|
146
|
+
- Per-method statistics
|
|
147
|
+
- Global statistics
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
```python
|
|
151
|
+
# In Django settings (auto-configured in dev mode)
|
|
152
|
+
GRPC_FRAMEWORK = {
|
|
153
|
+
"SERVER_INTERCEPTORS": [
|
|
154
|
+
"django_cfg.apps.grpc.interceptors.MetricsInterceptor",
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Access Metrics:
|
|
160
|
+
```python
|
|
161
|
+
from django_cfg.apps.grpc.interceptors.metrics import get_metrics
|
|
162
|
+
|
|
163
|
+
stats = get_metrics()
|
|
164
|
+
print(f"Total requests: {stats['total_requests']}")
|
|
165
|
+
print(f"Error rate: {stats['error_rate']:.2%}")
|
|
166
|
+
```
|
|
167
|
+
"""
|
|
168
|
+
|
|
169
|
+
def __init__(self):
|
|
170
|
+
"""Initialize metrics interceptor."""
|
|
171
|
+
self.collector = _metrics
|
|
172
|
+
|
|
173
|
+
def intercept_service(
|
|
174
|
+
self,
|
|
175
|
+
continuation: Callable,
|
|
176
|
+
handler_call_details: grpc.HandlerCallDetails,
|
|
177
|
+
) -> grpc.RpcMethodHandler:
|
|
178
|
+
"""
|
|
179
|
+
Intercept gRPC service call for metrics collection.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
continuation: Function to invoke the next interceptor or handler
|
|
183
|
+
handler_call_details: Details about the RPC call
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
RPC method handler with metrics
|
|
187
|
+
"""
|
|
188
|
+
method_name = handler_call_details.method
|
|
189
|
+
|
|
190
|
+
# Record request
|
|
191
|
+
self.collector.record_request(method_name)
|
|
192
|
+
|
|
193
|
+
# Get handler and wrap it
|
|
194
|
+
handler = continuation(handler_call_details)
|
|
195
|
+
|
|
196
|
+
if handler is None:
|
|
197
|
+
return None
|
|
198
|
+
|
|
199
|
+
# Wrap handler methods to track metrics
|
|
200
|
+
return self._wrap_handler(handler, method_name)
|
|
201
|
+
|
|
202
|
+
def _wrap_handler(
|
|
203
|
+
self,
|
|
204
|
+
handler: grpc.RpcMethodHandler,
|
|
205
|
+
method_name: str,
|
|
206
|
+
) -> grpc.RpcMethodHandler:
|
|
207
|
+
"""
|
|
208
|
+
Wrap handler to track metrics.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
handler: Original RPC method handler
|
|
212
|
+
method_name: gRPC method name
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
Wrapped RPC method handler
|
|
216
|
+
"""
|
|
217
|
+
def wrap_unary_unary(behavior):
|
|
218
|
+
def wrapper(request, context):
|
|
219
|
+
start_time = time.time()
|
|
220
|
+
try:
|
|
221
|
+
response = behavior(request, context)
|
|
222
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
223
|
+
self.collector.record_response_time(method_name, duration_ms)
|
|
224
|
+
return response
|
|
225
|
+
except Exception as e:
|
|
226
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
227
|
+
self.collector.record_response_time(method_name, duration_ms)
|
|
228
|
+
self.collector.record_error(method_name)
|
|
229
|
+
raise
|
|
230
|
+
return wrapper
|
|
231
|
+
|
|
232
|
+
def wrap_unary_stream(behavior):
|
|
233
|
+
def wrapper(request, context):
|
|
234
|
+
start_time = time.time()
|
|
235
|
+
try:
|
|
236
|
+
for response in behavior(request, context):
|
|
237
|
+
yield response
|
|
238
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
239
|
+
self.collector.record_response_time(method_name, duration_ms)
|
|
240
|
+
except Exception as e:
|
|
241
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
242
|
+
self.collector.record_response_time(method_name, duration_ms)
|
|
243
|
+
self.collector.record_error(method_name)
|
|
244
|
+
raise
|
|
245
|
+
return wrapper
|
|
246
|
+
|
|
247
|
+
def wrap_stream_unary(behavior):
|
|
248
|
+
def wrapper(request_iterator, context):
|
|
249
|
+
start_time = time.time()
|
|
250
|
+
try:
|
|
251
|
+
response = behavior(request_iterator, context)
|
|
252
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
253
|
+
self.collector.record_response_time(method_name, duration_ms)
|
|
254
|
+
return response
|
|
255
|
+
except Exception as e:
|
|
256
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
257
|
+
self.collector.record_response_time(method_name, duration_ms)
|
|
258
|
+
self.collector.record_error(method_name)
|
|
259
|
+
raise
|
|
260
|
+
return wrapper
|
|
261
|
+
|
|
262
|
+
def wrap_stream_stream(behavior):
|
|
263
|
+
def wrapper(request_iterator, context):
|
|
264
|
+
start_time = time.time()
|
|
265
|
+
try:
|
|
266
|
+
for response in behavior(request_iterator, context):
|
|
267
|
+
yield response
|
|
268
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
269
|
+
self.collector.record_response_time(method_name, duration_ms)
|
|
270
|
+
except Exception as e:
|
|
271
|
+
duration_ms = (time.time() - start_time) * 1000
|
|
272
|
+
self.collector.record_response_time(method_name, duration_ms)
|
|
273
|
+
self.collector.record_error(method_name)
|
|
274
|
+
raise
|
|
275
|
+
return wrapper
|
|
276
|
+
|
|
277
|
+
# Return wrapped handler based on type
|
|
278
|
+
if handler.unary_unary:
|
|
279
|
+
return grpc.unary_unary_rpc_method_handler(
|
|
280
|
+
wrap_unary_unary(handler.unary_unary),
|
|
281
|
+
request_deserializer=handler.request_deserializer,
|
|
282
|
+
response_serializer=handler.response_serializer,
|
|
283
|
+
)
|
|
284
|
+
elif handler.unary_stream:
|
|
285
|
+
return grpc.unary_stream_rpc_method_handler(
|
|
286
|
+
wrap_unary_stream(handler.unary_stream),
|
|
287
|
+
request_deserializer=handler.request_deserializer,
|
|
288
|
+
response_serializer=handler.response_serializer,
|
|
289
|
+
)
|
|
290
|
+
elif handler.stream_unary:
|
|
291
|
+
return grpc.stream_unary_rpc_method_handler(
|
|
292
|
+
wrap_stream_unary(handler.stream_unary),
|
|
293
|
+
request_deserializer=handler.request_deserializer,
|
|
294
|
+
response_serializer=handler.response_serializer,
|
|
295
|
+
)
|
|
296
|
+
elif handler.stream_stream:
|
|
297
|
+
return grpc.stream_stream_rpc_method_handler(
|
|
298
|
+
wrap_stream_stream(handler.stream_stream),
|
|
299
|
+
request_deserializer=handler.request_deserializer,
|
|
300
|
+
response_serializer=handler.response_serializer,
|
|
301
|
+
)
|
|
302
|
+
else:
|
|
303
|
+
return handler
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
__all__ = ["MetricsInterceptor", "MetricsCollector", "get_metrics", "reset_metrics"]
|