Initial commit - production deployment
This commit is contained in:
179
shared/monitoring/decorators.py
Executable file
179
shared/monitoring/decorators.py
Executable file
@@ -0,0 +1,179 @@
|
||||
# ================================================================
|
||||
# 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
|
||||
Reference in New Issue
Block a user