Files
bakery-ia/shared/monitoring/decorators.py
2025-12-13 23:57:54 +01:00

179 lines
7.6 KiB
Python
Executable File

# ================================================================
# shared/monitoring/decorators.py
# ================================================================
"""
Decorators for monitoring and metrics
"""
import time
import logging
import functools
from typing import Callable, Any, Optional
from .metrics import get_metrics_collector
logger = logging.getLogger(__name__)
def track_execution_time(metric_name: str, service_name: str,
labels: Optional[dict] = None):
"""Decorator to track function execution time"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
async def async_wrapper(*args, **kwargs) -> Any:
start_time = time.time()
try:
result = await func(*args, **kwargs)
duration = time.time() - start_time
metrics_collector = get_metrics_collector(service_name)
if metrics_collector:
metrics_collector.observe_histogram(metric_name, duration, labels)
return result
except Exception as e:
duration = time.time() - start_time
logger.error(f"Function {func.__name__} failed after {duration:.2f}s: {e}")
raise
@functools.wraps(func)
def sync_wrapper(*args, **kwargs) -> Any:
start_time = time.time()
try:
result = func(*args, **kwargs)
duration = time.time() - start_time
metrics_collector = get_metrics_collector(service_name)
if metrics_collector:
metrics_collector.observe_histogram(metric_name, duration, labels)
return result
except Exception as e:
duration = time.time() - start_time
logger.error(f"Function {func.__name__} failed after {duration:.2f}s: {e}")
raise
# Return appropriate wrapper based on function type
import asyncio
if asyncio.iscoroutinefunction(func):
return async_wrapper
else:
return sync_wrapper
return decorator
def count_calls(metric_name: str, service_name: str,
labels: Optional[dict] = None):
"""Decorator to count function calls"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
async def async_wrapper(*args, **kwargs) -> Any:
metrics_collector = get_metrics_collector(service_name)
if metrics_collector:
metrics_collector.increment_counter(metric_name, labels=labels)
return await func(*args, **kwargs)
@functools.wraps(func)
def sync_wrapper(*args, **kwargs) -> Any:
metrics_collector = get_metrics_collector(service_name)
if metrics_collector:
metrics_collector.increment_counter(metric_name, labels=labels)
return func(*args, **kwargs)
# Return appropriate wrapper based on function type
import asyncio
if asyncio.iscoroutinefunction(func):
return async_wrapper
else:
return sync_wrapper
return decorator
def monitor_performance(operation_name: str, labels: Optional[dict] = None):
"""
General purpose performance monitoring decorator
Tracks execution time and call counts for the given operation
"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
async def async_wrapper(*args, **kwargs) -> Any:
start_time = time.time()
service_name = "orders-service" # Could be dynamic based on context
try:
# Count the call
metrics_collector = get_metrics_collector(service_name)
if metrics_collector:
call_labels = {**(labels or {}), "operation": operation_name}
metrics_collector.increment_counter(f"{service_name}_operations_total", labels=call_labels)
# Execute the function
result = await func(*args, **kwargs)
# Record success timing
duration = time.time() - start_time
if metrics_collector:
timing_labels = {**(labels or {}), "operation": operation_name, "status": "success"}
metrics_collector.observe_histogram(f"{service_name}_operation_duration_seconds", duration, timing_labels)
return result
except Exception as e:
# Record failure timing
duration = time.time() - start_time
metrics_collector = get_metrics_collector(service_name)
if metrics_collector:
timing_labels = {**(labels or {}), "operation": operation_name, "status": "error"}
metrics_collector.observe_histogram(f"{service_name}_operation_duration_seconds", duration, timing_labels)
error_labels = {**(labels or {}), "operation": operation_name, "error_type": type(e).__name__}
metrics_collector.increment_counter(f"{service_name}_errors_total", labels=error_labels)
logger.error(f"Operation {operation_name} failed after {duration:.2f}s: {e}")
raise
@functools.wraps(func)
def sync_wrapper(*args, **kwargs) -> Any:
start_time = time.time()
service_name = "orders-service" # Could be dynamic based on context
try:
# Count the call
metrics_collector = get_metrics_collector(service_name)
if metrics_collector:
call_labels = {**(labels or {}), "operation": operation_name}
metrics_collector.increment_counter(f"{service_name}_operations_total", labels=call_labels)
# Execute the function
result = func(*args, **kwargs)
# Record success timing
duration = time.time() - start_time
if metrics_collector:
timing_labels = {**(labels or {}), "operation": operation_name, "status": "success"}
metrics_collector.observe_histogram(f"{service_name}_operation_duration_seconds", duration, timing_labels)
return result
except Exception as e:
# Record failure timing
duration = time.time() - start_time
metrics_collector = get_metrics_collector(service_name)
if metrics_collector:
timing_labels = {**(labels or {}), "operation": operation_name, "status": "error"}
metrics_collector.observe_histogram(f"{service_name}_operation_duration_seconds", duration, timing_labels)
error_labels = {**(labels or {}), "operation": operation_name, "error_type": type(e).__name__}
metrics_collector.increment_counter(f"{service_name}_errors_total", labels=error_labels)
logger.error(f"Operation {operation_name} failed after {duration:.2f}s: {e}")
raise
# Return appropriate wrapper based on function type
import asyncio
if asyncio.iscoroutinefunction(func):
return async_wrapper
else:
return sync_wrapper
return decorator