runbooks 0.9.2__py3-none-any.whl → 0.9.5__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 (49) hide show
  1. runbooks/__init__.py +15 -6
  2. runbooks/cfat/__init__.py +3 -1
  3. runbooks/cloudops/__init__.py +3 -1
  4. runbooks/common/aws_utils.py +367 -0
  5. runbooks/common/enhanced_logging_example.py +239 -0
  6. runbooks/common/enhanced_logging_integration_example.py +257 -0
  7. runbooks/common/logging_integration_helper.py +344 -0
  8. runbooks/common/profile_utils.py +8 -6
  9. runbooks/common/rich_utils.py +347 -3
  10. runbooks/enterprise/logging.py +400 -38
  11. runbooks/finops/README.md +262 -406
  12. runbooks/finops/__init__.py +44 -1
  13. runbooks/finops/accuracy_cross_validator.py +12 -3
  14. runbooks/finops/business_cases.py +552 -0
  15. runbooks/finops/commvault_ec2_analysis.py +415 -0
  16. runbooks/finops/cost_processor.py +718 -42
  17. runbooks/finops/dashboard_router.py +44 -22
  18. runbooks/finops/dashboard_runner.py +302 -39
  19. runbooks/finops/embedded_mcp_validator.py +358 -48
  20. runbooks/finops/finops_scenarios.py +1122 -0
  21. runbooks/finops/helpers.py +182 -0
  22. runbooks/finops/multi_dashboard.py +30 -15
  23. runbooks/finops/scenarios.py +789 -0
  24. runbooks/finops/single_dashboard.py +386 -58
  25. runbooks/finops/types.py +29 -4
  26. runbooks/inventory/__init__.py +2 -1
  27. runbooks/main.py +522 -29
  28. runbooks/operate/__init__.py +3 -1
  29. runbooks/remediation/__init__.py +3 -1
  30. runbooks/remediation/commons.py +55 -16
  31. runbooks/remediation/commvault_ec2_analysis.py +259 -0
  32. runbooks/remediation/rds_snapshot_list.py +267 -102
  33. runbooks/remediation/workspaces_list.py +182 -31
  34. runbooks/security/__init__.py +3 -1
  35. runbooks/sre/__init__.py +2 -1
  36. runbooks/utils/__init__.py +81 -6
  37. runbooks/utils/version_validator.py +241 -0
  38. runbooks/vpc/__init__.py +2 -1
  39. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/METADATA +98 -60
  40. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/RECORD +44 -39
  41. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/entry_points.txt +1 -0
  42. runbooks/inventory/cloudtrail.md +0 -727
  43. runbooks/inventory/discovery.md +0 -81
  44. runbooks/remediation/CLAUDE.md +0 -100
  45. runbooks/remediation/DOME9.md +0 -218
  46. runbooks/security/ENTERPRISE_SECURITY_FRAMEWORK.md +0 -506
  47. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/WHEEL +0 -0
  48. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/licenses/LICENSE +0 -0
  49. {runbooks-0.9.2.dist-info → runbooks-0.9.5.dist-info}/top_level.txt +0 -0
@@ -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
- class EnterpriseLogger:
29
- """Enterprise-grade logger with structured logging capabilities."""
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 info(self, message: str, **kwargs) -> None:
163
- """Log info message."""
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(**kwargs).info(message)
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
- if _HAS_LOGURU:
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
- if _HAS_LOGURU:
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
- if _HAS_LOGURU:
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
- if _HAS_LOGURU:
193
- loguru_logger.bind(**kwargs).critical(message)
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
- logging.getLogger(self.name).critical(message)
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
- ) -> EnterpriseLogger:
613
+ rich_console: Optional[Console] = None,
614
+ json_output: bool = False,
615
+ ) -> EnterpriseRichLogger:
332
616
  """
333
- Configure enterprise logging for the application.
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 EnterpriseLogger(
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[EnterpriseLogger] = None
722
+ _global_logger: Optional[EnterpriseRichLogger] = None
432
723
 
433
724
 
434
- def get_logger() -> EnterpriseLogger:
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