1+ import logging
2+ import os
3+ import json
4+ import time
5+ from datetime import datetime
6+ from pathlib import Path
7+
8+ logger = logging .getLogger (__name__ )
9+
10+ class MessageDebugger :
11+ """Utility to capture and log raw device messages for debugging."""
12+
13+ def __init__ (self , debug_dir = "debug_logs" ):
14+ self .debug_dir = debug_dir
15+ self .ensure_debug_dir ()
16+
17+ def ensure_debug_dir (self ):
18+ """Ensure the debug directory exists."""
19+ os .makedirs (self .debug_dir , exist_ok = True )
20+
21+ def capture_device_message (self , device_id , message ):
22+ """Capture a raw device message to a debug file.
23+ Messages are appended to a single file per device."""
24+ try :
25+ filename = f"{ self .debug_dir } /device_{ device_id } _log.json"
26+
27+ # Create entry for this message
28+ entry = {
29+ "timestamp" : time .time (),
30+ "datetime" : datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" ),
31+ "device_id" : device_id ,
32+ "message" : message
33+ }
34+
35+ # Read existing data if file exists
36+ messages = []
37+ if os .path .exists (filename ):
38+ try :
39+ with open (filename , 'r' ) as f :
40+ messages = json .load (f )
41+ if not isinstance (messages , list ):
42+ # Convert old format to new format
43+ messages = [messages ]
44+ except Exception as e :
45+ logger .error (f"Error reading existing log file { filename } : { str (e )} " )
46+ messages = []
47+
48+ # Append the new message
49+ messages .append (entry )
50+
51+ # Write all messages back to the file
52+ with open (filename , 'w' ) as f :
53+ json .dump (messages , f , indent = 2 )
54+
55+ logger .info (f"Appended device message to { filename } " )
56+ return filename
57+ except Exception as e :
58+ logger .error (f"Failed to capture device message: { str (e )} " )
59+ return None
60+
61+ def analyze_device_messages (self , device_id = None ):
62+ """Analyze captured messages to find patterns and issues."""
63+ results = {
64+ "total_messages" : 0 ,
65+ "messages_by_type" : {},
66+ "messages_without_type" : 0 ,
67+ "messages_with_gps" : 0 ,
68+ "example_gps_messages" : []
69+ }
70+
71+ path = Path (self .debug_dir )
72+
73+ # Handle both old format (multiple files) and new format (single file per device)
74+ if device_id :
75+ # First check for single log file
76+ log_file = path / f"device_{ device_id } _log.json"
77+ if log_file .exists ():
78+ self ._process_log_file (log_file , results )
79+ else :
80+ # Fall back to old format
81+ for file in path .glob (f"device_{ device_id } _*.json" ):
82+ self ._process_old_format_file (file , results )
83+ else :
84+ # Check for all device log files
85+ for file in path .glob ("device_*_log.json" ):
86+ self ._process_log_file (file , results )
87+
88+ # Also check old format files
89+ for file in path .glob ("device_*_20*.json" ): # Files with timestamp in name
90+ self ._process_old_format_file (file , results )
91+
92+ return results
93+
94+ def _process_log_file (self , file_path , results ):
95+ """Process a log file containing multiple messages."""
96+ try :
97+ with open (file_path , 'r' ) as f :
98+ messages = json .load (f )
99+
100+ if not isinstance (messages , list ):
101+ messages = [messages ] # Handle case of single message
102+
103+ for entry in messages :
104+ message = entry .get ("message" , {})
105+ results ["total_messages" ] += 1
106+
107+ # Analyze message type
108+ msg_type = message .get ("type" )
109+ if msg_type :
110+ results ["messages_by_type" ][msg_type ] = results ["messages_by_type" ].get (msg_type , 0 ) + 1
111+ else :
112+ results ["messages_without_type" ] += 1
113+
114+ # Check for GPS data
115+ self ._check_for_gps (message , results )
116+
117+ except Exception as e :
118+ logger .error (f"Error analyzing file { file_path } : { str (e )} " )
119+
120+ def _process_old_format_file (self , file_path , results ):
121+ """Process a file in the old format (single message per file)."""
122+ try :
123+ with open (file_path , 'r' ) as f :
124+ data = json .load (f )
125+
126+ message = data .get ("message" , {})
127+ results ["total_messages" ] += 1
128+
129+ # Analyze message type
130+ msg_type = message .get ("type" )
131+ if msg_type :
132+ results ["messages_by_type" ][msg_type ] = results ["messages_by_type" ].get (msg_type , 0 ) + 1
133+ else :
134+ results ["messages_without_type" ] += 1
135+
136+ # Check for GPS data
137+ self ._check_for_gps (message , results )
138+
139+ except Exception as e :
140+ logger .error (f"Error analyzing file { file_path } : { str (e )} " )
141+
142+ def _check_for_gps (self , message , results ):
143+ """Check if a message contains GPS data."""
144+ has_gps = False
145+
146+ # Option 1: data.gps structure
147+ if "data" in message and isinstance (message ["data" ], dict ) and "gps" in message ["data" ]:
148+ has_gps = True
149+
150+ # Option 2: direct gps object
151+ elif "gps" in message and isinstance (message ["gps" ], dict ):
152+ has_gps = True
153+
154+ # Option 3: latitude/longitude directly in message
155+ elif "latitude" in message and "longitude" in message :
156+ has_gps = True
157+
158+ if has_gps :
159+ results ["messages_with_gps" ] += 1
160+ # Store a few examples for analysis
161+ if len (results ["example_gps_messages" ]) < 3 :
162+ results ["example_gps_messages" ].append (message )
163+
164+
165+ # Singleton instance for global use
166+ message_debugger = MessageDebugger ()
0 commit comments