claude-mpm 4.2.13__py3-none-any.whl → 4.2.15__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.
Files changed (39) hide show
  1. claude_mpm/cli/commands/analyze_code.py +0 -1
  2. claude_mpm/cli/commands/dashboard.py +0 -1
  3. claude_mpm/cli/commands/monitor.py +0 -1
  4. claude_mpm/core/constants.py +65 -0
  5. claude_mpm/core/error_handler.py +625 -0
  6. claude_mpm/core/file_utils.py +770 -0
  7. claude_mpm/core/logging_utils.py +502 -0
  8. claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
  9. claude_mpm/dashboard/static/dist/components/file-viewer.js +1 -1
  10. claude_mpm/dashboard/static/dist/dashboard.js +1 -1
  11. claude_mpm/dashboard/static/dist/socket-client.js +1 -1
  12. claude_mpm/dashboard/static/js/components/code-simple.js +44 -1
  13. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +353 -0
  14. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +235 -0
  15. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +409 -0
  16. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +435 -0
  17. claude_mpm/dashboard/static/js/components/code-tree.js +29 -5
  18. claude_mpm/dashboard/static/js/components/file-viewer.js +69 -27
  19. claude_mpm/dashboard/static/js/components/session-manager.js +7 -7
  20. claude_mpm/dashboard/static/js/components/working-directory.js +18 -9
  21. claude_mpm/dashboard/static/js/dashboard.js +91 -9
  22. claude_mpm/dashboard/static/js/shared/dom-helpers.js +396 -0
  23. claude_mpm/dashboard/static/js/shared/event-bus.js +330 -0
  24. claude_mpm/dashboard/static/js/shared/logger.js +385 -0
  25. claude_mpm/dashboard/static/js/shared/tooltip-service.js +253 -0
  26. claude_mpm/dashboard/static/js/socket-client.js +18 -0
  27. claude_mpm/dashboard/templates/index.html +22 -9
  28. claude_mpm/services/cli/unified_dashboard_manager.py +2 -1
  29. claude_mpm/services/monitor/handlers/__init__.py +2 -1
  30. claude_mpm/services/monitor/handlers/file.py +263 -0
  31. claude_mpm/services/monitor/handlers/hooks.py +25 -1
  32. claude_mpm/services/monitor/server.py +111 -1
  33. claude_mpm/services/socketio/handlers/file.py +40 -5
  34. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/METADATA +1 -1
  35. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/RECORD +39 -27
  36. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/WHEEL +0 -0
  37. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/entry_points.txt +0 -0
  38. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/licenses/LICENSE +0 -0
  39. {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,502 @@
1
+ """Centralized logging utilities for Claude MPM.
2
+
3
+ This module provides standardized logging initialization and configuration
4
+ to replace duplicate logger initialization code across 76+ files.
5
+ """
6
+
7
+ import logging
8
+ import logging.handlers
9
+ import os
10
+ import sys
11
+ from functools import lru_cache
12
+ from pathlib import Path
13
+ from typing import Any, Dict, Optional
14
+
15
+ from claude_mpm.core.constants import Defaults
16
+
17
+ # ==============================================================================
18
+ # LOGGING CONFIGURATION
19
+ # ==============================================================================
20
+
21
+
22
+ class LoggingConfig:
23
+ """Logging configuration settings."""
24
+
25
+ # Log levels
26
+ LEVELS = {
27
+ "DEBUG": logging.DEBUG,
28
+ "INFO": logging.INFO,
29
+ "WARNING": logging.WARNING,
30
+ "ERROR": logging.ERROR,
31
+ "CRITICAL": logging.CRITICAL,
32
+ }
33
+
34
+ # Default formats
35
+ DEFAULT_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
36
+ SIMPLE_FORMAT = "%(levelname)s: %(message)s"
37
+ DETAILED_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s"
38
+ JSON_FORMAT = '{"time": "%(asctime)s", "name": "%(name)s", "level": "%(levelname)s", "message": "%(message)s"}'
39
+
40
+ # Date formats
41
+ DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
42
+ ISO_DATE_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
43
+
44
+ # File settings
45
+ MAX_BYTES = 10 * 1024 * 1024 # 10MB
46
+ BACKUP_COUNT = 5
47
+
48
+ # Component-specific log names
49
+ COMPONENT_NAMES = {
50
+ "agent": "claude_mpm.agent",
51
+ "service": "claude_mpm.service",
52
+ "core": "claude_mpm.core",
53
+ "cli": "claude_mpm.cli",
54
+ "hooks": "claude_mpm.hooks",
55
+ "monitor": "claude_mpm.monitor",
56
+ "socketio": "claude_mpm.socketio",
57
+ "memory": "claude_mpm.memory",
58
+ "config": "claude_mpm.config",
59
+ "utils": "claude_mpm.utils",
60
+ }
61
+
62
+
63
+ # ==============================================================================
64
+ # LOGGER FACTORY
65
+ # ==============================================================================
66
+
67
+
68
+ class LoggerFactory:
69
+ """Factory for creating standardized loggers."""
70
+
71
+ _initialized = False
72
+ _log_dir: Optional[Path] = None
73
+ _log_level: str = Defaults.DEFAULT_LOG_LEVEL
74
+ _handlers: Dict[str, logging.Handler] = {}
75
+
76
+ @classmethod
77
+ def initialize(
78
+ cls,
79
+ log_level: str = None,
80
+ log_dir: Optional[Path] = None,
81
+ log_to_file: bool = False,
82
+ log_format: str = None,
83
+ date_format: str = None,
84
+ ) -> None:
85
+ """Initialize the logging system globally.
86
+
87
+ Args:
88
+ log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
89
+ log_dir: Directory for log files
90
+ log_to_file: Whether to log to files
91
+ log_format: Custom log format string
92
+ date_format: Custom date format string
93
+ """
94
+ if cls._initialized:
95
+ return
96
+
97
+ cls._log_level = log_level or os.environ.get(
98
+ "CLAUDE_MPM_LOG_LEVEL", Defaults.DEFAULT_LOG_LEVEL
99
+ )
100
+ cls._log_dir = log_dir
101
+
102
+ # Set up root logger
103
+ root_logger = logging.getLogger()
104
+ root_logger.setLevel(LoggingConfig.LEVELS.get(cls._log_level, logging.INFO))
105
+
106
+ # Remove existing handlers
107
+ root_logger.handlers = []
108
+
109
+ # Console handler
110
+ console_handler = logging.StreamHandler(sys.stdout)
111
+ console_handler.setLevel(LoggingConfig.LEVELS.get(cls._log_level, logging.INFO))
112
+ console_formatter = logging.Formatter(
113
+ log_format or LoggingConfig.DEFAULT_FORMAT,
114
+ date_format or LoggingConfig.DATE_FORMAT,
115
+ )
116
+ console_handler.setFormatter(console_formatter)
117
+ root_logger.addHandler(console_handler)
118
+ cls._handlers["console"] = console_handler
119
+
120
+ # File handler (optional)
121
+ if log_to_file and cls._log_dir:
122
+ cls._setup_file_handler(log_format, date_format)
123
+
124
+ cls._initialized = True
125
+
126
+ @classmethod
127
+ def _setup_file_handler(
128
+ cls,
129
+ log_format: Optional[str] = None,
130
+ date_format: Optional[str] = None,
131
+ ) -> None:
132
+ """Set up file logging handler."""
133
+ if not cls._log_dir:
134
+ return
135
+
136
+ # Ensure log directory exists
137
+ cls._log_dir.mkdir(parents=True, exist_ok=True)
138
+
139
+ # Create rotating file handler
140
+ log_file = cls._log_dir / "claude_mpm.log"
141
+ file_handler = logging.handlers.RotatingFileHandler(
142
+ log_file,
143
+ maxBytes=LoggingConfig.MAX_BYTES,
144
+ backupCount=LoggingConfig.BACKUP_COUNT,
145
+ )
146
+ file_handler.setLevel(LoggingConfig.LEVELS.get(cls._log_level, logging.INFO))
147
+
148
+ file_formatter = logging.Formatter(
149
+ log_format or LoggingConfig.DETAILED_FORMAT,
150
+ date_format or LoggingConfig.DATE_FORMAT,
151
+ )
152
+ file_handler.setFormatter(file_formatter)
153
+
154
+ logging.getLogger().addHandler(file_handler)
155
+ cls._handlers["file"] = file_handler
156
+
157
+ @classmethod
158
+ def get_logger(
159
+ cls,
160
+ name: str,
161
+ component: Optional[str] = None,
162
+ level: Optional[str] = None,
163
+ ) -> logging.Logger:
164
+ """Get a standardized logger instance.
165
+
166
+ Args:
167
+ name: Logger name (typically __name__)
168
+ component: Optional component category for namespacing
169
+ level: Optional specific log level for this logger
170
+
171
+ Returns:
172
+ Configured logger instance
173
+ """
174
+ # Initialize if needed
175
+ if not cls._initialized:
176
+ cls.initialize()
177
+
178
+ # Apply component namespace if specified
179
+ if component and component in LoggingConfig.COMPONENT_NAMES:
180
+ logger_name = LoggingConfig.COMPONENT_NAMES[component]
181
+ if not name.startswith(logger_name):
182
+ logger_name = f"{logger_name}.{name.split('.')[-1]}"
183
+ else:
184
+ logger_name = name
185
+
186
+ logger = logging.getLogger(logger_name)
187
+
188
+ # Set specific level if requested
189
+ if level and level in LoggingConfig.LEVELS:
190
+ logger.setLevel(LoggingConfig.LEVELS[level])
191
+
192
+ return logger
193
+
194
+ @classmethod
195
+ def set_level(cls, level: str) -> None:
196
+ """Change the global logging level.
197
+
198
+ Args:
199
+ level: New logging level
200
+ """
201
+ if level not in LoggingConfig.LEVELS:
202
+ return
203
+
204
+ cls._log_level = level
205
+ log_level = LoggingConfig.LEVELS[level]
206
+
207
+ # Update root logger
208
+ logging.getLogger().setLevel(log_level)
209
+
210
+ # Update all handlers
211
+ for handler in cls._handlers.values():
212
+ handler.setLevel(log_level)
213
+
214
+ @classmethod
215
+ def add_handler(cls, name: str, handler: logging.Handler) -> None:
216
+ """Add a custom handler to the logging system.
217
+
218
+ Args:
219
+ name: Handler identifier
220
+ handler: The handler to add
221
+ """
222
+ logging.getLogger().addHandler(handler)
223
+ cls._handlers[name] = handler
224
+
225
+ @classmethod
226
+ def remove_handler(cls, name: str) -> None:
227
+ """Remove a handler from the logging system.
228
+
229
+ Args:
230
+ name: Handler identifier to remove
231
+ """
232
+ if name in cls._handlers:
233
+ logging.getLogger().removeHandler(cls._handlers[name])
234
+ del cls._handlers[name]
235
+
236
+
237
+ # ==============================================================================
238
+ # CONVENIENCE FUNCTIONS
239
+ # ==============================================================================
240
+
241
+
242
+ @lru_cache(maxsize=128)
243
+ def get_logger(
244
+ name: str,
245
+ component: Optional[str] = None,
246
+ level: Optional[str] = None,
247
+ ) -> logging.Logger:
248
+ """Get a standardized logger instance (cached).
249
+
250
+ This is the primary function that should be used throughout the codebase
251
+ to get logger instances. It replaces the pattern:
252
+ import logging
253
+ logger = logging.getLogger(__name__)
254
+
255
+ With:
256
+ from claude_mpm.core.logging_utils import get_logger
257
+ logger = get_logger(__name__)
258
+
259
+ Args:
260
+ name: Logger name (typically __name__)
261
+ component: Optional component category for namespacing
262
+ level: Optional specific log level for this logger
263
+
264
+ Returns:
265
+ Configured logger instance
266
+ """
267
+ return LoggerFactory.get_logger(name, component, level)
268
+
269
+
270
+ def initialize_logging(
271
+ log_level: str = None,
272
+ log_dir: Optional[Path] = None,
273
+ log_to_file: bool = False,
274
+ log_format: str = None,
275
+ ) -> None:
276
+ """Initialize the logging system with standard configuration.
277
+
278
+ This should be called once at application startup.
279
+
280
+ Args:
281
+ log_level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
282
+ log_dir: Directory for log files
283
+ log_to_file: Whether to log to files
284
+ log_format: Custom log format string
285
+ """
286
+ LoggerFactory.initialize(
287
+ log_level=log_level,
288
+ log_dir=log_dir,
289
+ log_to_file=log_to_file,
290
+ log_format=log_format,
291
+ )
292
+
293
+
294
+ def set_log_level(level: str) -> None:
295
+ """Change the global logging level dynamically.
296
+
297
+ Args:
298
+ level: New logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
299
+ """
300
+ LoggerFactory.set_level(level)
301
+
302
+
303
+ def get_component_logger(component: str, name: str = None) -> logging.Logger:
304
+ """Get a logger for a specific component.
305
+
306
+ Args:
307
+ component: Component name (agent, service, core, etc.)
308
+ name: Optional specific name within component
309
+
310
+ Returns:
311
+ Component-specific logger
312
+ """
313
+ if name is None:
314
+ name = component
315
+ return get_logger(name, component=component)
316
+
317
+
318
+ # ==============================================================================
319
+ # STRUCTURED LOGGING
320
+ # ==============================================================================
321
+
322
+
323
+ class StructuredLogger:
324
+ """Wrapper for structured logging with context."""
325
+
326
+ def __init__(self, logger: logging.Logger):
327
+ """Initialize structured logger.
328
+
329
+ Args:
330
+ logger: Base logger instance
331
+ """
332
+ self.logger = logger
333
+ self.context: Dict[str, Any] = {}
334
+
335
+ def with_context(self, **kwargs) -> "StructuredLogger":
336
+ """Add context to all log messages.
337
+
338
+ Args:
339
+ **kwargs: Context key-value pairs
340
+
341
+ Returns:
342
+ Self for chaining
343
+ """
344
+ self.context.update(kwargs)
345
+ return self
346
+
347
+ def clear_context(self) -> None:
348
+ """Clear all context."""
349
+ self.context.clear()
350
+
351
+ def _format_message(self, message: str) -> str:
352
+ """Format message with context.
353
+
354
+ Args:
355
+ message: Base message
356
+
357
+ Returns:
358
+ Formatted message with context
359
+ """
360
+ if not self.context:
361
+ return message
362
+
363
+ context_str = " ".join(f"{k}={v}" for k, v in self.context.items())
364
+ return f"{message} [{context_str}]"
365
+
366
+ def debug(self, message: str, **kwargs) -> None:
367
+ """Log debug message with context."""
368
+ self.logger.debug(self._format_message(message), **kwargs)
369
+
370
+ def info(self, message: str, **kwargs) -> None:
371
+ """Log info message with context."""
372
+ self.logger.info(self._format_message(message), **kwargs)
373
+
374
+ def warning(self, message: str, **kwargs) -> None:
375
+ """Log warning message with context."""
376
+ self.logger.warning(self._format_message(message), **kwargs)
377
+
378
+ def error(self, message: str, **kwargs) -> None:
379
+ """Log error message with context."""
380
+ self.logger.error(self._format_message(message), **kwargs)
381
+
382
+ def critical(self, message: str, **kwargs) -> None:
383
+ """Log critical message with context."""
384
+ self.logger.critical(self._format_message(message), **kwargs)
385
+
386
+ def exception(self, message: str, **kwargs) -> None:
387
+ """Log exception with traceback and context."""
388
+ self.logger.exception(self._format_message(message), **kwargs)
389
+
390
+
391
+ def get_structured_logger(
392
+ name: str, component: Optional[str] = None, **context
393
+ ) -> StructuredLogger:
394
+ """Get a structured logger with initial context.
395
+
396
+ Args:
397
+ name: Logger name
398
+ component: Optional component category
399
+ **context: Initial context key-value pairs
400
+
401
+ Returns:
402
+ Structured logger instance
403
+ """
404
+ base_logger = get_logger(name, component)
405
+ structured = StructuredLogger(base_logger)
406
+ if context:
407
+ structured.with_context(**context)
408
+ return structured
409
+
410
+
411
+ # ==============================================================================
412
+ # PERFORMANCE LOGGING
413
+ # ==============================================================================
414
+
415
+
416
+ class PerformanceLogger:
417
+ """Logger for performance metrics and timing."""
418
+
419
+ def __init__(self, logger: logging.Logger):
420
+ """Initialize performance logger.
421
+
422
+ Args:
423
+ logger: Base logger instance
424
+ """
425
+ self.logger = logger
426
+ self._timers: Dict[str, float] = {}
427
+
428
+ def start_timer(self, operation: str) -> None:
429
+ """Start timing an operation.
430
+
431
+ Args:
432
+ operation: Operation identifier
433
+ """
434
+ import time
435
+
436
+ self._timers[operation] = time.time()
437
+
438
+ def end_timer(self, operation: str, log_level: str = "INFO") -> float:
439
+ """End timing and log the duration.
440
+
441
+ Args:
442
+ operation: Operation identifier
443
+ log_level: Level to log at
444
+
445
+ Returns:
446
+ Duration in seconds
447
+ """
448
+ import time
449
+
450
+ if operation not in self._timers:
451
+ return 0.0
452
+
453
+ duration = time.time() - self._timers[operation]
454
+ del self._timers[operation]
455
+
456
+ level = LoggingConfig.LEVELS.get(log_level, logging.INFO)
457
+ self.logger.log(
458
+ level, f"Operation '{operation}' completed in {duration:.3f} seconds"
459
+ )
460
+
461
+ return duration
462
+
463
+ def log_memory_usage(self, context: str = "") -> None:
464
+ """Log current memory usage.
465
+
466
+ Args:
467
+ context: Optional context string
468
+ """
469
+ import os
470
+
471
+ import psutil
472
+
473
+ process = psutil.Process(os.getpid())
474
+ memory_info = process.memory_info()
475
+ memory_mb = memory_info.rss / (1024 * 1024)
476
+
477
+ ctx = f" [{context}]" if context else ""
478
+ self.logger.info(f"Memory usage{ctx}: {memory_mb:.2f} MB")
479
+
480
+ def log_metrics(self, metrics: Dict[str, Any], context: str = "") -> None:
481
+ """Log performance metrics.
482
+
483
+ Args:
484
+ metrics: Dictionary of metric name to value
485
+ context: Optional context string
486
+ """
487
+ ctx = f" [{context}]" if context else ""
488
+ metrics_str = ", ".join(f"{k}={v}" for k, v in metrics.items())
489
+ self.logger.info(f"Performance metrics{ctx}: {metrics_str}")
490
+
491
+
492
+ def get_performance_logger(name: str) -> PerformanceLogger:
493
+ """Get a performance logger instance.
494
+
495
+ Args:
496
+ name: Logger name
497
+
498
+ Returns:
499
+ Performance logger instance
500
+ """
501
+ base_logger = get_logger(name)
502
+ return PerformanceLogger(base_logger)