django-cfg 1.4.61__py3-none-any.whl → 1.4.63__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/accounts/services/otp_service.py +3 -14
- django_cfg/apps/centrifugo/__init__.py +57 -0
- django_cfg/apps/centrifugo/admin/__init__.py +13 -0
- django_cfg/apps/centrifugo/admin/centrifugo_log.py +249 -0
- django_cfg/apps/centrifugo/admin/config.py +82 -0
- django_cfg/apps/centrifugo/apps.py +31 -0
- django_cfg/apps/centrifugo/codegen/IMPLEMENTATION_SUMMARY.md +475 -0
- django_cfg/apps/centrifugo/codegen/README.md +242 -0
- django_cfg/apps/centrifugo/codegen/USAGE.md +616 -0
- django_cfg/apps/centrifugo/codegen/__init__.py +19 -0
- django_cfg/apps/centrifugo/codegen/discovery.py +246 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/generator.py +174 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/README.md.j2 +182 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/client.go.j2 +64 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/go.mod.j2 +10 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2 +300 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/rpc_client.go.j2.old +267 -0
- django_cfg/apps/centrifugo/codegen/generators/go_thin/templates/types.go.j2 +16 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/__init__.py +7 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/generator.py +241 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/README.md.j2 +128 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/__init__.py.j2 +22 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/client.py.j2 +73 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/models.py.j2 +19 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/requirements.txt.j2 +8 -0
- django_cfg/apps/centrifugo/codegen/generators/python_thin/templates/rpc_client.py.j2 +193 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/__init__.py +5 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/generator.py +124 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/README.md.j2 +38 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/client.ts.j2 +25 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/index.ts.j2 +12 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/package.json.j2 +13 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/rpc-client.ts.j2 +137 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/tsconfig.json.j2 +14 -0
- django_cfg/apps/centrifugo/codegen/generators/typescript_thin/templates/types.ts.j2 +9 -0
- django_cfg/apps/centrifugo/codegen/utils/__init__.py +37 -0
- django_cfg/apps/centrifugo/codegen/utils/naming.py +155 -0
- django_cfg/apps/centrifugo/codegen/utils/type_converter.py +349 -0
- django_cfg/apps/centrifugo/decorators.py +137 -0
- django_cfg/apps/centrifugo/management/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/__init__.py +1 -0
- django_cfg/apps/centrifugo/management/commands/generate_centrifugo_clients.py +254 -0
- django_cfg/apps/centrifugo/managers/__init__.py +12 -0
- django_cfg/apps/centrifugo/managers/centrifugo_log.py +264 -0
- django_cfg/apps/centrifugo/migrations/0001_initial.py +164 -0
- django_cfg/apps/centrifugo/migrations/__init__.py +3 -0
- django_cfg/apps/centrifugo/models/__init__.py +11 -0
- django_cfg/apps/centrifugo/models/centrifugo_log.py +210 -0
- django_cfg/apps/centrifugo/registry.py +106 -0
- django_cfg/apps/centrifugo/router.py +125 -0
- django_cfg/apps/centrifugo/serializers/__init__.py +40 -0
- django_cfg/apps/centrifugo/serializers/admin_api.py +264 -0
- django_cfg/apps/centrifugo/serializers/channels.py +26 -0
- django_cfg/apps/centrifugo/serializers/health.py +17 -0
- django_cfg/apps/centrifugo/serializers/publishes.py +16 -0
- django_cfg/apps/centrifugo/serializers/stats.py +21 -0
- django_cfg/apps/centrifugo/services/__init__.py +12 -0
- django_cfg/apps/centrifugo/services/client/__init__.py +29 -0
- django_cfg/apps/centrifugo/services/client/client.py +577 -0
- django_cfg/apps/centrifugo/services/client/config.py +228 -0
- django_cfg/apps/centrifugo/services/client/exceptions.py +212 -0
- django_cfg/apps/centrifugo/services/config_helper.py +63 -0
- django_cfg/apps/centrifugo/services/dashboard_notifier.py +157 -0
- django_cfg/apps/centrifugo/services/logging.py +677 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +260 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +313 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +803 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +333 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +432 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +33 -0
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +210 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +46 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +123 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +45 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +84 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/stat_cards.html +23 -20
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +91 -0
- django_cfg/apps/{ipc/templates/django_cfg_ipc → centrifugo/templates/django_cfg_centrifugo}/components/tab_navigation.html +15 -15
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +415 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +61 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +58 -0
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +48 -0
- django_cfg/apps/centrifugo/templatetags/__init__.py +1 -0
- django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +81 -0
- django_cfg/apps/centrifugo/urls.py +31 -0
- django_cfg/apps/{ipc → centrifugo}/urls_admin.py +4 -4
- django_cfg/apps/centrifugo/views/__init__.py +15 -0
- django_cfg/apps/centrifugo/views/admin_api.py +374 -0
- django_cfg/apps/centrifugo/views/dashboard.py +15 -0
- django_cfg/apps/centrifugo/views/monitoring.py +286 -0
- django_cfg/apps/centrifugo/views/testing_api.py +422 -0
- django_cfg/apps/support/utils/support_email_service.py +5 -18
- django_cfg/apps/tasks/templates/tasks/layout/base.html +0 -2
- django_cfg/apps/urls.py +5 -5
- django_cfg/core/base/config_model.py +4 -44
- django_cfg/core/builders/apps_builder.py +2 -2
- django_cfg/core/generation/integration_generators/third_party.py +8 -8
- django_cfg/core/utils/__init__.py +5 -0
- django_cfg/core/utils/url_helpers.py +73 -0
- django_cfg/modules/base.py +7 -7
- django_cfg/modules/django_client/core/__init__.py +2 -1
- django_cfg/modules/django_client/core/config/config.py +8 -0
- django_cfg/modules/django_client/core/generator/__init__.py +42 -2
- django_cfg/modules/django_client/core/generator/go/__init__.py +14 -0
- django_cfg/modules/django_client/core/generator/go/client_generator.py +124 -0
- django_cfg/modules/django_client/core/generator/go/files_generator.py +133 -0
- django_cfg/modules/django_client/core/generator/go/generator.py +203 -0
- django_cfg/modules/django_client/core/generator/go/models_generator.py +304 -0
- django_cfg/modules/django_client/core/generator/go/naming.py +193 -0
- django_cfg/modules/django_client/core/generator/go/operations_generator.py +134 -0
- django_cfg/modules/django_client/core/generator/go/templates/Makefile.j2 +38 -0
- django_cfg/modules/django_client/core/generator/go/templates/README.md.j2 +55 -0
- django_cfg/modules/django_client/core/generator/go/templates/client.go.j2 +122 -0
- django_cfg/modules/django_client/core/generator/go/templates/enums.go.j2 +49 -0
- django_cfg/modules/django_client/core/generator/go/templates/errors.go.j2 +182 -0
- django_cfg/modules/django_client/core/generator/go/templates/go.mod.j2 +6 -0
- django_cfg/modules/django_client/core/generator/go/templates/main_client.go.j2 +60 -0
- django_cfg/modules/django_client/core/generator/go/templates/middleware.go.j2 +388 -0
- django_cfg/modules/django_client/core/generator/go/templates/models.go.j2 +28 -0
- django_cfg/modules/django_client/core/generator/go/templates/operations_client.go.j2 +142 -0
- django_cfg/modules/django_client/core/generator/go/templates/validation.go.j2 +217 -0
- django_cfg/modules/django_client/core/generator/go/type_mapper.py +380 -0
- django_cfg/modules/django_client/management/commands/generate_client.py +53 -3
- django_cfg/modules/django_client/system/generate_mjs_clients.py +3 -1
- django_cfg/modules/django_client/system/schema_parser.py +5 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +1 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +7 -4
- django_cfg/modules/django_unfold/dashboard.py +25 -19
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/core.py +2 -0
- django_cfg/registry/modules.py +2 -2
- django_cfg/static/js/api/centrifugo/client.mjs +164 -0
- django_cfg/static/js/api/centrifugo/index.mjs +13 -0
- django_cfg/static/js/api/index.mjs +5 -5
- django_cfg/static/js/api/types.mjs +89 -26
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/METADATA +1 -1
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/RECORD +142 -68
- django_cfg/apps/ipc/README.md +0 -346
- django_cfg/apps/ipc/RPC_LOGGING.md +0 -321
- django_cfg/apps/ipc/TESTING.md +0 -539
- django_cfg/apps/ipc/__init__.py +0 -60
- django_cfg/apps/ipc/admin.py +0 -212
- django_cfg/apps/ipc/apps.py +0 -28
- django_cfg/apps/ipc/migrations/0001_initial.py +0 -137
- django_cfg/apps/ipc/migrations/__init__.py +0 -0
- django_cfg/apps/ipc/models.py +0 -221
- django_cfg/apps/ipc/serializers/__init__.py +0 -29
- django_cfg/apps/ipc/serializers/serializers.py +0 -343
- django_cfg/apps/ipc/services/__init__.py +0 -7
- django_cfg/apps/ipc/services/client/__init__.py +0 -23
- django_cfg/apps/ipc/services/client/client.py +0 -621
- django_cfg/apps/ipc/services/client/config.py +0 -214
- django_cfg/apps/ipc/services/client/exceptions.py +0 -201
- django_cfg/apps/ipc/services/logging.py +0 -239
- django_cfg/apps/ipc/services/monitor.py +0 -466
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/main.mjs +0 -269
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/overview.mjs +0 -259
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard/testing.mjs +0 -375
- django_cfg/apps/ipc/static/django_cfg_ipc/js/dashboard.mjs.old +0 -441
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/methods_content.html +0 -22
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/notifications_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/overview_content.html +0 -9
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/requests_content.html +0 -23
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/system_status.html +0 -47
- django_cfg/apps/ipc/templates/django_cfg_ipc/components/testing_tools.html +0 -184
- django_cfg/apps/ipc/templates/django_cfg_ipc/layout/base.html +0 -71
- django_cfg/apps/ipc/templates/django_cfg_ipc/pages/dashboard.html +0 -56
- django_cfg/apps/ipc/urls.py +0 -23
- django_cfg/apps/ipc/views/__init__.py +0 -13
- django_cfg/apps/ipc/views/dashboard.py +0 -15
- django_cfg/apps/ipc/views/monitoring.py +0 -251
- django_cfg/apps/ipc/views/testing.py +0 -285
- django_cfg/static/js/api/ipc/client.mjs +0 -114
- django_cfg/static/js/api/ipc/index.mjs +0 -13
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.61.dist-info → django_cfg-1.4.63.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Centrifugo Logging helper for tracking publish operations.
|
|
3
|
+
|
|
4
|
+
Provides async-safe logging of Centrifugo publishes to database.
|
|
5
|
+
Mirrors RPCLogger patterns from django-ipc for easy migration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any, Optional
|
|
10
|
+
|
|
11
|
+
from django_cfg.modules.django_logging import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger("centrifugo")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CentrifugoLogger:
|
|
17
|
+
"""
|
|
18
|
+
Helper class for logging Centrifugo publish operations to database.
|
|
19
|
+
|
|
20
|
+
Mirrors RPCLogger interface for migration compatibility.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
>>> log_entry = CentrifugoLogger.create_log(
|
|
24
|
+
... message_id="abc123",
|
|
25
|
+
... channel="user#456",
|
|
26
|
+
... data={"title": "Hello", "message": "World"},
|
|
27
|
+
... wait_for_ack=True,
|
|
28
|
+
... user=request.user if authenticated else None
|
|
29
|
+
... )
|
|
30
|
+
>>> # ... publish message ...
|
|
31
|
+
>>> CentrifugoLogger.mark_success(log_entry, acks_received=1, duration_ms=125)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def is_logging_enabled() -> bool:
|
|
36
|
+
"""
|
|
37
|
+
Check if Centrifugo logging is enabled in django-cfg config.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
bool: True if logging is enabled
|
|
41
|
+
"""
|
|
42
|
+
from .config_helper import get_centrifugo_config
|
|
43
|
+
|
|
44
|
+
config = get_centrifugo_config()
|
|
45
|
+
|
|
46
|
+
if not config:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
# If log_only_with_ack is True, only log ACK calls
|
|
50
|
+
if config.log_only_with_ack:
|
|
51
|
+
return True # Will check wait_for_ack in create_log
|
|
52
|
+
|
|
53
|
+
return config.log_all_calls
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
async def create_log_async(
|
|
57
|
+
message_id: str,
|
|
58
|
+
channel: str,
|
|
59
|
+
data: dict,
|
|
60
|
+
wait_for_ack: bool = False,
|
|
61
|
+
ack_timeout: int | None = None,
|
|
62
|
+
acks_expected: int | None = None,
|
|
63
|
+
is_notification: bool = True,
|
|
64
|
+
user: Any = None,
|
|
65
|
+
caller_ip: str | None = None,
|
|
66
|
+
user_agent: str | None = None,
|
|
67
|
+
) -> Any | None:
|
|
68
|
+
"""
|
|
69
|
+
Async version of create_log for use in async contexts.
|
|
70
|
+
"""
|
|
71
|
+
logging_enabled = CentrifugoLogger.is_logging_enabled()
|
|
72
|
+
logger.info(f"🔍 create_log_async called: message_id={message_id}, channel={channel}, logging_enabled={logging_enabled}")
|
|
73
|
+
|
|
74
|
+
if not logging_enabled:
|
|
75
|
+
logger.warning(f"❌ Logging disabled, skipping log creation for {message_id}")
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
# If log_only_with_ack is enabled, skip non-ACK publishes
|
|
79
|
+
from .config_helper import get_centrifugo_config
|
|
80
|
+
|
|
81
|
+
config = get_centrifugo_config()
|
|
82
|
+
logger.info(f"🔍 Config check: log_only_with_ack={config.log_only_with_ack if config else None}, wait_for_ack={wait_for_ack}")
|
|
83
|
+
|
|
84
|
+
if config and config.log_only_with_ack and not wait_for_ack:
|
|
85
|
+
logger.info(f"⏭️ Skipping non-ACK publish for {message_id}")
|
|
86
|
+
return None
|
|
87
|
+
|
|
88
|
+
logger.info(f"✅ Creating CentrifugoLog entry for {message_id} (async)")
|
|
89
|
+
try:
|
|
90
|
+
from asgiref.sync import sync_to_async
|
|
91
|
+
from ..models import CentrifugoLog
|
|
92
|
+
|
|
93
|
+
# Wrap ORM call in sync_to_async
|
|
94
|
+
log_entry = await sync_to_async(CentrifugoLog.objects.create)(
|
|
95
|
+
message_id=message_id,
|
|
96
|
+
channel=channel,
|
|
97
|
+
data=data,
|
|
98
|
+
wait_for_ack=wait_for_ack,
|
|
99
|
+
ack_timeout=ack_timeout,
|
|
100
|
+
acks_expected=acks_expected,
|
|
101
|
+
is_notification=is_notification,
|
|
102
|
+
user=user,
|
|
103
|
+
caller_ip=caller_ip,
|
|
104
|
+
user_agent=user_agent,
|
|
105
|
+
status=CentrifugoLog.StatusChoices.PENDING,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
logger.debug(
|
|
109
|
+
f"Created Centrifugo log entry: {message_id} on channel {channel}",
|
|
110
|
+
extra={
|
|
111
|
+
"message_id": message_id,
|
|
112
|
+
"channel": channel,
|
|
113
|
+
"wait_for_ack": wait_for_ack,
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Notify dashboard about new publish
|
|
118
|
+
try:
|
|
119
|
+
from .dashboard_notifier import DashboardNotifier
|
|
120
|
+
await DashboardNotifier.notify_new_publish(log_entry)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.debug(f"Dashboard notification failed: {e}")
|
|
123
|
+
|
|
124
|
+
return log_entry
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.error(
|
|
128
|
+
f"Failed to create Centrifugo log entry: {e}",
|
|
129
|
+
extra={"message_id": message_id, "error": str(e)},
|
|
130
|
+
)
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
@staticmethod
|
|
134
|
+
def create_log(
|
|
135
|
+
message_id: str,
|
|
136
|
+
channel: str,
|
|
137
|
+
data: dict,
|
|
138
|
+
wait_for_ack: bool = False,
|
|
139
|
+
ack_timeout: int | None = None,
|
|
140
|
+
acks_expected: int | None = None,
|
|
141
|
+
is_notification: bool = True,
|
|
142
|
+
user: Any = None,
|
|
143
|
+
caller_ip: str | None = None,
|
|
144
|
+
user_agent: str | None = None,
|
|
145
|
+
) -> Any | None:
|
|
146
|
+
"""
|
|
147
|
+
Create log entry for Centrifugo publish operation.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
message_id: Unique message identifier
|
|
151
|
+
channel: Centrifugo channel
|
|
152
|
+
data: Published data
|
|
153
|
+
wait_for_ack: Whether this publish waits for ACK
|
|
154
|
+
ack_timeout: ACK timeout in seconds
|
|
155
|
+
acks_expected: Expected number of ACKs
|
|
156
|
+
is_notification: Whether this is a notification
|
|
157
|
+
user: Django User instance
|
|
158
|
+
caller_ip: IP address of caller
|
|
159
|
+
user_agent: User agent of caller
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
CentrifugoLog instance or None if logging disabled
|
|
163
|
+
"""
|
|
164
|
+
logging_enabled = CentrifugoLogger.is_logging_enabled()
|
|
165
|
+
logger.info(f"🔍 create_log called: message_id={message_id}, channel={channel}, logging_enabled={logging_enabled}")
|
|
166
|
+
|
|
167
|
+
if not logging_enabled:
|
|
168
|
+
logger.warning(f"❌ Logging disabled, skipping log creation for {message_id}")
|
|
169
|
+
return None
|
|
170
|
+
|
|
171
|
+
# If log_only_with_ack is enabled, skip non-ACK publishes
|
|
172
|
+
from .config_helper import get_centrifugo_config
|
|
173
|
+
|
|
174
|
+
config = get_centrifugo_config()
|
|
175
|
+
logger.info(f"🔍 Config check: log_only_with_ack={config.log_only_with_ack if config else None}, wait_for_ack={wait_for_ack}")
|
|
176
|
+
|
|
177
|
+
if config and config.log_only_with_ack and not wait_for_ack:
|
|
178
|
+
logger.info(f"⏭️ Skipping non-ACK publish for {message_id}")
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
logger.info(f"✅ Creating CentrifugoLog entry for {message_id} (sync)")
|
|
182
|
+
try:
|
|
183
|
+
from ..models import CentrifugoLog
|
|
184
|
+
|
|
185
|
+
# Direct synchronous call - will fail if called from async context
|
|
186
|
+
# Use create_log_async() for async contexts instead
|
|
187
|
+
log_entry = CentrifugoLog.objects.create(
|
|
188
|
+
message_id=message_id,
|
|
189
|
+
channel=channel,
|
|
190
|
+
data=data,
|
|
191
|
+
wait_for_ack=wait_for_ack,
|
|
192
|
+
ack_timeout=ack_timeout,
|
|
193
|
+
acks_expected=acks_expected,
|
|
194
|
+
is_notification=is_notification,
|
|
195
|
+
user=user,
|
|
196
|
+
caller_ip=caller_ip,
|
|
197
|
+
user_agent=user_agent,
|
|
198
|
+
status=CentrifugoLog.StatusChoices.PENDING,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
logger.debug(
|
|
202
|
+
f"Created Centrifugo log entry: {message_id} on channel {channel}",
|
|
203
|
+
extra={
|
|
204
|
+
"message_id": message_id,
|
|
205
|
+
"channel": channel,
|
|
206
|
+
"wait_for_ack": wait_for_ack,
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return log_entry
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
logger.error(
|
|
214
|
+
f"Failed to create Centrifugo log entry: {e}",
|
|
215
|
+
extra={"message_id": message_id, "error": str(e)},
|
|
216
|
+
)
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
@staticmethod
|
|
220
|
+
async def mark_success_async(
|
|
221
|
+
log_entry: Any,
|
|
222
|
+
acks_received: int = 0,
|
|
223
|
+
duration_ms: int | None = None,
|
|
224
|
+
) -> None:
|
|
225
|
+
"""
|
|
226
|
+
Mark publish operation as successful (async version).
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
log_entry: CentrifugoLog instance
|
|
230
|
+
acks_received: Number of ACKs received
|
|
231
|
+
duration_ms: Duration in milliseconds
|
|
232
|
+
"""
|
|
233
|
+
if log_entry is None:
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
try:
|
|
237
|
+
from asgiref.sync import sync_to_async
|
|
238
|
+
from ..models import CentrifugoLog
|
|
239
|
+
|
|
240
|
+
await sync_to_async(CentrifugoLog.objects.mark_success)(
|
|
241
|
+
log_instance=log_entry,
|
|
242
|
+
acks_received=acks_received,
|
|
243
|
+
duration_ms=duration_ms,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
logger.info(
|
|
247
|
+
f"Centrifugo publish successful: {log_entry.message_id}",
|
|
248
|
+
extra={
|
|
249
|
+
"message_id": log_entry.message_id,
|
|
250
|
+
"channel": log_entry.channel,
|
|
251
|
+
"acks_received": acks_received,
|
|
252
|
+
"duration_ms": duration_ms,
|
|
253
|
+
},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Notify dashboard about status change
|
|
257
|
+
try:
|
|
258
|
+
from .dashboard_notifier import DashboardNotifier
|
|
259
|
+
await DashboardNotifier.notify_status_change(log_entry, old_status="pending")
|
|
260
|
+
except Exception as notify_error:
|
|
261
|
+
logger.debug(f"Dashboard notification failed: {notify_error}")
|
|
262
|
+
|
|
263
|
+
except Exception as e:
|
|
264
|
+
logger.error(
|
|
265
|
+
f"Failed to mark Centrifugo log as success: {e}",
|
|
266
|
+
extra={"message_id": getattr(log_entry, "message_id", "unknown")},
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def mark_success(
|
|
271
|
+
log_entry: Any,
|
|
272
|
+
acks_received: int = 0,
|
|
273
|
+
duration_ms: int | None = None,
|
|
274
|
+
) -> None:
|
|
275
|
+
"""
|
|
276
|
+
Mark publish operation as successful (sync version).
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
log_entry: CentrifugoLog instance
|
|
280
|
+
acks_received: Number of ACKs received
|
|
281
|
+
duration_ms: Duration in milliseconds
|
|
282
|
+
"""
|
|
283
|
+
if log_entry is None:
|
|
284
|
+
return
|
|
285
|
+
|
|
286
|
+
try:
|
|
287
|
+
from ..models import CentrifugoLog
|
|
288
|
+
|
|
289
|
+
CentrifugoLog.objects.mark_success(
|
|
290
|
+
log_instance=log_entry,
|
|
291
|
+
acks_received=acks_received,
|
|
292
|
+
duration_ms=duration_ms,
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
logger.info(
|
|
296
|
+
f"Centrifugo publish successful: {log_entry.message_id}",
|
|
297
|
+
extra={
|
|
298
|
+
"message_id": log_entry.message_id,
|
|
299
|
+
"channel": log_entry.channel,
|
|
300
|
+
"acks_received": acks_received,
|
|
301
|
+
"duration_ms": duration_ms,
|
|
302
|
+
},
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
except Exception as e:
|
|
306
|
+
logger.error(
|
|
307
|
+
f"Failed to mark Centrifugo log as success: {e}",
|
|
308
|
+
extra={"message_id": getattr(log_entry, "message_id", "unknown")},
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
@staticmethod
|
|
312
|
+
def mark_partial(
|
|
313
|
+
log_entry: Any,
|
|
314
|
+
acks_received: int,
|
|
315
|
+
acks_expected: int,
|
|
316
|
+
duration_ms: int | None = None,
|
|
317
|
+
) -> None:
|
|
318
|
+
"""
|
|
319
|
+
Mark publish operation as partially delivered.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
log_entry: CentrifugoLog instance
|
|
323
|
+
acks_received: Number of ACKs received
|
|
324
|
+
acks_expected: Number of ACKs expected
|
|
325
|
+
duration_ms: Duration in milliseconds
|
|
326
|
+
"""
|
|
327
|
+
if log_entry is None:
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
from ..models import CentrifugoLog
|
|
332
|
+
|
|
333
|
+
CentrifugoLog.objects.mark_partial(
|
|
334
|
+
log_instance=log_entry,
|
|
335
|
+
acks_received=acks_received,
|
|
336
|
+
acks_expected=acks_expected,
|
|
337
|
+
duration_ms=duration_ms,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
logger.warning(
|
|
341
|
+
f"Centrifugo publish partially delivered: {log_entry.message_id}",
|
|
342
|
+
extra={
|
|
343
|
+
"message_id": log_entry.message_id,
|
|
344
|
+
"channel": log_entry.channel,
|
|
345
|
+
"acks_received": acks_received,
|
|
346
|
+
"acks_expected": acks_expected,
|
|
347
|
+
"duration_ms": duration_ms,
|
|
348
|
+
},
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
except Exception as e:
|
|
352
|
+
logger.error(
|
|
353
|
+
f"Failed to mark Centrifugo log as partial: {e}",
|
|
354
|
+
extra={"message_id": getattr(log_entry, "message_id", "unknown")},
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
@staticmethod
|
|
358
|
+
def mark_failed(
|
|
359
|
+
log_entry: Any,
|
|
360
|
+
error_code: str,
|
|
361
|
+
error_message: str,
|
|
362
|
+
duration_ms: int | None = None,
|
|
363
|
+
) -> None:
|
|
364
|
+
"""
|
|
365
|
+
Mark publish operation as failed.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
log_entry: CentrifugoLog instance
|
|
369
|
+
error_code: Error code
|
|
370
|
+
error_message: Error message
|
|
371
|
+
duration_ms: Duration in milliseconds
|
|
372
|
+
"""
|
|
373
|
+
if log_entry is None:
|
|
374
|
+
return
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
from ..models import CentrifugoLog
|
|
378
|
+
|
|
379
|
+
CentrifugoLog.objects.mark_failed(
|
|
380
|
+
log_instance=log_entry,
|
|
381
|
+
error_code=error_code,
|
|
382
|
+
error_message=error_message,
|
|
383
|
+
duration_ms=duration_ms,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
logger.error(
|
|
387
|
+
f"Centrifugo publish failed: {log_entry.message_id}",
|
|
388
|
+
extra={
|
|
389
|
+
"message_id": log_entry.message_id,
|
|
390
|
+
"channel": log_entry.channel,
|
|
391
|
+
"error_code": error_code,
|
|
392
|
+
"error_message": error_message,
|
|
393
|
+
"duration_ms": duration_ms,
|
|
394
|
+
},
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
except Exception as e:
|
|
398
|
+
logger.error(
|
|
399
|
+
f"Failed to mark Centrifugo log as failed: {e}",
|
|
400
|
+
extra={"message_id": getattr(log_entry, "message_id", "unknown")},
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
@staticmethod
|
|
404
|
+
async def mark_timeout_async(
|
|
405
|
+
log_entry: Any,
|
|
406
|
+
acks_received: int = 0,
|
|
407
|
+
duration_ms: int | None = None,
|
|
408
|
+
) -> None:
|
|
409
|
+
"""
|
|
410
|
+
Mark publish operation as timed out (async version).
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
log_entry: CentrifugoLog instance
|
|
414
|
+
acks_received: Number of ACKs received before timeout
|
|
415
|
+
duration_ms: Duration in milliseconds
|
|
416
|
+
"""
|
|
417
|
+
if log_entry is None:
|
|
418
|
+
return
|
|
419
|
+
|
|
420
|
+
try:
|
|
421
|
+
from asgiref.sync import sync_to_async
|
|
422
|
+
from ..models import CentrifugoLog
|
|
423
|
+
|
|
424
|
+
await sync_to_async(CentrifugoLog.objects.mark_timeout)(
|
|
425
|
+
log_instance=log_entry,
|
|
426
|
+
acks_received=acks_received,
|
|
427
|
+
duration_ms=duration_ms,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
logger.warning(
|
|
431
|
+
f"Centrifugo publish timeout: {log_entry.message_id}",
|
|
432
|
+
extra={
|
|
433
|
+
"message_id": log_entry.message_id,
|
|
434
|
+
"channel": log_entry.channel,
|
|
435
|
+
"acks_received": acks_received,
|
|
436
|
+
"ack_timeout": log_entry.ack_timeout,
|
|
437
|
+
"duration_ms": duration_ms,
|
|
438
|
+
},
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
except Exception as e:
|
|
442
|
+
logger.error(
|
|
443
|
+
f"Failed to mark Centrifugo log as timeout: {e}",
|
|
444
|
+
extra={"message_id": getattr(log_entry, "message_id", "unknown")},
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
@staticmethod
|
|
448
|
+
def mark_timeout(
|
|
449
|
+
log_entry: Any,
|
|
450
|
+
acks_received: int = 0,
|
|
451
|
+
duration_ms: int | None = None,
|
|
452
|
+
) -> None:
|
|
453
|
+
"""
|
|
454
|
+
Mark publish operation as timed out (sync version).
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
log_entry: CentrifugoLog instance
|
|
458
|
+
acks_received: Number of ACKs received before timeout
|
|
459
|
+
duration_ms: Duration in milliseconds
|
|
460
|
+
"""
|
|
461
|
+
if log_entry is None:
|
|
462
|
+
return
|
|
463
|
+
|
|
464
|
+
try:
|
|
465
|
+
from ..models import CentrifugoLog
|
|
466
|
+
|
|
467
|
+
CentrifugoLog.objects.mark_timeout(
|
|
468
|
+
log_instance=log_entry,
|
|
469
|
+
acks_received=acks_received,
|
|
470
|
+
duration_ms=duration_ms,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
logger.warning(
|
|
474
|
+
f"Centrifugo publish timeout: {log_entry.message_id}",
|
|
475
|
+
extra={
|
|
476
|
+
"message_id": log_entry.message_id,
|
|
477
|
+
"channel": log_entry.channel,
|
|
478
|
+
"acks_received": acks_received,
|
|
479
|
+
"ack_timeout": log_entry.ack_timeout,
|
|
480
|
+
"duration_ms": duration_ms,
|
|
481
|
+
},
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
except Exception as e:
|
|
485
|
+
logger.error(
|
|
486
|
+
f"Failed to mark Centrifugo log as timeout: {e}",
|
|
487
|
+
extra={"message_id": getattr(log_entry, "message_id", "unknown")},
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
class CentrifugoLogContext:
|
|
492
|
+
"""
|
|
493
|
+
Context manager for automatic Centrifugo publish logging.
|
|
494
|
+
|
|
495
|
+
Mirrors RPCLogContext interface for migration compatibility.
|
|
496
|
+
|
|
497
|
+
Usage:
|
|
498
|
+
>>> with CentrifugoLogContext(
|
|
499
|
+
... message_id="abc123",
|
|
500
|
+
... channel="user#456",
|
|
501
|
+
... data={"title": "Hello"},
|
|
502
|
+
... wait_for_ack=True
|
|
503
|
+
... ) as log_ctx:
|
|
504
|
+
... result = await client.publish_with_ack(...)
|
|
505
|
+
... log_ctx.set_result(result.acks_received)
|
|
506
|
+
"""
|
|
507
|
+
|
|
508
|
+
def __init__(
|
|
509
|
+
self,
|
|
510
|
+
message_id: str,
|
|
511
|
+
channel: str,
|
|
512
|
+
data: dict,
|
|
513
|
+
wait_for_ack: bool = False,
|
|
514
|
+
ack_timeout: int | None = None,
|
|
515
|
+
acks_expected: int | None = None,
|
|
516
|
+
is_notification: bool = True,
|
|
517
|
+
user: Any = None,
|
|
518
|
+
caller_ip: str | None = None,
|
|
519
|
+
user_agent: str | None = None,
|
|
520
|
+
):
|
|
521
|
+
"""
|
|
522
|
+
Initialize logging context.
|
|
523
|
+
|
|
524
|
+
Args:
|
|
525
|
+
message_id: Unique message identifier
|
|
526
|
+
channel: Centrifugo channel
|
|
527
|
+
data: Published data
|
|
528
|
+
wait_for_ack: Whether this publish waits for ACK
|
|
529
|
+
ack_timeout: ACK timeout in seconds
|
|
530
|
+
acks_expected: Expected number of ACKs
|
|
531
|
+
is_notification: Whether this is a notification
|
|
532
|
+
user: Django User instance
|
|
533
|
+
caller_ip: IP address of caller
|
|
534
|
+
user_agent: User agent of caller
|
|
535
|
+
"""
|
|
536
|
+
self.message_id = message_id
|
|
537
|
+
self.channel = channel
|
|
538
|
+
self.data = data
|
|
539
|
+
self.wait_for_ack = wait_for_ack
|
|
540
|
+
self.ack_timeout = ack_timeout
|
|
541
|
+
self.acks_expected = acks_expected
|
|
542
|
+
self.is_notification = is_notification
|
|
543
|
+
self.user = user
|
|
544
|
+
self.caller_ip = caller_ip
|
|
545
|
+
self.user_agent = user_agent
|
|
546
|
+
|
|
547
|
+
self.log_entry: Any = None
|
|
548
|
+
self.start_time: float = 0
|
|
549
|
+
self._result_set: bool = False
|
|
550
|
+
|
|
551
|
+
def __enter__(self):
|
|
552
|
+
"""Enter context - create log entry."""
|
|
553
|
+
self.start_time = time.time()
|
|
554
|
+
|
|
555
|
+
self.log_entry = CentrifugoLogger.create_log(
|
|
556
|
+
message_id=self.message_id,
|
|
557
|
+
channel=self.channel,
|
|
558
|
+
data=self.data,
|
|
559
|
+
wait_for_ack=self.wait_for_ack,
|
|
560
|
+
ack_timeout=self.ack_timeout,
|
|
561
|
+
acks_expected=self.acks_expected,
|
|
562
|
+
is_notification=self.is_notification,
|
|
563
|
+
user=self.user,
|
|
564
|
+
caller_ip=self.caller_ip,
|
|
565
|
+
user_agent=self.user_agent,
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
return self
|
|
569
|
+
|
|
570
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
571
|
+
"""Exit context - mark result based on outcome."""
|
|
572
|
+
duration_ms = int((time.time() - self.start_time) * 1000)
|
|
573
|
+
|
|
574
|
+
# If result was explicitly set, don't override
|
|
575
|
+
if self._result_set:
|
|
576
|
+
return False
|
|
577
|
+
|
|
578
|
+
# If exception occurred, mark as failed
|
|
579
|
+
if exc_type is not None:
|
|
580
|
+
error_code = exc_type.__name__ if exc_type else "unknown"
|
|
581
|
+
error_message = str(exc_val) if exc_val else "Unknown error"
|
|
582
|
+
CentrifugoLogger.mark_failed(
|
|
583
|
+
self.log_entry,
|
|
584
|
+
error_code=error_code,
|
|
585
|
+
error_message=error_message,
|
|
586
|
+
duration_ms=duration_ms,
|
|
587
|
+
)
|
|
588
|
+
return False
|
|
589
|
+
|
|
590
|
+
# Otherwise mark as success with 0 ACKs (fire-and-forget)
|
|
591
|
+
if not self.wait_for_ack:
|
|
592
|
+
CentrifugoLogger.mark_success(
|
|
593
|
+
self.log_entry,
|
|
594
|
+
acks_received=0,
|
|
595
|
+
duration_ms=duration_ms,
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
return False
|
|
599
|
+
|
|
600
|
+
def set_result(self, acks_received: int) -> None:
|
|
601
|
+
"""
|
|
602
|
+
Set successful result.
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
acks_received: Number of ACKs received
|
|
606
|
+
"""
|
|
607
|
+
duration_ms = int((time.time() - self.start_time) * 1000)
|
|
608
|
+
|
|
609
|
+
CentrifugoLogger.mark_success(
|
|
610
|
+
self.log_entry,
|
|
611
|
+
acks_received=acks_received,
|
|
612
|
+
duration_ms=duration_ms,
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
self._result_set = True
|
|
616
|
+
|
|
617
|
+
def set_timeout(self, acks_received: int = 0) -> None:
|
|
618
|
+
"""
|
|
619
|
+
Set timeout result.
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
acks_received: Number of ACKs received before timeout
|
|
623
|
+
"""
|
|
624
|
+
duration_ms = int((time.time() - self.start_time) * 1000)
|
|
625
|
+
|
|
626
|
+
CentrifugoLogger.mark_timeout(
|
|
627
|
+
self.log_entry,
|
|
628
|
+
acks_received=acks_received,
|
|
629
|
+
duration_ms=duration_ms,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
self._result_set = True
|
|
633
|
+
|
|
634
|
+
def set_partial(self, acks_received: int, acks_expected: int) -> None:
|
|
635
|
+
"""
|
|
636
|
+
Set partial delivery result.
|
|
637
|
+
|
|
638
|
+
Args:
|
|
639
|
+
acks_received: Number of ACKs received
|
|
640
|
+
acks_expected: Number of ACKs expected
|
|
641
|
+
"""
|
|
642
|
+
duration_ms = int((time.time() - self.start_time) * 1000)
|
|
643
|
+
|
|
644
|
+
CentrifugoLogger.mark_partial(
|
|
645
|
+
self.log_entry,
|
|
646
|
+
acks_received=acks_received,
|
|
647
|
+
acks_expected=acks_expected,
|
|
648
|
+
duration_ms=duration_ms,
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
self._result_set = True
|
|
652
|
+
|
|
653
|
+
def set_error(self, error_code: str, error_message: str) -> None:
|
|
654
|
+
"""
|
|
655
|
+
Set error result.
|
|
656
|
+
|
|
657
|
+
Args:
|
|
658
|
+
error_code: Error code
|
|
659
|
+
error_message: Error message
|
|
660
|
+
"""
|
|
661
|
+
duration_ms = int((time.time() - self.start_time) * 1000)
|
|
662
|
+
|
|
663
|
+
CentrifugoLogger.mark_failed(
|
|
664
|
+
self.log_entry,
|
|
665
|
+
error_code=error_code,
|
|
666
|
+
error_message=error_message,
|
|
667
|
+
duration_ms=duration_ms,
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
self._result_set = True
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
__all__ = [
|
|
674
|
+
"CentrifugoLogger",
|
|
675
|
+
"CentrifugoLogContext",
|
|
676
|
+
"logger",
|
|
677
|
+
]
|