runbooks 0.7.6__py3-none-any.whl → 0.7.9__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 +1 -1
- runbooks/base.py +5 -1
- runbooks/cfat/__init__.py +8 -4
- runbooks/cfat/assessment/collectors.py +171 -14
- runbooks/cfat/assessment/compliance.py +871 -0
- runbooks/cfat/assessment/runner.py +122 -11
- runbooks/cfat/models.py +6 -2
- runbooks/common/logger.py +14 -0
- runbooks/common/rich_utils.py +451 -0
- runbooks/enterprise/__init__.py +68 -0
- runbooks/enterprise/error_handling.py +411 -0
- runbooks/enterprise/logging.py +439 -0
- runbooks/enterprise/multi_tenant.py +583 -0
- runbooks/finops/README.md +468 -241
- runbooks/finops/__init__.py +39 -3
- runbooks/finops/cli.py +83 -18
- runbooks/finops/cross_validation.py +375 -0
- runbooks/finops/dashboard_runner.py +812 -164
- runbooks/finops/enhanced_dashboard_runner.py +525 -0
- runbooks/finops/finops_dashboard.py +1892 -0
- runbooks/finops/helpers.py +485 -51
- runbooks/finops/optimizer.py +823 -0
- runbooks/finops/tests/__init__.py +19 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
- runbooks/finops/tests/run_comprehensive_tests.py +421 -0
- runbooks/finops/tests/run_tests.py +305 -0
- runbooks/finops/tests/test_finops_dashboard.py +705 -0
- runbooks/finops/tests/test_integration.py +477 -0
- runbooks/finops/tests/test_performance.py +380 -0
- runbooks/finops/tests/test_performance_benchmarks.py +500 -0
- runbooks/finops/tests/test_reference_images_validation.py +867 -0
- runbooks/finops/tests/test_single_account_features.py +715 -0
- runbooks/finops/tests/validate_test_suite.py +220 -0
- runbooks/finops/types.py +1 -1
- runbooks/hitl/enhanced_workflow_engine.py +725 -0
- runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
- runbooks/inventory/collectors/aws_comprehensive.py +442 -0
- runbooks/inventory/collectors/enterprise_scale.py +281 -0
- runbooks/inventory/core/collector.py +172 -13
- runbooks/inventory/discovery.md +1 -1
- runbooks/inventory/list_ec2_instances.py +18 -20
- runbooks/inventory/list_ssm_parameters.py +31 -3
- runbooks/inventory/organizations_discovery.py +1269 -0
- runbooks/inventory/rich_inventory_display.py +393 -0
- runbooks/inventory/run_on_multi_accounts.py +35 -19
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/vpc_flow_analyzer.py +1030 -0
- runbooks/main.py +2215 -119
- runbooks/metrics/dora_metrics_engine.py +599 -0
- runbooks/operate/__init__.py +2 -2
- runbooks/operate/base.py +122 -10
- runbooks/operate/deployment_framework.py +1032 -0
- runbooks/operate/deployment_validator.py +853 -0
- runbooks/operate/dynamodb_operations.py +10 -6
- runbooks/operate/ec2_operations.py +319 -11
- runbooks/operate/executive_dashboard.py +779 -0
- runbooks/operate/mcp_integration.py +750 -0
- runbooks/operate/nat_gateway_operations.py +1120 -0
- runbooks/operate/networking_cost_heatmap.py +685 -0
- runbooks/operate/privatelink_operations.py +940 -0
- runbooks/operate/s3_operations.py +10 -6
- runbooks/operate/vpc_endpoints.py +644 -0
- runbooks/operate/vpc_operations.py +1038 -0
- runbooks/remediation/__init__.py +2 -2
- runbooks/remediation/acm_remediation.py +1 -1
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/cloudtrail_remediation.py +1 -1
- runbooks/remediation/cognito_remediation.py +1 -1
- runbooks/remediation/dynamodb_remediation.py +1 -1
- runbooks/remediation/ec2_remediation.py +1 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
- runbooks/remediation/kms_enable_key_rotation.py +1 -1
- runbooks/remediation/kms_remediation.py +1 -1
- runbooks/remediation/lambda_remediation.py +1 -1
- runbooks/remediation/multi_account.py +1 -1
- runbooks/remediation/rds_remediation.py +1 -1
- runbooks/remediation/s3_block_public_access.py +1 -1
- runbooks/remediation/s3_enable_access_logging.py +1 -1
- runbooks/remediation/s3_encryption.py +1 -1
- runbooks/remediation/s3_remediation.py +1 -1
- runbooks/remediation/vpc_remediation.py +475 -0
- runbooks/security/__init__.py +3 -1
- runbooks/security/compliance_automation.py +632 -0
- runbooks/security/report_generator.py +10 -0
- runbooks/security/run_script.py +31 -5
- runbooks/security/security_baseline_tester.py +169 -30
- runbooks/security/security_export.py +477 -0
- runbooks/validation/__init__.py +10 -0
- runbooks/validation/benchmark.py +484 -0
- runbooks/validation/cli.py +356 -0
- runbooks/validation/mcp_validator.py +768 -0
- runbooks/vpc/__init__.py +38 -0
- runbooks/vpc/config.py +212 -0
- runbooks/vpc/cost_engine.py +347 -0
- runbooks/vpc/heatmap_engine.py +605 -0
- runbooks/vpc/manager_interface.py +634 -0
- runbooks/vpc/networking_wrapper.py +1260 -0
- runbooks/vpc/rich_formatters.py +679 -0
- runbooks/vpc/tests/__init__.py +5 -0
- runbooks/vpc/tests/conftest.py +356 -0
- runbooks/vpc/tests/test_cli_integration.py +530 -0
- runbooks/vpc/tests/test_config.py +458 -0
- runbooks/vpc/tests/test_cost_engine.py +479 -0
- runbooks/vpc/tests/test_networking_wrapper.py +512 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,439 @@
|
|
1
|
+
"""
|
2
|
+
Enterprise-grade structured logging for CloudOps-Runbooks.
|
3
|
+
|
4
|
+
This module provides structured logging capabilities for enterprise environments,
|
5
|
+
including audit trails, performance monitoring, and compliance logging.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
import sys
|
10
|
+
import time
|
11
|
+
from contextlib import contextmanager
|
12
|
+
from datetime import datetime
|
13
|
+
from functools import wraps
|
14
|
+
from pathlib import Path
|
15
|
+
from typing import Any, Dict, Optional, Union
|
16
|
+
|
17
|
+
try:
|
18
|
+
from loguru import logger as loguru_logger
|
19
|
+
|
20
|
+
_HAS_LOGURU = True
|
21
|
+
except ImportError:
|
22
|
+
import logging
|
23
|
+
|
24
|
+
loguru_logger = logging.getLogger(__name__)
|
25
|
+
_HAS_LOGURU = False
|
26
|
+
|
27
|
+
|
28
|
+
class EnterpriseLogger:
|
29
|
+
"""Enterprise-grade logger with structured logging capabilities."""
|
30
|
+
|
31
|
+
def __init__(
|
32
|
+
self,
|
33
|
+
name: str = "runbooks",
|
34
|
+
level: str = "INFO",
|
35
|
+
log_dir: Optional[Path] = None,
|
36
|
+
enable_console: bool = True,
|
37
|
+
enable_file: bool = True,
|
38
|
+
enable_audit: bool = True,
|
39
|
+
correlation_id: Optional[str] = None,
|
40
|
+
):
|
41
|
+
"""
|
42
|
+
Initialize enterprise logger.
|
43
|
+
|
44
|
+
Args:
|
45
|
+
name: Logger name
|
46
|
+
level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
47
|
+
log_dir: Directory for log files
|
48
|
+
enable_console: Enable console logging
|
49
|
+
enable_file: Enable file logging
|
50
|
+
enable_audit: Enable audit logging
|
51
|
+
correlation_id: Correlation ID for tracking operations
|
52
|
+
"""
|
53
|
+
self.name = name
|
54
|
+
self.level = level
|
55
|
+
self.log_dir = log_dir or Path.home() / ".runbooks" / "logs"
|
56
|
+
self.correlation_id = correlation_id or self._generate_correlation_id()
|
57
|
+
|
58
|
+
# Ensure log directory exists
|
59
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
60
|
+
|
61
|
+
# Setup logging handlers
|
62
|
+
if _HAS_LOGURU:
|
63
|
+
self._setup_loguru_logging(enable_console, enable_file, enable_audit)
|
64
|
+
else:
|
65
|
+
self._setup_standard_logging(enable_console, enable_file)
|
66
|
+
|
67
|
+
def _generate_correlation_id(self) -> str:
|
68
|
+
"""Generate unique correlation ID for tracking operations."""
|
69
|
+
import uuid
|
70
|
+
|
71
|
+
return f"runbooks-{int(time.time())}-{str(uuid.uuid4())[:8]}"
|
72
|
+
|
73
|
+
def _setup_loguru_logging(self, enable_console: bool, enable_file: bool, enable_audit: bool) -> None:
|
74
|
+
"""Setup Loguru-based logging."""
|
75
|
+
# Remove default handler
|
76
|
+
loguru_logger.remove()
|
77
|
+
|
78
|
+
# Console handler
|
79
|
+
if enable_console:
|
80
|
+
loguru_logger.add(
|
81
|
+
sys.stderr,
|
82
|
+
level=self.level,
|
83
|
+
format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | "
|
84
|
+
"<level>{level: <8}</level> | "
|
85
|
+
"<cyan>{extra[correlation_id]}</cyan> | "
|
86
|
+
"<cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - "
|
87
|
+
"<level>{message}</level>",
|
88
|
+
colorize=True,
|
89
|
+
filter=lambda record: record["extra"].setdefault("correlation_id", self.correlation_id),
|
90
|
+
)
|
91
|
+
|
92
|
+
# Application log file
|
93
|
+
if enable_file:
|
94
|
+
app_log_file = self.log_dir / "runbooks.log"
|
95
|
+
loguru_logger.add(
|
96
|
+
app_log_file,
|
97
|
+
level="DEBUG",
|
98
|
+
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {extra[correlation_id]} | "
|
99
|
+
"{name}:{function}:{line} - {message}",
|
100
|
+
rotation="10 MB",
|
101
|
+
retention="30 days",
|
102
|
+
compression="zip",
|
103
|
+
filter=lambda record: record["extra"].setdefault("correlation_id", self.correlation_id),
|
104
|
+
)
|
105
|
+
|
106
|
+
# Audit log file
|
107
|
+
if enable_audit:
|
108
|
+
audit_log_file = self.log_dir / "audit.log"
|
109
|
+
loguru_logger.add(
|
110
|
+
audit_log_file,
|
111
|
+
level="INFO",
|
112
|
+
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {extra[correlation_id]} | {message}",
|
113
|
+
rotation="50 MB",
|
114
|
+
retention="365 days",
|
115
|
+
compression="zip",
|
116
|
+
filter=lambda record: (
|
117
|
+
record["extra"].setdefault("correlation_id", self.correlation_id)
|
118
|
+
or record.get("extra", {}).get("audit", False)
|
119
|
+
),
|
120
|
+
)
|
121
|
+
|
122
|
+
# Performance log file
|
123
|
+
performance_log_file = self.log_dir / "performance.log"
|
124
|
+
loguru_logger.add(
|
125
|
+
performance_log_file,
|
126
|
+
level="INFO",
|
127
|
+
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {extra[correlation_id]} | {message}",
|
128
|
+
rotation="20 MB",
|
129
|
+
retention="7 days",
|
130
|
+
filter=lambda record: (
|
131
|
+
record["extra"].setdefault("correlation_id", self.correlation_id)
|
132
|
+
or record.get("extra", {}).get("performance", False)
|
133
|
+
),
|
134
|
+
)
|
135
|
+
|
136
|
+
# Bind correlation ID
|
137
|
+
loguru_logger = loguru_logger.bind(correlation_id=self.correlation_id)
|
138
|
+
|
139
|
+
def _setup_standard_logging(self, enable_console: bool, enable_file: bool) -> None:
|
140
|
+
"""Setup standard logging as fallback."""
|
141
|
+
logger = logging.getLogger(self.name)
|
142
|
+
logger.setLevel(getattr(logging, self.level))
|
143
|
+
|
144
|
+
# Console handler
|
145
|
+
if enable_console:
|
146
|
+
console_handler = logging.StreamHandler(sys.stderr)
|
147
|
+
console_formatter = logging.Formatter(
|
148
|
+
"%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d - %(message)s"
|
149
|
+
)
|
150
|
+
console_handler.setFormatter(console_formatter)
|
151
|
+
logger.addHandler(console_handler)
|
152
|
+
|
153
|
+
# File handler
|
154
|
+
if enable_file:
|
155
|
+
file_handler = logging.FileHandler(self.log_dir / "runbooks.log")
|
156
|
+
file_formatter = logging.Formatter(
|
157
|
+
"%(asctime)s | %(levelname)-8s | %(name)s:%(funcName)s:%(lineno)d - %(message)s"
|
158
|
+
)
|
159
|
+
file_handler.setFormatter(file_formatter)
|
160
|
+
logger.addHandler(file_handler)
|
161
|
+
|
162
|
+
def info(self, message: str, **kwargs) -> None:
|
163
|
+
"""Log info message."""
|
164
|
+
if _HAS_LOGURU:
|
165
|
+
loguru_logger.bind(**kwargs).info(message)
|
166
|
+
else:
|
167
|
+
logging.getLogger(self.name).info(message)
|
168
|
+
|
169
|
+
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)
|
175
|
+
|
176
|
+
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)
|
182
|
+
|
183
|
+
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)
|
189
|
+
|
190
|
+
def critical(self, message: str, **kwargs) -> None:
|
191
|
+
"""Log critical message."""
|
192
|
+
if _HAS_LOGURU:
|
193
|
+
loguru_logger.bind(**kwargs).critical(message)
|
194
|
+
else:
|
195
|
+
logging.getLogger(self.name).critical(message)
|
196
|
+
|
197
|
+
|
198
|
+
class AuditLogger:
|
199
|
+
"""Specialized logger for audit trails and compliance."""
|
200
|
+
|
201
|
+
def __init__(self, log_dir: Optional[Path] = None):
|
202
|
+
"""Initialize audit logger."""
|
203
|
+
self.log_dir = log_dir or Path.home() / ".runbooks" / "logs"
|
204
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
205
|
+
self.audit_file = self.log_dir / "audit.log"
|
206
|
+
|
207
|
+
def log_operation(
|
208
|
+
self,
|
209
|
+
operation: str,
|
210
|
+
user: Optional[str] = None,
|
211
|
+
resource: Optional[str] = None,
|
212
|
+
success: bool = True,
|
213
|
+
details: Optional[Dict[str, Any]] = None,
|
214
|
+
correlation_id: Optional[str] = None,
|
215
|
+
) -> None:
|
216
|
+
"""
|
217
|
+
Log audit operation.
|
218
|
+
|
219
|
+
Args:
|
220
|
+
operation: Operation performed
|
221
|
+
user: User who performed the operation
|
222
|
+
resource: Resource affected
|
223
|
+
success: Whether operation was successful
|
224
|
+
details: Additional operation details
|
225
|
+
correlation_id: Correlation ID for tracking
|
226
|
+
"""
|
227
|
+
audit_entry = {
|
228
|
+
"timestamp": datetime.utcnow().isoformat(),
|
229
|
+
"correlation_id": correlation_id,
|
230
|
+
"operation": operation,
|
231
|
+
"user": user or "system",
|
232
|
+
"resource": resource,
|
233
|
+
"success": success,
|
234
|
+
"details": details or {},
|
235
|
+
}
|
236
|
+
|
237
|
+
if _HAS_LOGURU:
|
238
|
+
loguru_logger.bind(audit=True, **audit_entry).info(
|
239
|
+
f"AUDIT: {operation} - {'SUCCESS' if success else 'FAILED'}"
|
240
|
+
)
|
241
|
+
else:
|
242
|
+
# Fallback to direct file writing
|
243
|
+
with open(self.audit_file, "a") as f:
|
244
|
+
f.write(json.dumps(audit_entry) + "\n")
|
245
|
+
|
246
|
+
|
247
|
+
class PerformanceLogger:
|
248
|
+
"""Specialized logger for performance monitoring."""
|
249
|
+
|
250
|
+
def __init__(self, log_dir: Optional[Path] = None):
|
251
|
+
"""Initialize performance logger."""
|
252
|
+
self.log_dir = log_dir or Path.home() / ".runbooks" / "logs"
|
253
|
+
self.log_dir.mkdir(parents=True, exist_ok=True)
|
254
|
+
self.performance_file = self.log_dir / "performance.log"
|
255
|
+
|
256
|
+
def log_performance(
|
257
|
+
self,
|
258
|
+
operation: str,
|
259
|
+
duration: float,
|
260
|
+
success: bool = True,
|
261
|
+
details: Optional[Dict[str, Any]] = None,
|
262
|
+
correlation_id: Optional[str] = None,
|
263
|
+
) -> None:
|
264
|
+
"""
|
265
|
+
Log performance metrics.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
operation: Operation name
|
269
|
+
duration: Duration in seconds
|
270
|
+
success: Whether operation was successful
|
271
|
+
details: Additional performance details
|
272
|
+
correlation_id: Correlation ID for tracking
|
273
|
+
"""
|
274
|
+
perf_entry = {
|
275
|
+
"timestamp": datetime.utcnow().isoformat(),
|
276
|
+
"correlation_id": correlation_id,
|
277
|
+
"operation": operation,
|
278
|
+
"duration_seconds": round(duration, 3),
|
279
|
+
"success": success,
|
280
|
+
"details": details or {},
|
281
|
+
}
|
282
|
+
|
283
|
+
if _HAS_LOGURU:
|
284
|
+
loguru_logger.bind(performance=True, **perf_entry).info(
|
285
|
+
f"PERFORMANCE: {operation} completed in {duration:.3f}s"
|
286
|
+
)
|
287
|
+
else:
|
288
|
+
# Fallback to direct file writing
|
289
|
+
with open(self.performance_file, "a") as f:
|
290
|
+
f.write(json.dumps(perf_entry) + "\n")
|
291
|
+
|
292
|
+
@contextmanager
|
293
|
+
def measure_operation(
|
294
|
+
self,
|
295
|
+
operation: str,
|
296
|
+
details: Optional[Dict[str, Any]] = None,
|
297
|
+
correlation_id: Optional[str] = None,
|
298
|
+
):
|
299
|
+
"""
|
300
|
+
Context manager for measuring operation performance.
|
301
|
+
|
302
|
+
Args:
|
303
|
+
operation: Operation name
|
304
|
+
details: Additional performance details
|
305
|
+
correlation_id: Correlation ID for tracking
|
306
|
+
"""
|
307
|
+
start_time = time.time()
|
308
|
+
success = True
|
309
|
+
try:
|
310
|
+
yield
|
311
|
+
except Exception:
|
312
|
+
success = False
|
313
|
+
raise
|
314
|
+
finally:
|
315
|
+
duration = time.time() - start_time
|
316
|
+
self.log_performance(
|
317
|
+
operation=operation,
|
318
|
+
duration=duration,
|
319
|
+
success=success,
|
320
|
+
details=details,
|
321
|
+
correlation_id=correlation_id,
|
322
|
+
)
|
323
|
+
|
324
|
+
|
325
|
+
def configure_enterprise_logging(
|
326
|
+
level: str = "INFO",
|
327
|
+
log_dir: Optional[Union[str, Path]] = None,
|
328
|
+
correlation_id: Optional[str] = None,
|
329
|
+
enable_audit: bool = True,
|
330
|
+
enable_performance: bool = True,
|
331
|
+
) -> EnterpriseLogger:
|
332
|
+
"""
|
333
|
+
Configure enterprise logging for the application.
|
334
|
+
|
335
|
+
Args:
|
336
|
+
level: Log level
|
337
|
+
log_dir: Log directory path
|
338
|
+
correlation_id: Correlation ID for tracking
|
339
|
+
enable_audit: Enable audit logging
|
340
|
+
enable_performance: Enable performance logging
|
341
|
+
|
342
|
+
Returns:
|
343
|
+
Configured enterprise logger
|
344
|
+
"""
|
345
|
+
if isinstance(log_dir, str):
|
346
|
+
log_dir = Path(log_dir)
|
347
|
+
|
348
|
+
return EnterpriseLogger(
|
349
|
+
level=level,
|
350
|
+
log_dir=log_dir,
|
351
|
+
correlation_id=correlation_id,
|
352
|
+
enable_audit=enable_audit,
|
353
|
+
)
|
354
|
+
|
355
|
+
|
356
|
+
def log_operation_performance(
|
357
|
+
operation_name: Optional[str] = None,
|
358
|
+
details: Optional[Dict[str, Any]] = None,
|
359
|
+
):
|
360
|
+
"""
|
361
|
+
Decorator for logging operation performance.
|
362
|
+
|
363
|
+
Args:
|
364
|
+
operation_name: Name of operation (defaults to function name)
|
365
|
+
details: Additional details to log
|
366
|
+
"""
|
367
|
+
|
368
|
+
def decorator(func):
|
369
|
+
@wraps(func)
|
370
|
+
def wrapper(*args, **kwargs):
|
371
|
+
op_name = operation_name or func.__name__
|
372
|
+
perf_logger = PerformanceLogger()
|
373
|
+
|
374
|
+
with perf_logger.measure_operation(
|
375
|
+
operation=op_name,
|
376
|
+
details=details,
|
377
|
+
):
|
378
|
+
return func(*args, **kwargs)
|
379
|
+
|
380
|
+
return wrapper
|
381
|
+
|
382
|
+
return decorator
|
383
|
+
|
384
|
+
|
385
|
+
def log_audit_operation(
|
386
|
+
operation_name: Optional[str] = None,
|
387
|
+
resource_extractor: Optional[callable] = None,
|
388
|
+
):
|
389
|
+
"""
|
390
|
+
Decorator for logging audit operations.
|
391
|
+
|
392
|
+
Args:
|
393
|
+
operation_name: Name of operation (defaults to function name)
|
394
|
+
resource_extractor: Function to extract resource from arguments
|
395
|
+
"""
|
396
|
+
|
397
|
+
def decorator(func):
|
398
|
+
@wraps(func)
|
399
|
+
def wrapper(*args, **kwargs):
|
400
|
+
op_name = operation_name or func.__name__
|
401
|
+
resource = None
|
402
|
+
|
403
|
+
if resource_extractor:
|
404
|
+
try:
|
405
|
+
resource = resource_extractor(*args, **kwargs)
|
406
|
+
except Exception:
|
407
|
+
pass
|
408
|
+
|
409
|
+
audit_logger = AuditLogger()
|
410
|
+
success = True
|
411
|
+
|
412
|
+
try:
|
413
|
+
result = func(*args, **kwargs)
|
414
|
+
return result
|
415
|
+
except Exception:
|
416
|
+
success = False
|
417
|
+
raise
|
418
|
+
finally:
|
419
|
+
audit_logger.log_operation(
|
420
|
+
operation=op_name,
|
421
|
+
resource=resource,
|
422
|
+
success=success,
|
423
|
+
)
|
424
|
+
|
425
|
+
return wrapper
|
426
|
+
|
427
|
+
return decorator
|
428
|
+
|
429
|
+
|
430
|
+
# Global logger instance
|
431
|
+
_global_logger: Optional[EnterpriseLogger] = None
|
432
|
+
|
433
|
+
|
434
|
+
def get_logger() -> EnterpriseLogger:
|
435
|
+
"""Get global enterprise logger instance."""
|
436
|
+
global _global_logger
|
437
|
+
if _global_logger is None:
|
438
|
+
_global_logger = configure_enterprise_logging()
|
439
|
+
return _global_logger
|