Fix: Websocket communication misunderstanding error

This commit is contained in:
ziesorx 2025-09-13 00:19:41 +07:00
parent 9967bff6dc
commit 42a8325faf
8 changed files with 1109 additions and 63 deletions

View file

@ -6,4 +6,25 @@ This module contains:
- Input validation functions
- Logging configuration and utilities
- Common helper functions
"""
- Enhanced RX/TX logging for debugging
- HTTP request logging for MPTA downloads
"""
# Import key logging utilities
from .logging_utils import (
get_websocket_logger,
get_http_logger,
create_correlation_logger,
WebSocketRXTXLogger,
HTTPRequestLogger,
CorrelatedLogger
)
__all__ = [
"get_websocket_logger",
"get_http_logger",
"create_correlation_logger",
"WebSocketRXTXLogger",
"HTTPRequestLogger",
"CorrelatedLogger"
]

View file

@ -0,0 +1,358 @@
"""
Logging utilities module.
This module provides structured logging utilities with correlation IDs,
timestamps, and enhanced formatting for debugging CMS backend communication.
"""
import logging
import json
import uuid
import time
from typing import Dict, Any, Optional
from datetime import datetime
# Setup loggers
logger = logging.getLogger(__name__)
class CorrelatedLogger:
"""
Logger with correlation ID support for tracking message flows.
This class provides structured logging with correlation IDs to help
track requests/responses and debug communication between CMS backend
and the worker.
"""
def __init__(self, logger_name: str = "detector_worker.correlated"):
"""
Initialize the correlated logger.
Args:
logger_name: Name for the logger
"""
self.logger = logging.getLogger(logger_name)
self.correlation_id: Optional[str] = None
def set_correlation_id(self, correlation_id: Optional[str] = None) -> str:
"""
Set or generate a correlation ID.
Args:
correlation_id: Specific correlation ID or None to generate
Returns:
The correlation ID that was set
"""
if correlation_id is None:
correlation_id = str(uuid.uuid4())[:8] # Short UUID for readability
self.correlation_id = correlation_id
return correlation_id
def clear_correlation_id(self) -> None:
"""Clear the current correlation ID."""
self.correlation_id = None
def _format_message(self, message: str, extra_data: Optional[Dict[str, Any]] = None) -> str:
"""
Format a message with correlation ID and timestamp.
Args:
message: Base message
extra_data: Additional data to include
Returns:
Formatted message with correlation info
"""
timestamp = datetime.utcnow().strftime("%H:%M:%S.%f")[:-3] # Include milliseconds
parts = [f"[{timestamp}]"]
if self.correlation_id:
parts.append(f"[{self.correlation_id}]")
parts.append(message)
if extra_data:
# Format extra data as compact JSON
try:
extra_json = json.dumps(extra_data, separators=(',', ':'))
parts.append(f"| {extra_json}")
except (TypeError, ValueError):
parts.append(f"| {str(extra_data)}")
return " ".join(parts)
def info(self, message: str, extra_data: Optional[Dict[str, Any]] = None) -> None:
"""Log an info message with correlation."""
formatted = self._format_message(message, extra_data)
self.logger.info(formatted)
def debug(self, message: str, extra_data: Optional[Dict[str, Any]] = None) -> None:
"""Log a debug message with correlation."""
formatted = self._format_message(message, extra_data)
self.logger.debug(formatted)
def warning(self, message: str, extra_data: Optional[Dict[str, Any]] = None) -> None:
"""Log a warning message with correlation."""
formatted = self._format_message(message, extra_data)
self.logger.warning(formatted)
def error(self, message: str, extra_data: Optional[Dict[str, Any]] = None) -> None:
"""Log an error message with correlation."""
formatted = self._format_message(message, extra_data)
self.logger.error(formatted)
class WebSocketRXTXLogger:
"""
Specialized logger for WebSocket RX/TX communication with CMS backend.
This logger provides enhanced debugging for WebSocket messages with
payload inspection, message types, and correlation tracking.
"""
def __init__(self):
"""Initialize the WebSocket RX/TX logger."""
self.logger = logging.getLogger("websocket.rxtx")
self.correlation_logger = CorrelatedLogger("websocket.rxtx.correlated")
def log_rx(self, message_data: str, correlation_id: Optional[str] = None) -> Optional[str]:
"""
Log incoming WebSocket message (RX).
Args:
message_data: Raw message data received
correlation_id: Optional correlation ID for tracking
Returns:
Generated or provided correlation ID
"""
if correlation_id:
self.correlation_logger.set_correlation_id(correlation_id)
else:
correlation_id = self.correlation_logger.set_correlation_id()
# Basic RX logging
self.logger.info(f"RX <- {message_data}")
# Enhanced correlation logging with payload analysis
try:
parsed = json.loads(message_data)
message_type = parsed.get("type", "unknown")
extra_data = {
"direction": "RX",
"message_type": message_type,
"size_bytes": len(message_data)
}
# Add specific payload info for important message types
if message_type == "setSubscriptionList":
subscriptions = parsed.get("subscriptions", [])
extra_data["subscription_count"] = len(subscriptions)
elif message_type in ["subscribe", "unsubscribe"]:
payload = parsed.get("payload", {})
extra_data["subscription_id"] = payload.get("subscriptionIdentifier")
extra_data["model_id"] = payload.get("modelId")
extra_data["model_url"] = payload.get("modelUrl", "")[:50] + "..." if len(payload.get("modelUrl", "")) > 50 else payload.get("modelUrl")
self.correlation_logger.info(f"MESSAGE RECEIVED: {message_type}", extra_data)
except (json.JSONDecodeError, KeyError) as e:
self.correlation_logger.warning(f"Failed to parse RX message: {e}")
return correlation_id
def log_tx(self, message_data: Dict[str, Any], correlation_id: Optional[str] = None) -> None:
"""
Log outgoing WebSocket message (TX).
Args:
message_data: Message data being sent
correlation_id: Optional correlation ID for tracking
"""
if correlation_id:
self.correlation_logger.set_correlation_id(correlation_id)
# Convert to JSON for logging
message_json = json.dumps(message_data, separators=(',', ':'))
# Basic TX logging
self.logger.info(f"TX -> {message_json}")
# Enhanced correlation logging
message_type = message_data.get("type", "unknown")
extra_data = {
"direction": "TX",
"message_type": message_type,
"size_bytes": len(message_json)
}
# Add specific info for important message types
if message_type == "imageDetection":
extra_data["subscription_id"] = message_data.get("subscriptionIdentifier")
extra_data["session_id"] = message_data.get("sessionId")
detection_data = message_data.get("data", {}).get("detection")
extra_data["has_detection"] = detection_data is not None
elif message_type == "stateReport":
extra_data["camera_count"] = len(message_data.get("cameraConnections", []))
extra_data["cpu_usage"] = message_data.get("cpuUsage")
extra_data["memory_usage"] = message_data.get("memoryUsage")
self.correlation_logger.info(f"MESSAGE SENT: {message_type}", extra_data)
class HTTPRequestLogger:
"""
Specialized logger for HTTP requests (MPTA downloads, etc.).
This logger tracks HTTP requests and responses for debugging
download issues and CMS backend communication.
"""
def __init__(self):
"""Initialize the HTTP request logger."""
self.logger = logging.getLogger("http.requests")
self.correlation_logger = CorrelatedLogger("http.requests.correlated")
def log_request_start(
self,
method: str,
url: str,
headers: Optional[Dict[str, str]] = None,
correlation_id: Optional[str] = None
) -> str:
"""
Log the start of an HTTP request.
Args:
method: HTTP method (GET, POST, etc.)
url: Request URL
headers: Request headers
correlation_id: Optional correlation ID for tracking
Returns:
Generated or provided correlation ID
"""
if correlation_id:
self.correlation_logger.set_correlation_id(correlation_id)
else:
correlation_id = self.correlation_logger.set_correlation_id()
extra_data = {
"method": method,
"url": url,
"start_time": time.time()
}
if headers:
# Log important headers only
important_headers = ["content-length", "content-type", "authorization"]
filtered_headers = {
k: v for k, v in headers.items()
if k.lower() in important_headers
}
if filtered_headers:
extra_data["headers"] = filtered_headers
self.correlation_logger.info(f"HTTP REQUEST START: {method} {url}", extra_data)
return correlation_id
def log_request_end(
self,
status_code: int,
response_size: Optional[int] = None,
duration_ms: Optional[float] = None,
correlation_id: Optional[str] = None
) -> None:
"""
Log the end of an HTTP request.
Args:
status_code: HTTP response status code
response_size: Response size in bytes
duration_ms: Request duration in milliseconds
correlation_id: Correlation ID for tracking
"""
if correlation_id:
self.correlation_logger.set_correlation_id(correlation_id)
extra_data = {
"status_code": status_code,
"success": 200 <= status_code < 300
}
if response_size is not None:
extra_data["response_size_bytes"] = response_size
extra_data["response_size_mb"] = round(response_size / (1024 * 1024), 2)
if duration_ms is not None:
extra_data["duration_ms"] = round(duration_ms, 2)
level_func = self.correlation_logger.info if extra_data["success"] else self.correlation_logger.error
level_func(f"HTTP REQUEST END: {status_code}", extra_data)
def log_download_progress(
self,
bytes_downloaded: int,
total_bytes: Optional[int] = None,
percent_complete: Optional[float] = None,
correlation_id: Optional[str] = None
) -> None:
"""
Log download progress for large files.
Args:
bytes_downloaded: Number of bytes downloaded so far
total_bytes: Total file size in bytes
percent_complete: Percentage complete (0-100)
correlation_id: Correlation ID for tracking
"""
if correlation_id:
self.correlation_logger.set_correlation_id(correlation_id)
extra_data = {
"bytes_downloaded": bytes_downloaded,
"mb_downloaded": round(bytes_downloaded / (1024 * 1024), 2)
}
if total_bytes:
extra_data["total_bytes"] = total_bytes
extra_data["total_mb"] = round(total_bytes / (1024 * 1024), 2)
if percent_complete:
extra_data["percent_complete"] = round(percent_complete, 1)
self.correlation_logger.debug(f"DOWNLOAD PROGRESS", extra_data)
# Global logger instances
websocket_rxtx_logger = WebSocketRXTXLogger()
http_request_logger = HTTPRequestLogger()
# Convenience functions
def get_websocket_logger() -> WebSocketRXTXLogger:
"""Get the global WebSocket RX/TX logger."""
return websocket_rxtx_logger
def get_http_logger() -> HTTPRequestLogger:
"""Get the global HTTP request logger."""
return http_request_logger
def create_correlation_logger(name: str) -> CorrelatedLogger:
"""
Create a new correlated logger with given name.
Args:
name: Logger name
Returns:
New CorrelatedLogger instance
"""
return CorrelatedLogger(name)