Voice et bot modif
This commit is contained in:
parent
189d56026b
commit
7333a22bcd
10774 changed files with 634644 additions and 933308 deletions
|
|
@ -0,0 +1,371 @@
|
|||
"""
|
||||
OpenTelemetry provider management for redis-py.
|
||||
|
||||
This module handles initialization and lifecycle management of OTel SDK components
|
||||
including MeterProvider, TracerProvider (future), and LoggerProvider (future).
|
||||
|
||||
Uses a singleton pattern - initialize once globally, all Redis clients use it automatically.
|
||||
|
||||
Redis-py uses the global MeterProvider set by your application. Set it up before
|
||||
initializing observability:
|
||||
|
||||
from opentelemetry import metrics
|
||||
from opentelemetry.sdk.metrics import MeterProvider
|
||||
|
||||
provider = MeterProvider(...)
|
||||
metrics.set_meter_provider(provider)
|
||||
|
||||
# Then initialize redis-py observability
|
||||
otel = get_observability_instance()
|
||||
otel.init(OTelConfig(enable_metrics=True))
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from redis.observability.config import OTelConfig
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Optional imports - OTel SDK may not be installed
|
||||
try:
|
||||
from opentelemetry.sdk.metrics import MeterProvider
|
||||
|
||||
OTEL_AVAILABLE = True
|
||||
except ImportError:
|
||||
OTEL_AVAILABLE = False
|
||||
MeterProvider = None
|
||||
|
||||
# Global singleton instance
|
||||
_global_provider_manager: Optional["OTelProviderManager"] = None
|
||||
|
||||
|
||||
class OTelProviderManager:
|
||||
"""
|
||||
Manages OpenTelemetry SDK providers and their lifecycle.
|
||||
|
||||
This class handles:
|
||||
- Getting the global MeterProvider set by the application
|
||||
- Configuring histogram bucket boundaries via Views
|
||||
- Graceful shutdown
|
||||
|
||||
Args:
|
||||
config: OTel configuration object
|
||||
"""
|
||||
|
||||
def __init__(self, config: OTelConfig):
|
||||
self.config = config
|
||||
self._meter_provider: Optional[MeterProvider] = None
|
||||
|
||||
def get_meter_provider(self) -> Optional[MeterProvider]:
|
||||
"""
|
||||
Get the global MeterProvider set by the application.
|
||||
|
||||
Returns:
|
||||
MeterProvider instance or None if metrics are disabled
|
||||
|
||||
Raises:
|
||||
ImportError: If OpenTelemetry is not installed
|
||||
RuntimeError: If metrics are enabled but no global MeterProvider is set
|
||||
"""
|
||||
if not self.config.is_enabled():
|
||||
return None
|
||||
|
||||
# Lazy import - only import OTel when metrics are enabled
|
||||
try:
|
||||
from opentelemetry import metrics
|
||||
from opentelemetry.metrics import NoOpMeterProvider
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"OpenTelemetry is not installed. Install it with:\n"
|
||||
" pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http"
|
||||
)
|
||||
|
||||
# Get the global MeterProvider
|
||||
if self._meter_provider is None:
|
||||
self._meter_provider = metrics.get_meter_provider()
|
||||
|
||||
# Check if it's a real provider (not NoOp)
|
||||
if isinstance(self._meter_provider, NoOpMeterProvider):
|
||||
raise RuntimeError(
|
||||
"Metrics are enabled but no global MeterProvider is configured.\n"
|
||||
"\n"
|
||||
"Set up OpenTelemetry before initializing redis-py observability:\n"
|
||||
"\n"
|
||||
" from opentelemetry import metrics\n"
|
||||
" from opentelemetry.sdk.metrics import MeterProvider\n"
|
||||
" from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader\n"
|
||||
" from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter\n"
|
||||
"\n"
|
||||
" # Create exporter\n"
|
||||
" exporter = OTLPMetricExporter(\n"
|
||||
" endpoint='http://localhost:4318/v1/metrics'\n"
|
||||
" )\n"
|
||||
"\n"
|
||||
" # Create reader\n"
|
||||
" reader = PeriodicExportingMetricReader(\n"
|
||||
" exporter=exporter,\n"
|
||||
" export_interval_millis=10000\n"
|
||||
" )\n"
|
||||
"\n"
|
||||
" # Create and set global provider\n"
|
||||
" provider = MeterProvider(metric_readers=[reader])\n"
|
||||
" metrics.set_meter_provider(provider)\n"
|
||||
"\n"
|
||||
" # Now initialize redis-py observability\n"
|
||||
" from redis.observability import get_observability_instance, OTelConfig\n"
|
||||
" otel = get_observability_instance()\n"
|
||||
" otel.init(OTelConfig(enable_metrics=True))\n"
|
||||
)
|
||||
|
||||
logger.info("Using global MeterProvider from application")
|
||||
|
||||
return self._meter_provider
|
||||
|
||||
def shutdown(self, timeout_millis: int = 30000) -> bool:
|
||||
"""
|
||||
Shutdown observability and flush any pending metrics.
|
||||
|
||||
Note: We don't shutdown the global MeterProvider since it's owned by the application.
|
||||
We only force flush pending metrics.
|
||||
|
||||
Args:
|
||||
timeout_millis: Maximum time to wait for flush
|
||||
|
||||
Returns:
|
||||
True if flush was successful, False otherwise
|
||||
"""
|
||||
logger.debug(
|
||||
"Flushing metrics before shutdown (not shutting down global MeterProvider)"
|
||||
)
|
||||
return self.force_flush(timeout_millis=timeout_millis)
|
||||
|
||||
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
||||
"""
|
||||
Force flush any pending metrics from the global MeterProvider.
|
||||
|
||||
Args:
|
||||
timeout_millis: Maximum time to wait for flush
|
||||
|
||||
Returns:
|
||||
True if flush was successful, False otherwise
|
||||
"""
|
||||
if self._meter_provider is None:
|
||||
return True
|
||||
|
||||
# NoOpMeterProvider doesn't have force_flush method
|
||||
if not hasattr(self._meter_provider, "force_flush"):
|
||||
logger.debug("MeterProvider does not support force_flush, skipping")
|
||||
return True
|
||||
|
||||
try:
|
||||
logger.debug("Force flushing metrics from global MeterProvider")
|
||||
self._meter_provider.force_flush(timeout_millis=timeout_millis)
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Error flushing metrics: {e}")
|
||||
return False
|
||||
|
||||
def __enter__(self):
|
||||
"""Context manager entry."""
|
||||
return self
|
||||
|
||||
def __exit__(self, _exc_type, _exc_val, _exc_tb):
|
||||
"""Context manager exit - shutdown provider."""
|
||||
self.shutdown()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"OTelProviderManager(config={self.config})"
|
||||
|
||||
|
||||
# Singleton instance class
|
||||
|
||||
|
||||
class ObservabilityInstance:
|
||||
"""
|
||||
Singleton instance for managing OpenTelemetry observability.
|
||||
|
||||
This class follows the singleton pattern similar to Glide's GetOtelInstance().
|
||||
Use GetObservabilityInstance() to get the singleton instance, then call init()
|
||||
to initialize observability.
|
||||
|
||||
Example:
|
||||
>>> from redis.observability.config import OTelConfig
|
||||
>>>
|
||||
>>> # Get singleton instance
|
||||
>>> otel = get_observability_instance()
|
||||
>>>
|
||||
>>> # Initialize once at app startup
|
||||
>>> otel.init(OTelConfig())
|
||||
>>>
|
||||
>>> # All Redis clients now automatically collect metrics
|
||||
>>> import redis
|
||||
>>> r = redis.Redis(host='localhost', port=6379)
|
||||
>>> r.set('key', 'value') # Metrics collected automatically
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._provider_manager: Optional[OTelProviderManager] = None
|
||||
|
||||
def init(self, config: OTelConfig) -> "ObservabilityInstance":
|
||||
"""
|
||||
Initialize OpenTelemetry observability globally for all Redis clients.
|
||||
|
||||
This should be called once at application startup. After initialization,
|
||||
all Redis clients will automatically collect and export metrics without
|
||||
needing any additional configuration.
|
||||
|
||||
Safe to call multiple times - will shutdown previous instance before
|
||||
initializing a new one.
|
||||
|
||||
Args:
|
||||
config: OTel configuration object
|
||||
|
||||
Returns:
|
||||
Self for method chaining
|
||||
|
||||
Example:
|
||||
>>> otel = get_observability_instance()
|
||||
>>> otel.init(OTelConfig())
|
||||
"""
|
||||
if self._provider_manager is not None:
|
||||
logger.warning(
|
||||
"Observability already initialized. Shutting down previous instance."
|
||||
)
|
||||
self._provider_manager.shutdown()
|
||||
|
||||
self._provider_manager = OTelProviderManager(config)
|
||||
|
||||
logger.info("Observability initialized")
|
||||
|
||||
return self
|
||||
|
||||
def is_enabled(self) -> bool:
|
||||
"""
|
||||
Check if observability is enabled.
|
||||
|
||||
Returns:
|
||||
True if observability is initialized and metrics are enabled
|
||||
|
||||
Example:
|
||||
>>> otel = get_observability_instance()
|
||||
>>> if otel.is_enabled():
|
||||
... print("Metrics are being collected")
|
||||
"""
|
||||
return (
|
||||
self._provider_manager is not None
|
||||
and self._provider_manager.config.is_enabled()
|
||||
)
|
||||
|
||||
def get_provider_manager(self) -> Optional[OTelProviderManager]:
|
||||
"""
|
||||
Get the provider manager instance.
|
||||
|
||||
Returns:
|
||||
The provider manager, or None if not initialized
|
||||
|
||||
Example:
|
||||
>>> otel = get_observability_instance()
|
||||
>>> manager = otel.get_provider_manager()
|
||||
>>> if manager is not None:
|
||||
... print(f"Observability enabled: {manager.config.is_enabled()}")
|
||||
"""
|
||||
return self._provider_manager
|
||||
|
||||
def shutdown(self, timeout_millis: int = 30000) -> bool:
|
||||
"""
|
||||
Shutdown observability and flush any pending metrics.
|
||||
|
||||
This should be called at application shutdown to ensure all metrics
|
||||
are exported before the application exits.
|
||||
|
||||
Args:
|
||||
timeout_millis: Maximum time to wait for shutdown
|
||||
|
||||
Returns:
|
||||
True if shutdown was successful
|
||||
|
||||
Example:
|
||||
>>> otel = get_observability_instance()
|
||||
>>> # At application shutdown
|
||||
>>> otel.shutdown()
|
||||
"""
|
||||
if self._provider_manager is None:
|
||||
logger.debug("Observability not initialized, nothing to shutdown")
|
||||
return True
|
||||
|
||||
success = self._provider_manager.shutdown(timeout_millis)
|
||||
self._provider_manager = None
|
||||
logger.info("Observability shutdown")
|
||||
|
||||
return success
|
||||
|
||||
def force_flush(self, timeout_millis: int = 30000) -> bool:
|
||||
"""
|
||||
Force flush all pending metrics immediately.
|
||||
|
||||
Useful for testing or when you want to ensure metrics are exported
|
||||
before a specific point in your application.
|
||||
|
||||
Args:
|
||||
timeout_millis: Maximum time to wait for flush
|
||||
|
||||
Returns:
|
||||
True if flush was successful
|
||||
|
||||
Example:
|
||||
>>> otel = get_observability_instance()
|
||||
>>> # Execute some Redis commands
|
||||
>>> r.set('key', 'value')
|
||||
>>> # Force flush metrics immediately
|
||||
>>> otel.force_flush()
|
||||
"""
|
||||
if self._provider_manager is None:
|
||||
logger.debug("Observability not initialized, nothing to flush")
|
||||
return True
|
||||
|
||||
return self._provider_manager.force_flush(timeout_millis)
|
||||
|
||||
|
||||
# Global singleton instance
|
||||
_observability_instance: Optional[ObservabilityInstance] = None
|
||||
|
||||
|
||||
def get_observability_instance() -> ObservabilityInstance:
|
||||
"""
|
||||
Get the global observability singleton instance.
|
||||
|
||||
This is the Pythonic way to get the singleton instance.
|
||||
|
||||
Returns:
|
||||
The global ObservabilityInstance singleton
|
||||
|
||||
Example:
|
||||
>>>
|
||||
>>> otel = get_observability_instance()
|
||||
>>> otel.init(OTelConfig())
|
||||
"""
|
||||
global _observability_instance
|
||||
|
||||
if _observability_instance is None:
|
||||
_observability_instance = ObservabilityInstance()
|
||||
|
||||
return _observability_instance
|
||||
|
||||
|
||||
def reset_observability_instance() -> None:
|
||||
"""
|
||||
Reset the global observability singleton instance.
|
||||
|
||||
This is primarily used for testing and benchmarking to ensure
|
||||
a clean state between test runs.
|
||||
|
||||
Warning:
|
||||
This will shutdown any active provider manager and reset
|
||||
the global state. Use with caution in production code.
|
||||
"""
|
||||
global _observability_instance
|
||||
|
||||
if _observability_instance is not None:
|
||||
_observability_instance.shutdown()
|
||||
_observability_instance = None
|
||||
Loading…
Add table
Add a link
Reference in a new issue