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.
- claude_mpm/cli/commands/analyze_code.py +0 -1
- claude_mpm/cli/commands/dashboard.py +0 -1
- claude_mpm/cli/commands/monitor.py +0 -1
- claude_mpm/core/constants.py +65 -0
- claude_mpm/core/error_handler.py +625 -0
- claude_mpm/core/file_utils.py +770 -0
- claude_mpm/core/logging_utils.py +502 -0
- claude_mpm/dashboard/static/dist/components/code-tree.js +1 -1
- claude_mpm/dashboard/static/dist/components/file-viewer.js +1 -1
- claude_mpm/dashboard/static/dist/dashboard.js +1 -1
- claude_mpm/dashboard/static/dist/socket-client.js +1 -1
- claude_mpm/dashboard/static/js/components/code-simple.js +44 -1
- claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +353 -0
- claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +235 -0
- claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +409 -0
- claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +435 -0
- claude_mpm/dashboard/static/js/components/code-tree.js +29 -5
- claude_mpm/dashboard/static/js/components/file-viewer.js +69 -27
- claude_mpm/dashboard/static/js/components/session-manager.js +7 -7
- claude_mpm/dashboard/static/js/components/working-directory.js +18 -9
- claude_mpm/dashboard/static/js/dashboard.js +91 -9
- claude_mpm/dashboard/static/js/shared/dom-helpers.js +396 -0
- claude_mpm/dashboard/static/js/shared/event-bus.js +330 -0
- claude_mpm/dashboard/static/js/shared/logger.js +385 -0
- claude_mpm/dashboard/static/js/shared/tooltip-service.js +253 -0
- claude_mpm/dashboard/static/js/socket-client.js +18 -0
- claude_mpm/dashboard/templates/index.html +22 -9
- claude_mpm/services/cli/unified_dashboard_manager.py +2 -1
- claude_mpm/services/monitor/handlers/__init__.py +2 -1
- claude_mpm/services/monitor/handlers/file.py +263 -0
- claude_mpm/services/monitor/handlers/hooks.py +25 -1
- claude_mpm/services/monitor/server.py +111 -1
- claude_mpm/services/socketio/handlers/file.py +40 -5
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/METADATA +1 -1
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/RECORD +39 -27
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/WHEEL +0 -0
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/entry_points.txt +0 -0
- {claude_mpm-4.2.13.dist-info → claude_mpm-4.2.15.dist-info}/licenses/LICENSE +0 -0
- {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)
|