runbooks 0.9.2__py3-none-any.whl → 0.9.4__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.
- runbooks/__init__.py +15 -6
- runbooks/cfat/__init__.py +3 -1
- runbooks/cloudops/__init__.py +3 -1
- runbooks/common/aws_utils.py +367 -0
- runbooks/common/enhanced_logging_example.py +239 -0
- runbooks/common/enhanced_logging_integration_example.py +257 -0
- runbooks/common/logging_integration_helper.py +344 -0
- runbooks/common/profile_utils.py +8 -6
- runbooks/common/rich_utils.py +347 -3
- runbooks/enterprise/logging.py +400 -38
- runbooks/finops/README.md +262 -406
- runbooks/finops/__init__.py +2 -1
- runbooks/finops/accuracy_cross_validator.py +12 -3
- runbooks/finops/commvault_ec2_analysis.py +415 -0
- runbooks/finops/cost_processor.py +718 -42
- runbooks/finops/dashboard_router.py +44 -22
- runbooks/finops/dashboard_runner.py +302 -39
- runbooks/finops/embedded_mcp_validator.py +358 -48
- runbooks/finops/finops_scenarios.py +771 -0
- runbooks/finops/multi_dashboard.py +30 -15
- runbooks/finops/single_dashboard.py +386 -58
- runbooks/finops/types.py +29 -4
- runbooks/inventory/__init__.py +2 -1
- runbooks/main.py +522 -29
- runbooks/operate/__init__.py +3 -1
- runbooks/remediation/__init__.py +3 -1
- runbooks/remediation/commons.py +55 -16
- runbooks/remediation/commvault_ec2_analysis.py +259 -0
- runbooks/remediation/rds_snapshot_list.py +267 -102
- runbooks/remediation/workspaces_list.py +182 -31
- runbooks/security/__init__.py +3 -1
- runbooks/sre/__init__.py +2 -1
- runbooks/utils/__init__.py +81 -6
- runbooks/utils/version_validator.py +241 -0
- runbooks/vpc/__init__.py +2 -1
- {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/METADATA +98 -60
- {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/RECORD +41 -38
- {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/entry_points.txt +1 -0
- runbooks/inventory/cloudtrail.md +0 -727
- runbooks/inventory/discovery.md +0 -81
- runbooks/remediation/CLAUDE.md +0 -100
- runbooks/remediation/DOME9.md +0 -218
- runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +0 -506
- {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/WHEEL +0 -0
- {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.9.2.dist-info → runbooks-0.9.4.dist-info}/top_level.txt +0 -0
runbooks/enterprise/logging.py
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
Enterprise-grade structured logging for CloudOps-Runbooks.
|
3
3
|
|
4
4
|
This module provides structured logging capabilities for enterprise environments,
|
5
|
-
including audit trails, performance monitoring, and compliance logging
|
5
|
+
including audit trails, performance monitoring, and compliance logging with
|
6
|
+
Rich CLI integration for user-type specific output.
|
6
7
|
"""
|
7
8
|
|
8
9
|
import json
|
@@ -12,34 +13,49 @@ from contextlib import contextmanager
|
|
12
13
|
from datetime import datetime
|
13
14
|
from functools import wraps
|
14
15
|
from pathlib import Path
|
15
|
-
from typing import Any, Dict, Optional, Union
|
16
|
+
from typing import Any, Dict, List, Optional, Union, Literal
|
16
17
|
|
17
18
|
try:
|
18
19
|
from loguru import logger as loguru_logger
|
19
|
-
|
20
20
|
_HAS_LOGURU = True
|
21
21
|
except ImportError:
|
22
22
|
import logging
|
23
|
-
|
24
23
|
loguru_logger = logging.getLogger(__name__)
|
25
24
|
_HAS_LOGURU = False
|
26
25
|
|
26
|
+
# Rich CLI integration
|
27
|
+
try:
|
28
|
+
from rich.console import Console
|
29
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn, TaskProgressColumn, TimeElapsedColumn
|
30
|
+
from rich.table import Table
|
31
|
+
from rich.panel import Panel
|
32
|
+
from rich.text import Text
|
33
|
+
from rich import box
|
34
|
+
_HAS_RICH = True
|
35
|
+
except ImportError:
|
36
|
+
_HAS_RICH = False
|
37
|
+
|
38
|
+
# User type definitions for logging levels
|
39
|
+
UserType = Literal["DEBUG", "INFO", "WARNING", "ERROR"]
|
27
40
|
|
28
|
-
|
29
|
-
|
41
|
+
|
42
|
+
class EnterpriseRichLogger:
|
43
|
+
"""Enterprise-grade logger with Rich CLI integration and user-type specific output."""
|
30
44
|
|
31
45
|
def __init__(
|
32
46
|
self,
|
33
47
|
name: str = "runbooks",
|
34
|
-
level: str = "INFO",
|
48
|
+
level: str = "INFO",
|
35
49
|
log_dir: Optional[Path] = None,
|
36
50
|
enable_console: bool = True,
|
37
51
|
enable_file: bool = True,
|
38
52
|
enable_audit: bool = True,
|
39
53
|
correlation_id: Optional[str] = None,
|
54
|
+
rich_console: Optional[Console] = None,
|
55
|
+
json_output: bool = False,
|
40
56
|
):
|
41
57
|
"""
|
42
|
-
Initialize enterprise logger.
|
58
|
+
Initialize enterprise logger with Rich CLI integration.
|
43
59
|
|
44
60
|
Args:
|
45
61
|
name: Logger name
|
@@ -49,11 +65,26 @@ class EnterpriseLogger:
|
|
49
65
|
enable_file: Enable file logging
|
50
66
|
enable_audit: Enable audit logging
|
51
67
|
correlation_id: Correlation ID for tracking operations
|
68
|
+
rich_console: Rich console instance for beautiful output
|
69
|
+
json_output: Enable structured JSON output for programmatic use
|
52
70
|
"""
|
53
71
|
self.name = name
|
54
|
-
self.level = level
|
72
|
+
self.level = level.upper()
|
55
73
|
self.log_dir = log_dir or Path.home() / ".runbooks" / "logs"
|
56
74
|
self.correlation_id = correlation_id or self._generate_correlation_id()
|
75
|
+
self.json_output = json_output
|
76
|
+
|
77
|
+
# Initialize Rich console for beautiful output
|
78
|
+
self.console = rich_console or (Console() if _HAS_RICH else None)
|
79
|
+
|
80
|
+
# User-type specific styling
|
81
|
+
self.level_styles = {
|
82
|
+
"DEBUG": {"style": "dim white", "icon": "🔧", "label": "TECH"},
|
83
|
+
"INFO": {"style": "cyan", "icon": "ℹ️", "label": "INFO"},
|
84
|
+
"WARNING": {"style": "yellow bold", "icon": "⚠️", "label": "BIZ"},
|
85
|
+
"ERROR": {"style": "red bold", "icon": "❌", "label": "ALL"},
|
86
|
+
"CRITICAL": {"style": "red bold reverse", "icon": "🚨", "label": "ALL"}
|
87
|
+
}
|
57
88
|
|
58
89
|
# Ensure log directory exists
|
59
90
|
self.log_dir.mkdir(parents=True, exist_ok=True)
|
@@ -159,40 +190,291 @@ class EnterpriseLogger:
|
|
159
190
|
file_handler.setFormatter(file_formatter)
|
160
191
|
logger.addHandler(file_handler)
|
161
192
|
|
162
|
-
def
|
163
|
-
"""
|
193
|
+
def _should_log(self, level: str) -> bool:
|
194
|
+
"""Determine if message should be logged based on current log level."""
|
195
|
+
level_hierarchy = {"DEBUG": 0, "INFO": 1, "WARNING": 2, "ERROR": 3, "CRITICAL": 4}
|
196
|
+
return level_hierarchy.get(level, 0) >= level_hierarchy.get(self.level, 1)
|
197
|
+
|
198
|
+
def _rich_log(self, level: str, message: str, details: Optional[Dict[str, Any]] = None, progress: Optional[Progress] = None) -> None:
|
199
|
+
"""Enhanced logging with Rich CLI formatting and user-type specific content."""
|
200
|
+
if not self._should_log(level):
|
201
|
+
return
|
202
|
+
|
203
|
+
# Get level-specific styling
|
204
|
+
level_config = self.level_styles.get(level, self.level_styles["INFO"])
|
205
|
+
icon = level_config["icon"]
|
206
|
+
style = level_config["style"]
|
207
|
+
label = level_config["label"]
|
208
|
+
|
209
|
+
# Handle JSON output for programmatic use
|
210
|
+
if self.json_output:
|
211
|
+
log_entry = {
|
212
|
+
"timestamp": datetime.utcnow().isoformat(),
|
213
|
+
"level": level,
|
214
|
+
"correlation_id": self.correlation_id,
|
215
|
+
"message": message,
|
216
|
+
"details": details or {}
|
217
|
+
}
|
218
|
+
if self.console:
|
219
|
+
self.console.print_json(data=log_entry)
|
220
|
+
else:
|
221
|
+
print(json.dumps(log_entry))
|
222
|
+
return
|
223
|
+
|
224
|
+
# Rich console output with user-type specific formatting
|
225
|
+
if self.console and _HAS_RICH:
|
226
|
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
227
|
+
|
228
|
+
# User-type specific message formatting
|
229
|
+
if level == "DEBUG":
|
230
|
+
# Tech users - Full details with timing and API info
|
231
|
+
self.console.print(f"[dim]{timestamp}[/] {icon} [bold]{label}[/] {message}")
|
232
|
+
if details and "aws_api" in details and details["aws_api"]:
|
233
|
+
api_details = details["aws_api"]
|
234
|
+
self.console.print(f" └─ [dim]API: {api_details.get('service', 'unknown')} / {api_details.get('operation', 'unknown')}[/]")
|
235
|
+
if details.get("request_id"):
|
236
|
+
self.console.print(f" └─ [dim]Request ID: {details['request_id']}[/]")
|
237
|
+
if details.get("duration"):
|
238
|
+
duration_color = "green" if details["duration"] < 1.0 else "yellow" if details["duration"] < 5.0 else "red"
|
239
|
+
self.console.print(f" └─ [dim]Duration: [{duration_color}]{details['duration']:.3f}s[/dim][/]")
|
240
|
+
if details.get("memory_usage"):
|
241
|
+
memory_mb = details["memory_usage"] / 1024 / 1024
|
242
|
+
memory_color = "green" if memory_mb < 50 else "yellow" if memory_mb < 200 else "red"
|
243
|
+
self.console.print(f" └─ [dim]Memory: [{memory_color}]{memory_mb:.1f}MB[/dim][/]")
|
244
|
+
|
245
|
+
elif level == "INFO":
|
246
|
+
# Standard users - Clean status with progress bars
|
247
|
+
if progress:
|
248
|
+
self.console.print(f"{icon} {message}", style=style)
|
249
|
+
else:
|
250
|
+
info_text = f"[{style}]{icon} {message}[/]"
|
251
|
+
if details.get("resource_count"):
|
252
|
+
info_text += f" ({details['resource_count']} resources)"
|
253
|
+
if details.get("operation_status"):
|
254
|
+
status_color = "green" if details["operation_status"] == "completed" else "yellow"
|
255
|
+
info_text += f" [{status_color}][{details['operation_status']}][/]"
|
256
|
+
self.console.print(info_text)
|
257
|
+
|
258
|
+
elif level == "WARNING":
|
259
|
+
# Business users - Recommendations and alerts
|
260
|
+
self.console.print(f"{icon} [bold]{label}[/] [{style}]{message}[/]")
|
261
|
+
if details.get("recommendation"):
|
262
|
+
self.console.print(f" 💡 [bright_cyan]Recommendation:[/] {details['recommendation']}")
|
263
|
+
if details.get("cost_impact"):
|
264
|
+
impact_color = "red" if details["cost_impact"] > 1000 else "yellow" if details["cost_impact"] > 100 else "green"
|
265
|
+
self.console.print(f" 💰 [{impact_color}]Cost Impact:[/] ${details['cost_impact']:,.2f}/month")
|
266
|
+
if details.get("savings_opportunity"):
|
267
|
+
self.console.print(f" 💎 [bright_green]Savings Opportunity:[/] ${details['savings_opportunity']:,.2f}/month")
|
268
|
+
if details.get("business_impact"):
|
269
|
+
self.console.print(f" 📊 [bright_blue]Business Impact:[/] {details['business_impact']}")
|
270
|
+
|
271
|
+
elif level in ["ERROR", "CRITICAL"]:
|
272
|
+
# All users - Clear errors with solutions
|
273
|
+
self.console.print(f"{icon} [bold]{label}[/] [{style}]{message}[/]")
|
274
|
+
if details.get("solution"):
|
275
|
+
self.console.print(f" 🔧 [bright_blue]Solution:[/] {details['solution']}")
|
276
|
+
if details.get("suggested_command"):
|
277
|
+
self.console.print(f" ⚡ [bright_yellow]Try this command:[/] [cyan]{details['suggested_command']}[/]")
|
278
|
+
if details.get("aws_error"):
|
279
|
+
self.console.print(f" 📋 [dim]AWS Error:[/] {details['aws_error']}")
|
280
|
+
if details.get("troubleshooting_steps"):
|
281
|
+
self.console.print(f" 📝 [bright_magenta]Troubleshooting Steps:[/]")
|
282
|
+
for i, step in enumerate(details["troubleshooting_steps"], 1):
|
283
|
+
self.console.print(f" {i}. {step}")
|
284
|
+
else:
|
285
|
+
# Fallback to standard logging
|
286
|
+
print(f"[{level}] {message}")
|
287
|
+
|
288
|
+
# Always log to file systems
|
164
289
|
if _HAS_LOGURU:
|
165
|
-
loguru_logger.bind(**
|
290
|
+
loguru_logger.bind(correlation_id=self.correlation_id, **(details or {})).info(f"[{level}] {message}")
|
166
291
|
else:
|
167
|
-
logging.getLogger(self.name).info(message)
|
292
|
+
logging.getLogger(self.name).info(f"[{level}] {message}")
|
293
|
+
|
294
|
+
def debug_tech(self, message: str, aws_api: Optional[Dict[str, str]] = None, duration: Optional[float] = None,
|
295
|
+
memory_usage: Optional[int] = None, request_id: Optional[str] = None, **kwargs) -> None:
|
296
|
+
"""Log debug message for tech users (SRE/DevOps) with full API details."""
|
297
|
+
details = {
|
298
|
+
"aws_api": aws_api or {},
|
299
|
+
"duration": duration,
|
300
|
+
"memory_usage": memory_usage,
|
301
|
+
"request_id": request_id,
|
302
|
+
**kwargs
|
303
|
+
}
|
304
|
+
self._rich_log("DEBUG", message, details)
|
305
|
+
|
306
|
+
def info_standard(self, message: str, progress: Optional[Progress] = None, resource_count: Optional[int] = None,
|
307
|
+
operation_status: Optional[str] = None, **kwargs) -> None:
|
308
|
+
"""Log info message for standard users with progress indicators."""
|
309
|
+
details = {
|
310
|
+
"resource_count": resource_count,
|
311
|
+
"operation_status": operation_status,
|
312
|
+
**kwargs
|
313
|
+
}
|
314
|
+
self._rich_log("INFO", message, details, progress)
|
315
|
+
|
316
|
+
def warning_business(self, message: str, recommendation: Optional[str] = None, cost_impact: Optional[float] = None,
|
317
|
+
savings_opportunity: Optional[float] = None, business_impact: Optional[str] = None, **kwargs) -> None:
|
318
|
+
"""Log warning message for business users with recommendations and cost impact."""
|
319
|
+
details = {
|
320
|
+
"recommendation": recommendation,
|
321
|
+
"cost_impact": cost_impact,
|
322
|
+
"savings_opportunity": savings_opportunity,
|
323
|
+
"business_impact": business_impact,
|
324
|
+
**kwargs
|
325
|
+
}
|
326
|
+
self._rich_log("WARNING", message, details)
|
327
|
+
|
328
|
+
def error_all(self, message: str, solution: Optional[str] = None, aws_error: Optional[str] = None,
|
329
|
+
suggested_command: Optional[str] = None, troubleshooting_steps: Optional[List[str]] = None, **kwargs) -> None:
|
330
|
+
"""Log error message for all users with clear solutions."""
|
331
|
+
details = {
|
332
|
+
"solution": solution,
|
333
|
+
"aws_error": aws_error,
|
334
|
+
"suggested_command": suggested_command,
|
335
|
+
"troubleshooting_steps": troubleshooting_steps or [],
|
336
|
+
**kwargs
|
337
|
+
}
|
338
|
+
self._rich_log("ERROR", message, details)
|
339
|
+
|
340
|
+
# Backward compatibility methods
|
341
|
+
def info(self, message: str, **kwargs) -> None:
|
342
|
+
"""Log info message (backward compatibility)."""
|
343
|
+
self.info_standard(message, **kwargs)
|
168
344
|
|
169
345
|
def debug(self, message: str, **kwargs) -> None:
|
170
|
-
"""Log debug message."""
|
171
|
-
|
172
|
-
loguru_logger.bind(**kwargs).debug(message)
|
173
|
-
else:
|
174
|
-
logging.getLogger(self.name).debug(message)
|
346
|
+
"""Log debug message (backward compatibility)."""
|
347
|
+
self.debug_tech(message, **kwargs)
|
175
348
|
|
176
349
|
def warning(self, message: str, **kwargs) -> None:
|
177
|
-
"""Log warning message."""
|
178
|
-
|
179
|
-
loguru_logger.bind(**kwargs).warning(message)
|
180
|
-
else:
|
181
|
-
logging.getLogger(self.name).warning(message)
|
350
|
+
"""Log warning message (backward compatibility)."""
|
351
|
+
self.warning_business(message, **kwargs)
|
182
352
|
|
183
353
|
def error(self, message: str, **kwargs) -> None:
|
184
|
-
"""Log error message."""
|
185
|
-
|
186
|
-
loguru_logger.bind(**kwargs).error(message)
|
187
|
-
else:
|
188
|
-
logging.getLogger(self.name).error(message)
|
354
|
+
"""Log error message (backward compatibility)."""
|
355
|
+
self.error_all(message, **kwargs)
|
189
356
|
|
190
357
|
def critical(self, message: str, **kwargs) -> None:
|
191
358
|
"""Log critical message."""
|
192
|
-
|
193
|
-
|
359
|
+
self._rich_log("CRITICAL", message, kwargs)
|
360
|
+
|
361
|
+
# Convenience methods for common operations
|
362
|
+
def log_aws_operation(self, operation: str, service: str, duration: Optional[float] = None,
|
363
|
+
success: bool = True, resource_count: Optional[int] = None, **kwargs) -> None:
|
364
|
+
"""Log AWS operation with appropriate level based on success and duration."""
|
365
|
+
if not success:
|
366
|
+
self.error_all(
|
367
|
+
f"AWS {service} {operation} failed",
|
368
|
+
solution=f"Check AWS permissions for {service}:{operation}",
|
369
|
+
aws_error=kwargs.get("error"),
|
370
|
+
suggested_command=f"aws {service} {operation.replace('_', '-')} --help"
|
371
|
+
)
|
372
|
+
elif self.level == "DEBUG":
|
373
|
+
self.debug_tech(
|
374
|
+
f"AWS {service} {operation} completed",
|
375
|
+
aws_api={"service": service, "operation": operation},
|
376
|
+
duration=duration,
|
377
|
+
**kwargs
|
378
|
+
)
|
194
379
|
else:
|
195
|
-
|
380
|
+
status = "completed" if success else "failed"
|
381
|
+
self.info_standard(
|
382
|
+
f"{service.upper()} {operation.replace('_', ' ')} {status}",
|
383
|
+
resource_count=resource_count,
|
384
|
+
operation_status=status
|
385
|
+
)
|
386
|
+
|
387
|
+
def log_cost_analysis(self, operation: str, cost_impact: Optional[float] = None,
|
388
|
+
savings_opportunity: Optional[float] = None, recommendation: Optional[str] = None) -> None:
|
389
|
+
"""Log cost analysis with business-focused messaging."""
|
390
|
+
if cost_impact and cost_impact > 100: # Significant cost impact
|
391
|
+
self.warning_business(
|
392
|
+
f"Cost analysis: {operation}",
|
393
|
+
cost_impact=cost_impact,
|
394
|
+
savings_opportunity=savings_opportunity,
|
395
|
+
recommendation=recommendation or f"Review {operation} for optimization opportunities"
|
396
|
+
)
|
397
|
+
else:
|
398
|
+
self.info_standard(f"Cost analysis completed: {operation}")
|
399
|
+
|
400
|
+
def log_performance_metric(self, operation: str, duration: float, threshold: float = 5.0,
|
401
|
+
memory_usage: Optional[int] = None) -> None:
|
402
|
+
"""Log performance metrics with appropriate warnings."""
|
403
|
+
if duration > threshold:
|
404
|
+
self.warning_business(
|
405
|
+
f"Performance alert: {operation} took {duration:.2f}s",
|
406
|
+
recommendation=f"Consider optimizing {operation} - target: <{threshold}s",
|
407
|
+
business_impact="May affect user experience during peak hours"
|
408
|
+
)
|
409
|
+
elif self.level == "DEBUG":
|
410
|
+
self.debug_tech(
|
411
|
+
f"Performance: {operation}",
|
412
|
+
duration=duration,
|
413
|
+
memory_usage=memory_usage
|
414
|
+
)
|
415
|
+
else:
|
416
|
+
self.info_standard(f"{operation} completed", operation_status="completed")
|
417
|
+
|
418
|
+
def log_security_finding(self, finding: str, severity: str = "medium",
|
419
|
+
remediation_steps: Optional[List[str]] = None) -> None:
|
420
|
+
"""Log security finding with appropriate level and remediation."""
|
421
|
+
if severity.lower() in ["high", "critical"]:
|
422
|
+
self.error_all(
|
423
|
+
f"Security finding: {finding}",
|
424
|
+
solution=f"Immediate action required for {severity} severity finding",
|
425
|
+
troubleshooting_steps=remediation_steps or [
|
426
|
+
"Review security policies",
|
427
|
+
"Apply security patches",
|
428
|
+
"Contact security team if needed"
|
429
|
+
]
|
430
|
+
)
|
431
|
+
elif severity.lower() == "medium":
|
432
|
+
self.warning_business(
|
433
|
+
f"Security alert: {finding}",
|
434
|
+
recommendation="Schedule remediation within next maintenance window",
|
435
|
+
business_impact="Potential security risk if not addressed"
|
436
|
+
)
|
437
|
+
else:
|
438
|
+
self.info_standard(f"Security scan: {finding} ({severity} severity)")
|
439
|
+
|
440
|
+
@contextmanager
|
441
|
+
def operation_context(self, operation_name: str, **context_details):
|
442
|
+
"""Context manager for logging operation start/end with performance tracking."""
|
443
|
+
import time
|
444
|
+
start_time = time.time()
|
445
|
+
|
446
|
+
if self.level == "DEBUG":
|
447
|
+
self.debug_tech(f"Starting {operation_name}", **context_details)
|
448
|
+
else:
|
449
|
+
self.info_standard(f"Starting {operation_name}")
|
450
|
+
|
451
|
+
success = True
|
452
|
+
try:
|
453
|
+
yield self
|
454
|
+
except Exception as e:
|
455
|
+
success = False
|
456
|
+
self.error_all(
|
457
|
+
f"Operation failed: {operation_name}",
|
458
|
+
solution="Check logs above for detailed error information",
|
459
|
+
aws_error=str(e)
|
460
|
+
)
|
461
|
+
raise
|
462
|
+
finally:
|
463
|
+
duration = time.time() - start_time
|
464
|
+
if success:
|
465
|
+
if self.level == "DEBUG":
|
466
|
+
self.debug_tech(
|
467
|
+
f"Completed {operation_name}",
|
468
|
+
duration=duration,
|
469
|
+
**context_details
|
470
|
+
)
|
471
|
+
else:
|
472
|
+
self.info_standard(
|
473
|
+
f"Completed {operation_name}",
|
474
|
+
operation_status="completed"
|
475
|
+
)
|
476
|
+
else:
|
477
|
+
self.error_all(f"Failed {operation_name} after {duration:.2f}s")
|
196
478
|
|
197
479
|
|
198
480
|
class AuditLogger:
|
@@ -328,30 +610,39 @@ def configure_enterprise_logging(
|
|
328
610
|
correlation_id: Optional[str] = None,
|
329
611
|
enable_audit: bool = True,
|
330
612
|
enable_performance: bool = True,
|
331
|
-
|
613
|
+
rich_console: Optional[Console] = None,
|
614
|
+
json_output: bool = False,
|
615
|
+
) -> EnterpriseRichLogger:
|
332
616
|
"""
|
333
|
-
Configure enterprise logging
|
617
|
+
Configure enhanced enterprise logging with Rich CLI integration.
|
334
618
|
|
335
619
|
Args:
|
336
|
-
level: Log level
|
620
|
+
level: Log level (DEBUG, INFO, WARNING, ERROR)
|
337
621
|
log_dir: Log directory path
|
338
622
|
correlation_id: Correlation ID for tracking
|
339
623
|
enable_audit: Enable audit logging
|
340
624
|
enable_performance: Enable performance logging
|
625
|
+
rich_console: Rich console instance for beautiful output
|
626
|
+
json_output: Enable structured JSON output
|
341
627
|
|
342
628
|
Returns:
|
343
|
-
Configured enterprise logger
|
629
|
+
Configured enterprise logger with Rich CLI support
|
344
630
|
"""
|
345
631
|
if isinstance(log_dir, str):
|
346
632
|
log_dir = Path(log_dir)
|
347
633
|
|
348
|
-
return
|
634
|
+
return EnterpriseRichLogger(
|
349
635
|
level=level,
|
350
636
|
log_dir=log_dir,
|
351
637
|
correlation_id=correlation_id,
|
352
638
|
enable_audit=enable_audit,
|
639
|
+
rich_console=rich_console,
|
640
|
+
json_output=json_output,
|
353
641
|
)
|
354
642
|
|
643
|
+
# Backward compatibility alias
|
644
|
+
EnterpriseLogger = EnterpriseRichLogger
|
645
|
+
|
355
646
|
|
356
647
|
def log_operation_performance(
|
357
648
|
operation_name: Optional[str] = None,
|
@@ -428,12 +719,83 @@ def log_audit_operation(
|
|
428
719
|
|
429
720
|
|
430
721
|
# Global logger instance
|
431
|
-
_global_logger: Optional[
|
722
|
+
_global_logger: Optional[EnterpriseRichLogger] = None
|
432
723
|
|
433
724
|
|
434
|
-
def get_logger() ->
|
725
|
+
def get_logger() -> EnterpriseRichLogger:
|
435
726
|
"""Get global enterprise logger instance."""
|
436
727
|
global _global_logger
|
437
728
|
if _global_logger is None:
|
438
729
|
_global_logger = configure_enterprise_logging()
|
439
730
|
return _global_logger
|
731
|
+
|
732
|
+
|
733
|
+
def get_context_logger(level: str = "INFO", json_output: bool = False) -> EnterpriseRichLogger:
|
734
|
+
"""
|
735
|
+
Get context-aware enterprise logger with Rich CLI integration.
|
736
|
+
|
737
|
+
This is the recommended way to get a logger instance with proper
|
738
|
+
Rich CLI integration and user-type specific formatting.
|
739
|
+
|
740
|
+
Args:
|
741
|
+
level: Log level (DEBUG, INFO, WARNING, ERROR)
|
742
|
+
json_output: Enable structured JSON output
|
743
|
+
|
744
|
+
Returns:
|
745
|
+
Configured enterprise logger with Rich CLI support
|
746
|
+
"""
|
747
|
+
try:
|
748
|
+
from runbooks.common.rich_utils import get_context_aware_console
|
749
|
+
rich_console = get_context_aware_console()
|
750
|
+
except ImportError:
|
751
|
+
rich_console = Console() if _HAS_RICH else None
|
752
|
+
|
753
|
+
return configure_enterprise_logging(
|
754
|
+
level=level,
|
755
|
+
rich_console=rich_console,
|
756
|
+
json_output=json_output
|
757
|
+
)
|
758
|
+
|
759
|
+
|
760
|
+
def get_module_logger(module_name: str, level: str = "INFO", json_output: bool = False) -> EnterpriseRichLogger:
|
761
|
+
"""
|
762
|
+
Get a module-specific enhanced logger with automatic correlation ID and module identification.
|
763
|
+
|
764
|
+
This is the recommended method for modules to get their logger instance.
|
765
|
+
|
766
|
+
Args:
|
767
|
+
module_name: Name of the module (e.g., 'finops', 'inventory', 'security')
|
768
|
+
level: Log level (DEBUG, INFO, WARNING, ERROR)
|
769
|
+
json_output: Enable structured JSON output
|
770
|
+
|
771
|
+
Returns:
|
772
|
+
Configured enterprise logger with module-specific identification
|
773
|
+
|
774
|
+
Example:
|
775
|
+
>>> from runbooks.enterprise.logging import get_module_logger
|
776
|
+
>>> logger = get_module_logger("finops", level="INFO")
|
777
|
+
>>> logger.info_standard("Starting cost analysis", resource_count=10)
|
778
|
+
>>> logger.log_cost_analysis("monthly_spend", cost_impact=1500.0, savings_opportunity=450.0)
|
779
|
+
"""
|
780
|
+
try:
|
781
|
+
from runbooks.common.rich_utils import get_context_aware_console
|
782
|
+
rich_console = get_context_aware_console()
|
783
|
+
except ImportError:
|
784
|
+
rich_console = Console() if _HAS_RICH else None
|
785
|
+
|
786
|
+
logger = EnterpriseRichLogger(
|
787
|
+
name=f"runbooks.{module_name}",
|
788
|
+
level=level,
|
789
|
+
rich_console=rich_console,
|
790
|
+
json_output=json_output
|
791
|
+
)
|
792
|
+
|
793
|
+
# Add module-specific initialization message
|
794
|
+
if level == "DEBUG":
|
795
|
+
logger.debug_tech(
|
796
|
+
f"Module logger initialized for {module_name}",
|
797
|
+
aws_api={"service": "logging", "operation": "module_init"},
|
798
|
+
duration=0.001
|
799
|
+
)
|
800
|
+
|
801
|
+
return logger
|