config.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. from enum import IntFlag, auto
  2. from typing import List, Optional, Sequence
  3. """
  4. OpenTelemetry configuration for redis-py.
  5. This module handles configuration for OTel observability features,
  6. including parsing environment variables and validating settings.
  7. """
  8. class MetricGroup(IntFlag):
  9. """Metric groups that can be enabled/disabled."""
  10. RESILIENCY = auto()
  11. CONNECTION_BASIC = auto()
  12. CONNECTION_ADVANCED = auto()
  13. COMMAND = auto()
  14. CSC = auto()
  15. STREAMING = auto()
  16. PUBSUB = auto()
  17. class TelemetryOption(IntFlag):
  18. """Telemetry options to export."""
  19. METRICS = auto()
  20. def default_operation_duration_buckets() -> Sequence[float]:
  21. return [
  22. 0.0001,
  23. 0.00025,
  24. 0.0005,
  25. 0.001,
  26. 0.0025,
  27. 0.005,
  28. 0.01,
  29. 0.025,
  30. 0.05,
  31. 0.1,
  32. 0.25,
  33. 0.5,
  34. 1,
  35. 2.5,
  36. ]
  37. def default_histogram_buckets() -> Sequence[float]:
  38. return [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10]
  39. class OTelConfig:
  40. """
  41. Configuration for OpenTelemetry observability in redis-py.
  42. This class manages all OTel-related settings including metrics, traces (future),
  43. and logs (future). Configuration can be provided via constructor parameters or
  44. environment variables (OTEL_* spec).
  45. Constructor parameters take precedence over environment variables.
  46. Args:
  47. enabled_telemetry: Enabled telemetry options to export (default: metrics). Traces and logs will be added
  48. in future phases.
  49. metric_groups: Group of metrics that should be exported.
  50. include_commands: Explicit allowlist of commands to track
  51. exclude_commands: Blocklist of commands to track
  52. hide_pubsub_channel_names: If True, hide PubSub channel names in metrics (default: False)
  53. hide_stream_names: If True, hide stream names in streaming metrics (default: False)
  54. Note:
  55. Redis-py uses the global MeterProvider set by your application.
  56. Set it up before initializing observability:
  57. from opentelemetry import metrics
  58. from opentelemetry.sdk.metrics import MeterProvider
  59. from opentelemetry.sdk.metrics._internal.view import View
  60. from opentelemetry.sdk.metrics._internal.aggregation import ExplicitBucketHistogramAggregation
  61. # Configure histogram bucket boundaries via Views
  62. views = [
  63. View(
  64. instrument_name="db.client.operation.duration",
  65. aggregation=ExplicitBucketHistogramAggregation(
  66. boundaries=[0.0001, 0.00025, 0.0005, 0.001, 0.0025, 0.005,
  67. 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5]
  68. ),
  69. ),
  70. # Add more views for other histograms...
  71. ]
  72. provider = MeterProvider(views=views, metric_readers=[reader])
  73. metrics.set_meter_provider(provider)
  74. # Then initialize redis-py observability
  75. from redis.observability import get_observability_instance, OTelConfig
  76. otel = get_observability_instance()
  77. otel.init(OTelConfig())
  78. """
  79. DEFAULT_TELEMETRY = TelemetryOption.METRICS
  80. DEFAULT_METRIC_GROUPS = MetricGroup.CONNECTION_BASIC | MetricGroup.RESILIENCY
  81. def __init__(
  82. self,
  83. # Core enablement
  84. enabled_telemetry: Optional[List[TelemetryOption]] = None,
  85. # Metrics-specific
  86. metric_groups: Optional[List[MetricGroup]] = None,
  87. # Redis-specific telemetry controls
  88. include_commands: Optional[List[str]] = None,
  89. exclude_commands: Optional[List[str]] = None,
  90. # Privacy controls
  91. hide_pubsub_channel_names: bool = False,
  92. hide_stream_names: bool = False,
  93. # Bucket sizes
  94. buckets_operation_duration: Sequence[
  95. float
  96. ] = default_operation_duration_buckets(),
  97. buckets_stream_processing_duration: Sequence[
  98. float
  99. ] = default_histogram_buckets(),
  100. buckets_connection_create_time: Sequence[float] = default_histogram_buckets(),
  101. buckets_connection_wait_time: Sequence[float] = default_histogram_buckets(),
  102. ):
  103. # Core enablement
  104. if enabled_telemetry is None:
  105. self.enabled_telemetry = self.DEFAULT_TELEMETRY
  106. else:
  107. self.enabled_telemetry = TelemetryOption(0)
  108. for option in enabled_telemetry:
  109. self.enabled_telemetry |= option
  110. # Enable default metrics if None given
  111. if metric_groups is None:
  112. self.metric_groups = self.DEFAULT_METRIC_GROUPS
  113. else:
  114. self.metric_groups = MetricGroup(0)
  115. for metric_group in metric_groups:
  116. self.metric_groups |= metric_group
  117. # Redis-specific controls
  118. self.include_commands = set(include_commands) if include_commands else None
  119. self.exclude_commands = set(exclude_commands) if exclude_commands else set()
  120. # Privacy controls for hiding sensitive names in metrics
  121. self.hide_pubsub_channel_names = hide_pubsub_channel_names
  122. self.hide_stream_names = hide_stream_names
  123. # Bucket sizes
  124. self.buckets_operation_duration = buckets_operation_duration
  125. self.buckets_stream_processing_duration = buckets_stream_processing_duration
  126. self.buckets_connection_create_time = buckets_connection_create_time
  127. self.buckets_connection_wait_time = buckets_connection_wait_time
  128. def is_enabled(self) -> bool:
  129. """Check if any observability feature is enabled."""
  130. return bool(self.enabled_telemetry)
  131. def should_track_command(self, command_name: str) -> bool:
  132. """
  133. Determine if a command should be tracked based on include/exclude lists.
  134. Args:
  135. command_name: The Redis command name (e.g., 'GET', 'SET')
  136. Returns:
  137. True if the command should be tracked, False otherwise
  138. """
  139. command_upper = command_name.upper()
  140. # If include list is specified, only track commands in the list
  141. if self.include_commands is not None:
  142. return command_upper in self.include_commands
  143. # Otherwise, track all commands except those in exclude list
  144. return command_upper not in self.exclude_commands
  145. def __repr__(self) -> str:
  146. return f"OTelConfig(enabled_telemetry={self.enabled_telemetry}"