django-cfg 1.5.14__py3-none-any.whl → 1.5.29__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 +1 -1
- django_cfg/apps/business/accounts/serializers/profile.py +42 -0
- django_cfg/apps/business/support/serializers.py +3 -2
- django_cfg/apps/integrations/centrifugo/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/apps.py +2 -1
- django_cfg/apps/integrations/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +151 -12
- django_cfg/apps/integrations/centrifugo/management/commands/generate_centrifugo_clients.py +2 -2
- django_cfg/apps/integrations/centrifugo/services/__init__.py +6 -0
- django_cfg/apps/integrations/centrifugo/services/client/__init__.py +6 -1
- django_cfg/apps/integrations/centrifugo/services/client/client.py +1 -1
- django_cfg/apps/integrations/centrifugo/services/client/direct_client.py +282 -0
- django_cfg/apps/integrations/centrifugo/services/logging.py +47 -0
- django_cfg/apps/integrations/centrifugo/services/publisher.py +371 -0
- django_cfg/apps/integrations/centrifugo/services/token_generator.py +122 -0
- django_cfg/apps/integrations/centrifugo/urls.py +8 -0
- django_cfg/apps/integrations/centrifugo/views/__init__.py +2 -0
- django_cfg/apps/integrations/centrifugo/views/admin_api.py +29 -32
- django_cfg/apps/integrations/centrifugo/views/testing_api.py +31 -116
- django_cfg/apps/integrations/centrifugo/views/token_api.py +101 -0
- django_cfg/apps/integrations/centrifugo/views/wrapper.py +259 -0
- django_cfg/apps/integrations/grpc/auth/api_key_auth.py +11 -10
- django_cfg/apps/integrations/grpc/management/commands/compile_proto.py +105 -0
- django_cfg/apps/integrations/grpc/management/commands/generate_protos.py +56 -1
- django_cfg/apps/integrations/grpc/management/commands/rungrpc.py +315 -26
- django_cfg/apps/integrations/grpc/management/proto/__init__.py +3 -0
- django_cfg/apps/integrations/grpc/management/proto/compiler.py +194 -0
- django_cfg/apps/integrations/grpc/managers/grpc_request_log.py +84 -0
- django_cfg/apps/integrations/grpc/managers/grpc_server_status.py +126 -3
- django_cfg/apps/integrations/grpc/models/grpc_api_key.py +7 -1
- django_cfg/apps/integrations/grpc/models/grpc_server_status.py +22 -3
- django_cfg/apps/integrations/grpc/services/__init__.py +102 -17
- django_cfg/apps/integrations/grpc/services/centrifugo/__init__.py +29 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/bridge.py +469 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/config.py +167 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/demo.py +626 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/test_publish.py +229 -0
- django_cfg/apps/integrations/grpc/services/centrifugo/transformers.py +89 -0
- django_cfg/apps/integrations/grpc/services/client/__init__.py +26 -0
- django_cfg/apps/integrations/grpc/services/commands/IMPLEMENTATION.md +456 -0
- django_cfg/apps/integrations/grpc/services/commands/README.md +252 -0
- django_cfg/apps/integrations/grpc/services/commands/__init__.py +93 -0
- django_cfg/apps/integrations/grpc/services/commands/base.py +243 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/__init__.py +22 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/base_client.py +228 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/client.py +272 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/config.py +177 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/start.py +125 -0
- django_cfg/apps/integrations/grpc/services/commands/examples/stop.py +101 -0
- django_cfg/apps/integrations/grpc/services/commands/registry.py +170 -0
- django_cfg/apps/integrations/grpc/services/discovery/__init__.py +39 -0
- django_cfg/apps/integrations/grpc/services/{discovery.py → discovery/discovery.py} +67 -54
- django_cfg/apps/integrations/grpc/services/{service_registry.py → discovery/registry.py} +215 -5
- django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/__init__.py +3 -1
- django_cfg/apps/integrations/grpc/services/interceptors/centrifugo.py +541 -0
- django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/metrics.py +3 -3
- django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/request_logger.py +10 -13
- django_cfg/apps/integrations/grpc/services/management/__init__.py +37 -0
- django_cfg/apps/integrations/grpc/services/monitoring/__init__.py +38 -0
- django_cfg/apps/integrations/grpc/services/{monitoring_service.py → monitoring/monitoring.py} +2 -2
- django_cfg/apps/integrations/grpc/services/{testing_service.py → monitoring/testing.py} +5 -5
- django_cfg/apps/integrations/grpc/services/rendering/__init__.py +27 -0
- django_cfg/apps/integrations/grpc/services/{chart_generator.py → rendering/charts.py} +1 -1
- django_cfg/apps/integrations/grpc/services/routing/__init__.py +59 -0
- django_cfg/apps/integrations/grpc/services/routing/config.py +76 -0
- django_cfg/apps/integrations/grpc/services/routing/router.py +430 -0
- django_cfg/apps/integrations/grpc/services/streaming/__init__.py +117 -0
- django_cfg/apps/integrations/grpc/services/streaming/config.py +451 -0
- django_cfg/apps/integrations/grpc/services/streaming/service.py +651 -0
- django_cfg/apps/integrations/grpc/services/streaming/types.py +367 -0
- django_cfg/apps/integrations/grpc/utils/SERVER_LOGGING.md +164 -0
- django_cfg/apps/integrations/grpc/utils/__init__.py +58 -1
- django_cfg/apps/integrations/grpc/utils/converters.py +565 -0
- django_cfg/apps/integrations/grpc/utils/handlers.py +242 -0
- django_cfg/apps/integrations/grpc/utils/proto_gen.py +1 -1
- django_cfg/apps/integrations/grpc/utils/streaming_logger.py +261 -13
- django_cfg/apps/integrations/grpc/views/charts.py +1 -1
- django_cfg/apps/integrations/grpc/views/config.py +1 -1
- django_cfg/apps/system/dashboard/serializers/config.py +95 -9
- django_cfg/apps/system/dashboard/serializers/statistics.py +9 -4
- django_cfg/apps/system/frontend/views.py +87 -6
- django_cfg/core/base/config_model.py +11 -0
- django_cfg/core/builders/middleware_builder.py +5 -0
- django_cfg/core/builders/security_builder.py +1 -0
- django_cfg/core/generation/integration_generators/api.py +2 -0
- django_cfg/management/commands/pool_status.py +153 -0
- django_cfg/middleware/pool_cleanup.py +261 -0
- django_cfg/models/api/grpc/config.py +2 -2
- django_cfg/models/infrastructure/database/config.py +16 -0
- django_cfg/models/infrastructure/database/converters.py +2 -0
- django_cfg/modules/django_admin/utils/html/composition.py +57 -13
- django_cfg/modules/django_admin/utils/html_builder.py +1 -0
- django_cfg/modules/django_client/core/generator/typescript/generator.py +26 -0
- django_cfg/modules/django_client/core/generator/typescript/hooks_generator.py +7 -1
- django_cfg/modules/django_client/core/generator/typescript/models_generator.py +5 -0
- django_cfg/modules/django_client/core/generator/typescript/schemas_generator.py +11 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/fetchers.ts.jinja +1 -0
- django_cfg/modules/django_client/core/generator/typescript/templates/fetchers/function.ts.jinja +29 -1
- django_cfg/modules/django_client/core/generator/typescript/templates/hooks/hooks.ts.jinja +4 -0
- django_cfg/modules/django_client/core/groups/manager.py +25 -18
- django_cfg/modules/django_client/core/ir/schema.py +15 -1
- django_cfg/modules/django_client/core/parser/base.py +12 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +9 -5
- django_cfg/modules/django_logging/django_logger.py +58 -19
- django_cfg/pyproject.toml +3 -3
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/index.html +0 -39
- django_cfg/utils/pool_monitor.py +320 -0
- django_cfg/utils/smart_defaults.py +233 -7
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/METADATA +75 -5
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/RECORD +118 -74
- /django_cfg/apps/integrations/grpc/services/{grpc_client.py → client/client.py} +0 -0
- /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/errors.py +0 -0
- /django_cfg/apps/integrations/grpc/{interceptors → services/interceptors}/logging.py +0 -0
- /django_cfg/apps/integrations/grpc/services/{config_helper.py → management/config_helper.py} +0 -0
- /django_cfg/apps/integrations/grpc/services/{proto_files_manager.py → management/proto_manager.py} +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/WHEEL +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.5.14.dist-info → django_cfg-1.5.29.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
gRPC handler factory utilities.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for creating and registering gRPC servicers
|
|
5
|
+
with proper error handling and logging.
|
|
6
|
+
|
|
7
|
+
**Purpose**:
|
|
8
|
+
Simplify the boilerplate code needed to register gRPC services with servers.
|
|
9
|
+
|
|
10
|
+
**Usage Example**:
|
|
11
|
+
```python
|
|
12
|
+
from django_cfg.apps.integrations.grpc.utils.handlers import create_grpc_handler
|
|
13
|
+
|
|
14
|
+
# Your servicer class
|
|
15
|
+
class BotStreamingService(pb2_grpc.BotStreamingServiceServicer):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
# Create handler tuple
|
|
19
|
+
handlers = create_grpc_handler(
|
|
20
|
+
servicer_class=BotStreamingService,
|
|
21
|
+
add_servicer_func=pb2_grpc.add_BotStreamingServiceServicer_to_server,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Use with django-cfg
|
|
25
|
+
GRPC_HANDLERS = [handlers]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Created: 2025-11-07
|
|
29
|
+
Status: %%PRODUCTION%%
|
|
30
|
+
Phase: Phase 1 - Universal Components
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from typing import Callable, Tuple, Any, Optional, Type
|
|
34
|
+
import logging
|
|
35
|
+
|
|
36
|
+
logger = logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ============================================================================
|
|
40
|
+
# Handler Factory
|
|
41
|
+
# ============================================================================
|
|
42
|
+
|
|
43
|
+
def create_grpc_handler(
|
|
44
|
+
servicer_class: Type,
|
|
45
|
+
add_servicer_func: Callable[[Any, Any], None],
|
|
46
|
+
servicer_kwargs: Optional[dict] = None,
|
|
47
|
+
) -> Tuple[Callable[[Any, Any], None], Type, dict]:
|
|
48
|
+
"""
|
|
49
|
+
Create gRPC handler tuple for django-cfg GRPC_HANDLERS.
|
|
50
|
+
|
|
51
|
+
This factory creates the standardized tuple format expected by django-cfg's
|
|
52
|
+
gRPC server initialization:
|
|
53
|
+
```python
|
|
54
|
+
(add_servicer_function, servicer_class, init_kwargs)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
servicer_class: gRPC servicer class (e.g., MyStreamingService)
|
|
59
|
+
add_servicer_func: Generated add_*_to_server function from pb2_grpc
|
|
60
|
+
servicer_kwargs: Optional kwargs to pass to servicer constructor
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Tuple of (add_func, servicer_class, kwargs) for GRPC_HANDLERS
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
```python
|
|
67
|
+
from .generated import bot_streaming_service_pb2_grpc
|
|
68
|
+
from .services import BotStreamingService
|
|
69
|
+
|
|
70
|
+
handlers = create_grpc_handler(
|
|
71
|
+
servicer_class=BotStreamingService,
|
|
72
|
+
add_servicer_func=bot_streaming_service_pb2_grpc.add_BotStreamingServiceServicer_to_server,
|
|
73
|
+
servicer_kwargs={'config': my_config},
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# In settings.py
|
|
77
|
+
GRPC_HANDLERS = [handlers]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**django-cfg Integration**:
|
|
81
|
+
The tuple is used by django-cfg like this:
|
|
82
|
+
```python
|
|
83
|
+
for add_func, servicer_class, kwargs in GRPC_HANDLERS:
|
|
84
|
+
servicer = servicer_class(**kwargs)
|
|
85
|
+
add_func(servicer, server)
|
|
86
|
+
```
|
|
87
|
+
"""
|
|
88
|
+
kwargs = servicer_kwargs or {}
|
|
89
|
+
|
|
90
|
+
logger.debug(
|
|
91
|
+
f"Created gRPC handler: {servicer_class.__name__} "
|
|
92
|
+
f"with {len(kwargs)} init kwargs"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return (add_servicer_func, servicer_class, kwargs)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def create_multiple_grpc_handlers(
|
|
99
|
+
handlers_config: list[dict],
|
|
100
|
+
) -> list[Tuple[Callable[[Any, Any], None], Type, dict]]:
|
|
101
|
+
"""
|
|
102
|
+
Create multiple gRPC handlers from configuration list.
|
|
103
|
+
|
|
104
|
+
Convenience function for creating multiple handlers at once.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
handlers_config: List of handler configurations, each with keys:
|
|
108
|
+
- servicer_class: Servicer class
|
|
109
|
+
- add_servicer_func: Add function
|
|
110
|
+
- servicer_kwargs: Optional init kwargs
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
List of handler tuples for GRPC_HANDLERS
|
|
114
|
+
|
|
115
|
+
Example:
|
|
116
|
+
```python
|
|
117
|
+
handlers = create_multiple_grpc_handlers([
|
|
118
|
+
{
|
|
119
|
+
'servicer_class': BotStreamingService,
|
|
120
|
+
'add_servicer_func': pb2_grpc.add_BotStreamingServiceServicer_to_server,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
'servicer_class': SignalStreamingService,
|
|
124
|
+
'add_servicer_func': signal_pb2_grpc.add_SignalStreamingServiceServicer_to_server,
|
|
125
|
+
'servicer_kwargs': {'timeout': 30},
|
|
126
|
+
},
|
|
127
|
+
])
|
|
128
|
+
|
|
129
|
+
# In settings.py
|
|
130
|
+
GRPC_HANDLERS = handlers
|
|
131
|
+
```
|
|
132
|
+
"""
|
|
133
|
+
handlers = []
|
|
134
|
+
|
|
135
|
+
for config in handlers_config:
|
|
136
|
+
handler = create_grpc_handler(
|
|
137
|
+
servicer_class=config['servicer_class'],
|
|
138
|
+
add_servicer_func=config['add_servicer_func'],
|
|
139
|
+
servicer_kwargs=config.get('servicer_kwargs'),
|
|
140
|
+
)
|
|
141
|
+
handlers.append(handler)
|
|
142
|
+
|
|
143
|
+
logger.info(f"Created {len(handlers)} gRPC handlers")
|
|
144
|
+
|
|
145
|
+
return handlers
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
# ============================================================================
|
|
149
|
+
# Validation Utilities
|
|
150
|
+
# ============================================================================
|
|
151
|
+
|
|
152
|
+
def validate_grpc_handler(
|
|
153
|
+
handler: Tuple[Callable, Type, dict],
|
|
154
|
+
) -> bool:
|
|
155
|
+
"""
|
|
156
|
+
Validate that handler tuple has correct structure.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
handler: Handler tuple to validate
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
True if valid, False otherwise
|
|
163
|
+
|
|
164
|
+
Example:
|
|
165
|
+
```python
|
|
166
|
+
handler = create_grpc_handler(MyService, add_func)
|
|
167
|
+
|
|
168
|
+
if validate_grpc_handler(handler):
|
|
169
|
+
GRPC_HANDLERS.append(handler)
|
|
170
|
+
```
|
|
171
|
+
"""
|
|
172
|
+
if not isinstance(handler, tuple):
|
|
173
|
+
logger.error(f"Handler must be tuple, got {type(handler)}")
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
if len(handler) != 3:
|
|
177
|
+
logger.error(f"Handler tuple must have 3 elements, got {len(handler)}")
|
|
178
|
+
return False
|
|
179
|
+
|
|
180
|
+
add_func, servicer_class, kwargs = handler
|
|
181
|
+
|
|
182
|
+
if not callable(add_func):
|
|
183
|
+
logger.error(f"First element (add_func) must be callable, got {type(add_func)}")
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
if not isinstance(servicer_class, type):
|
|
187
|
+
logger.error(f"Second element (servicer_class) must be class, got {type(servicer_class)}")
|
|
188
|
+
return False
|
|
189
|
+
|
|
190
|
+
if not isinstance(kwargs, dict):
|
|
191
|
+
logger.error(f"Third element (kwargs) must be dict, got {type(kwargs)}")
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
return True
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def validate_grpc_handlers(
|
|
198
|
+
handlers: list,
|
|
199
|
+
) -> Tuple[bool, list[str]]:
|
|
200
|
+
"""
|
|
201
|
+
Validate list of handlers.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
handlers: List of handler tuples
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Tuple of (all_valid: bool, errors: list[str])
|
|
208
|
+
|
|
209
|
+
Example:
|
|
210
|
+
```python
|
|
211
|
+
valid, errors = validate_grpc_handlers(GRPC_HANDLERS)
|
|
212
|
+
|
|
213
|
+
if not valid:
|
|
214
|
+
for error in errors:
|
|
215
|
+
logger.error(error)
|
|
216
|
+
raise ValueError("Invalid gRPC handlers")
|
|
217
|
+
```
|
|
218
|
+
"""
|
|
219
|
+
errors = []
|
|
220
|
+
|
|
221
|
+
if not isinstance(handlers, list):
|
|
222
|
+
errors.append(f"GRPC_HANDLERS must be list, got {type(handlers)}")
|
|
223
|
+
return False, errors
|
|
224
|
+
|
|
225
|
+
for i, handler in enumerate(handlers):
|
|
226
|
+
if not validate_grpc_handler(handler):
|
|
227
|
+
errors.append(f"Handler #{i} is invalid")
|
|
228
|
+
|
|
229
|
+
all_valid = len(errors) == 0
|
|
230
|
+
return all_valid, errors
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# ============================================================================
|
|
234
|
+
# Exports
|
|
235
|
+
# ============================================================================
|
|
236
|
+
|
|
237
|
+
__all__ = [
|
|
238
|
+
'create_grpc_handler',
|
|
239
|
+
'create_multiple_grpc_handlers',
|
|
240
|
+
'validate_grpc_handler',
|
|
241
|
+
'validate_grpc_handlers',
|
|
242
|
+
]
|
|
@@ -394,7 +394,7 @@ def generate_proto_for_app(app_label: str, output_dir: Optional[Path] = None) ->
|
|
|
394
394
|
```
|
|
395
395
|
"""
|
|
396
396
|
# Get gRPC config from django-cfg (Pydantic)
|
|
397
|
-
from ..services.config_helper import get_grpc_config
|
|
397
|
+
from ..services.management.config_helper import get_grpc_config
|
|
398
398
|
|
|
399
399
|
grpc_config = get_grpc_config()
|
|
400
400
|
proto_config = grpc_config.proto if grpc_config else None
|
|
@@ -12,6 +12,45 @@ from pathlib import Path
|
|
|
12
12
|
from typing import Optional
|
|
13
13
|
from django.utils import timezone
|
|
14
14
|
|
|
15
|
+
# Rich for beautiful console output
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
from rich.table import Table
|
|
19
|
+
from rich.text import Text
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ========================================================================
|
|
23
|
+
# Module-level debug mode caching (performance optimization)
|
|
24
|
+
# ========================================================================
|
|
25
|
+
|
|
26
|
+
_debug_mode: Optional[bool] = None # Cached debug mode to avoid repeated config loads
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_debug_mode() -> bool:
|
|
30
|
+
"""
|
|
31
|
+
Get debug mode from config (cached at module level).
|
|
32
|
+
|
|
33
|
+
Loads config only once and caches the result to avoid repeated config loads.
|
|
34
|
+
This is a performance optimization - config loading can be expensive.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
True if debug mode is enabled, False otherwise
|
|
38
|
+
"""
|
|
39
|
+
global _debug_mode
|
|
40
|
+
|
|
41
|
+
if _debug_mode is not None:
|
|
42
|
+
return _debug_mode
|
|
43
|
+
|
|
44
|
+
# Load config once and cache
|
|
45
|
+
try:
|
|
46
|
+
from django_cfg.core.state import get_current_config
|
|
47
|
+
config = get_current_config()
|
|
48
|
+
_debug_mode = config.debug if config and hasattr(config, 'debug') else False
|
|
49
|
+
except Exception:
|
|
50
|
+
_debug_mode = os.getenv('DEBUG', 'false').lower() in ('true', '1', 'yes')
|
|
51
|
+
|
|
52
|
+
return _debug_mode
|
|
53
|
+
|
|
15
54
|
|
|
16
55
|
class AutoTracebackHandler(logging.Handler):
|
|
17
56
|
"""
|
|
@@ -43,8 +82,8 @@ class AutoTracebackHandler(logging.Handler):
|
|
|
43
82
|
def setup_streaming_logger(
|
|
44
83
|
name: str = "grpc_streaming",
|
|
45
84
|
logs_dir: Optional[Path] = None,
|
|
46
|
-
level: int =
|
|
47
|
-
console_level: int =
|
|
85
|
+
level: Optional[int] = None,
|
|
86
|
+
console_level: Optional[int] = None
|
|
48
87
|
) -> logging.Logger:
|
|
49
88
|
"""
|
|
50
89
|
Setup dedicated logger for gRPC streaming with file and console handlers.
|
|
@@ -52,14 +91,15 @@ def setup_streaming_logger(
|
|
|
52
91
|
Follows django-cfg logging pattern:
|
|
53
92
|
- Uses os.getcwd() / 'logs' / 'grpc_streaming' for log directory
|
|
54
93
|
- Time-based log file names (streaming_YYYYMMDD_HHMMSS.log)
|
|
55
|
-
-
|
|
56
|
-
-
|
|
94
|
+
- Auto-detects debug mode for appropriate logging levels
|
|
95
|
+
- In dev/debug: files=DEBUG+, console=DEBUG+
|
|
96
|
+
- In production: files=INFO+, console=WARNING+
|
|
57
97
|
|
|
58
98
|
Args:
|
|
59
99
|
name: Logger name (default: "grpc_streaming")
|
|
60
100
|
logs_dir: Directory for log files (default: <cwd>/logs/grpc_streaming)
|
|
61
|
-
level: File logging level (default:
|
|
62
|
-
console_level: Console logging level (default:
|
|
101
|
+
level: File logging level (default: auto-detect from debug mode)
|
|
102
|
+
console_level: Console logging level (default: auto-detect from debug mode)
|
|
63
103
|
|
|
64
104
|
Returns:
|
|
65
105
|
Configured logger instance
|
|
@@ -68,14 +108,14 @@ def setup_streaming_logger(
|
|
|
68
108
|
```python
|
|
69
109
|
from django_cfg.apps.integrations.grpc.utils import setup_streaming_logger
|
|
70
110
|
|
|
71
|
-
# Basic usage
|
|
111
|
+
# Basic usage (auto-detects debug mode)
|
|
72
112
|
logger = setup_streaming_logger()
|
|
73
113
|
|
|
74
114
|
# Custom configuration
|
|
75
115
|
logger = setup_streaming_logger(
|
|
76
116
|
name="my_streaming_service",
|
|
77
117
|
logs_dir=Path("/var/log/grpc"),
|
|
78
|
-
level=logging.INFO
|
|
118
|
+
level=logging.INFO # Override auto-detection
|
|
79
119
|
)
|
|
80
120
|
|
|
81
121
|
logger.info("Service started")
|
|
@@ -87,8 +127,21 @@ def setup_streaming_logger(
|
|
|
87
127
|
- Time-based log file names
|
|
88
128
|
- No duplicate logs (propagate=False)
|
|
89
129
|
- UTF-8 encoding
|
|
130
|
+
- Debug mode auto-detection (cached for performance)
|
|
90
131
|
- Reusable across all django-cfg gRPC projects
|
|
91
132
|
"""
|
|
133
|
+
# Auto-detect debug mode (cached - loaded once)
|
|
134
|
+
debug = _get_debug_mode()
|
|
135
|
+
|
|
136
|
+
# Auto-determine logging levels based on debug mode if not explicitly provided
|
|
137
|
+
if level is None:
|
|
138
|
+
# File handlers: DEBUG in dev, INFO in production
|
|
139
|
+
level = logging.DEBUG if debug else logging.INFO
|
|
140
|
+
|
|
141
|
+
if console_level is None:
|
|
142
|
+
# Console: DEBUG in dev (full visibility), WARNING in production (reduce noise)
|
|
143
|
+
console_level = logging.DEBUG if debug else logging.WARNING
|
|
144
|
+
|
|
92
145
|
# Create logger
|
|
93
146
|
streaming_logger = logging.getLogger(name)
|
|
94
147
|
streaming_logger.setLevel(level)
|
|
@@ -128,11 +181,14 @@ def setup_streaming_logger(
|
|
|
128
181
|
file_handler = AutoTracebackHandler(base_file_handler)
|
|
129
182
|
streaming_logger.addHandler(file_handler)
|
|
130
183
|
|
|
131
|
-
# Console handler - important messages only
|
|
132
|
-
|
|
133
|
-
|
|
184
|
+
# Console handler - important messages only (also with auto-traceback)
|
|
185
|
+
base_console_handler = logging.StreamHandler()
|
|
186
|
+
base_console_handler.setLevel(console_level)
|
|
134
187
|
console_formatter = logging.Formatter('%(levelname)s: %(message)s')
|
|
135
|
-
|
|
188
|
+
base_console_handler.setFormatter(console_formatter)
|
|
189
|
+
|
|
190
|
+
# Wrap console handler with auto-traceback too
|
|
191
|
+
console_handler = AutoTracebackHandler(base_console_handler)
|
|
136
192
|
streaming_logger.addHandler(console_handler)
|
|
137
193
|
|
|
138
194
|
# Prevent propagation to avoid duplicate logs
|
|
@@ -174,4 +230,196 @@ def get_streaming_logger(name: str = "grpc_streaming") -> logging.Logger:
|
|
|
174
230
|
return logger
|
|
175
231
|
|
|
176
232
|
|
|
177
|
-
|
|
233
|
+
def log_server_start(
|
|
234
|
+
logger: logging.Logger,
|
|
235
|
+
server_type: str = "Server",
|
|
236
|
+
mode: str = "Development",
|
|
237
|
+
hotreload_enabled: bool = False,
|
|
238
|
+
use_rich: bool = True,
|
|
239
|
+
**extra_info
|
|
240
|
+
):
|
|
241
|
+
"""
|
|
242
|
+
Log server startup with timestamp and configuration using Rich panels.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
logger: Logger instance to use
|
|
246
|
+
server_type: Type of server (e.g., "gRPC Server", "WebSocket Server")
|
|
247
|
+
mode: Running mode (Development/Production)
|
|
248
|
+
hotreload_enabled: Whether hot-reload is enabled
|
|
249
|
+
use_rich: Use Rich for beautiful output (default: True)
|
|
250
|
+
**extra_info: Additional key-value pairs to log
|
|
251
|
+
|
|
252
|
+
Example:
|
|
253
|
+
```python
|
|
254
|
+
from django_cfg.apps.integrations.grpc.utils import log_server_start
|
|
255
|
+
from datetime import datetime
|
|
256
|
+
|
|
257
|
+
start_time = log_server_start(
|
|
258
|
+
logger,
|
|
259
|
+
server_type="gRPC Server",
|
|
260
|
+
mode="Development",
|
|
261
|
+
hotreload_enabled=True,
|
|
262
|
+
host="0.0.0.0",
|
|
263
|
+
port=50051
|
|
264
|
+
)
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
datetime object of start time for later use in log_server_shutdown
|
|
269
|
+
"""
|
|
270
|
+
from datetime import datetime
|
|
271
|
+
|
|
272
|
+
start_time = datetime.now()
|
|
273
|
+
|
|
274
|
+
if use_rich:
|
|
275
|
+
# Create Rich table for server info
|
|
276
|
+
console = Console()
|
|
277
|
+
|
|
278
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
279
|
+
table.add_column("Key", style="cyan")
|
|
280
|
+
table.add_column("Value", style="white")
|
|
281
|
+
|
|
282
|
+
table.add_row("⏰ Started at", start_time.strftime('%Y-%m-%d %H:%M:%S'))
|
|
283
|
+
table.add_row("Mode", f"[{'red' if mode == 'Production' else 'green'}]{mode}[/]")
|
|
284
|
+
table.add_row("Hotreload", f"[{'yellow' if hotreload_enabled else 'dim'}]{'Enabled ⚡' if hotreload_enabled else 'Disabled'}[/]")
|
|
285
|
+
|
|
286
|
+
# Add extra info
|
|
287
|
+
for key, value in extra_info.items():
|
|
288
|
+
key_display = key.replace('_', ' ').title()
|
|
289
|
+
table.add_row(key_display, str(value))
|
|
290
|
+
|
|
291
|
+
# Create panel
|
|
292
|
+
panel = Panel(
|
|
293
|
+
table,
|
|
294
|
+
title=f"[bold green]🚀 {server_type} Starting[/bold green]",
|
|
295
|
+
border_style="green",
|
|
296
|
+
padding=(1, 2)
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
console.print(panel)
|
|
300
|
+
|
|
301
|
+
if hotreload_enabled:
|
|
302
|
+
console.print(
|
|
303
|
+
"[yellow]⚠️ Hotreload active - connections may be dropped on code changes[/yellow]",
|
|
304
|
+
style="bold"
|
|
305
|
+
)
|
|
306
|
+
else:
|
|
307
|
+
# Fallback to simple logging
|
|
308
|
+
logger.info("=" * 80)
|
|
309
|
+
logger.info(f"🚀 {server_type} Starting")
|
|
310
|
+
logger.info(f" ⏰ Started at: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
311
|
+
logger.info(f" Mode: {mode}")
|
|
312
|
+
logger.info(f" Hotreload: {'Enabled' if hotreload_enabled else 'Disabled'}")
|
|
313
|
+
|
|
314
|
+
for key, value in extra_info.items():
|
|
315
|
+
key_display = key.replace('_', ' ').title()
|
|
316
|
+
logger.info(f" {key_display}: {value}")
|
|
317
|
+
|
|
318
|
+
if hotreload_enabled:
|
|
319
|
+
logger.warning(
|
|
320
|
+
"⚠️ Hotreload active - connections may be dropped on code changes"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
logger.info("=" * 80)
|
|
324
|
+
|
|
325
|
+
return start_time
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def log_server_shutdown(
|
|
329
|
+
logger: logging.Logger,
|
|
330
|
+
start_time,
|
|
331
|
+
server_type: str = "Server",
|
|
332
|
+
reason: str = None,
|
|
333
|
+
use_rich: bool = True,
|
|
334
|
+
**extra_info
|
|
335
|
+
):
|
|
336
|
+
"""
|
|
337
|
+
Log server shutdown with uptime calculation using Rich panels.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
logger: Logger instance to use
|
|
341
|
+
start_time: datetime object from log_server_start()
|
|
342
|
+
server_type: Type of server (e.g., "gRPC Server", "WebSocket Server")
|
|
343
|
+
reason: Shutdown reason (e.g., "Keyboard interrupt", "Hotreload")
|
|
344
|
+
use_rich: Use Rich for beautiful output (default: True)
|
|
345
|
+
**extra_info: Additional key-value pairs to log
|
|
346
|
+
|
|
347
|
+
Example:
|
|
348
|
+
```python
|
|
349
|
+
from django_cfg.apps.integrations.grpc.utils import log_server_shutdown
|
|
350
|
+
|
|
351
|
+
log_server_shutdown(
|
|
352
|
+
logger,
|
|
353
|
+
start_time,
|
|
354
|
+
server_type="gRPC Server",
|
|
355
|
+
reason="Hotreload triggered",
|
|
356
|
+
active_connections=5
|
|
357
|
+
)
|
|
358
|
+
```
|
|
359
|
+
"""
|
|
360
|
+
from datetime import datetime
|
|
361
|
+
|
|
362
|
+
end_time = datetime.now()
|
|
363
|
+
uptime = end_time - start_time
|
|
364
|
+
uptime_seconds = int(uptime.total_seconds())
|
|
365
|
+
|
|
366
|
+
# Format uptime
|
|
367
|
+
hours = uptime_seconds // 3600
|
|
368
|
+
minutes = (uptime_seconds % 3600) // 60
|
|
369
|
+
seconds = uptime_seconds % 60
|
|
370
|
+
uptime_str = f"{hours}h {minutes}m {seconds}s"
|
|
371
|
+
|
|
372
|
+
if use_rich:
|
|
373
|
+
# Create Rich table for shutdown info
|
|
374
|
+
console = Console()
|
|
375
|
+
|
|
376
|
+
table = Table(show_header=False, box=None, padding=(0, 1))
|
|
377
|
+
table.add_column("Key", style="cyan")
|
|
378
|
+
table.add_column("Value", style="white")
|
|
379
|
+
|
|
380
|
+
if reason:
|
|
381
|
+
table.add_row("📋 Reason", reason)
|
|
382
|
+
|
|
383
|
+
table.add_row("⏱️ Uptime", f"[bold]{uptime_str}[/bold]")
|
|
384
|
+
table.add_row("🕐 Stopped at", end_time.strftime('%Y-%m-%d %H:%M:%S'))
|
|
385
|
+
|
|
386
|
+
# Add extra info
|
|
387
|
+
for key, value in extra_info.items():
|
|
388
|
+
key_display = key.replace('_', ' ').title()
|
|
389
|
+
table.add_row(key_display, str(value))
|
|
390
|
+
|
|
391
|
+
# Create panel
|
|
392
|
+
panel = Panel(
|
|
393
|
+
table,
|
|
394
|
+
title=f"[bold red]🧹 Shutting down {server_type}[/bold red]",
|
|
395
|
+
border_style="red",
|
|
396
|
+
padding=(1, 2)
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
console.print(panel)
|
|
400
|
+
console.print("[green]✅ Server shutdown complete[/green]", style="bold")
|
|
401
|
+
else:
|
|
402
|
+
# Fallback to simple logging
|
|
403
|
+
logger.info("=" * 80)
|
|
404
|
+
logger.info(f"🧹 Shutting down {server_type}...")
|
|
405
|
+
|
|
406
|
+
if reason:
|
|
407
|
+
logger.info(f" 📋 Reason: {reason}")
|
|
408
|
+
|
|
409
|
+
logger.info(f" ⏱️ Uptime: {uptime_str}")
|
|
410
|
+
logger.info(f" 🕐 Stopped at: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
411
|
+
|
|
412
|
+
for key, value in extra_info.items():
|
|
413
|
+
key_display = key.replace('_', ' ').title()
|
|
414
|
+
logger.info(f" {key_display}: {value}")
|
|
415
|
+
|
|
416
|
+
logger.info("✅ Server shutdown complete")
|
|
417
|
+
logger.info("=" * 80)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
__all__ = [
|
|
421
|
+
"setup_streaming_logger",
|
|
422
|
+
"get_streaming_logger",
|
|
423
|
+
"log_server_start",
|
|
424
|
+
"log_server_shutdown",
|
|
425
|
+
]
|
|
@@ -22,7 +22,7 @@ from ..serializers.charts import (
|
|
|
22
22
|
ServerUptimeChartSerializer,
|
|
23
23
|
ServiceActivityChartSerializer,
|
|
24
24
|
)
|
|
25
|
-
from ..services.
|
|
25
|
+
from ..services.rendering.charts import ChartGeneratorService
|
|
26
26
|
|
|
27
27
|
logger = get_logger("grpc.charts")
|
|
28
28
|
|
|
@@ -16,7 +16,7 @@ from rest_framework.response import Response
|
|
|
16
16
|
from ..models import GRPCRequestLog, GRPCServerStatus
|
|
17
17
|
from ..serializers.config import GRPCConfigSerializer, GRPCServerInfoSerializer
|
|
18
18
|
from ..services import ServiceDiscovery
|
|
19
|
-
from ..services.config_helper import get_grpc_config, get_grpc_server_config
|
|
19
|
+
from ..services.management.config_helper import get_grpc_config, get_grpc_server_config
|
|
20
20
|
|
|
21
21
|
logger = get_logger("grpc.config")
|
|
22
22
|
|