python-etcd3-async 0.1.0__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.
- etcd3/__init__.py +90 -0
- etcd3/async_client.py +1495 -0
- etcd3/async_client_extensions.py +878 -0
- etcd3/async_endpoint.py +140 -0
- etcd3/async_managers.py +498 -0
- etcd3/async_monitor.py +16 -0
- etcd3/async_watch.py +462 -0
- etcd3/client.py +2021 -0
- etcd3/endpoint.py +79 -0
- etcd3/events.py +40 -0
- etcd3/exceptions.py +47 -0
- etcd3/exporters/__init__.py +18 -0
- etcd3/exporters/prometheus.py +365 -0
- etcd3/leases.py +106 -0
- etcd3/locks.py +593 -0
- etcd3/log.py +146 -0
- etcd3/log_formatters.py +228 -0
- etcd3/log_sampling.py +221 -0
- etcd3/members.py +44 -0
- etcd3/observability.py +431 -0
- etcd3/shared.py +486 -0
- etcd3/trace.py +231 -0
- etcd3/trace_attributes.py +434 -0
- etcd3/transactions.py +280 -0
- etcd3/utils.py +158 -0
- etcd3/watch.py +270 -0
- etcdrpc/__init__.py +16 -0
- etcdrpc/auth.proto +49 -0
- etcdrpc/auth_pb2.py +43 -0
- etcdrpc/auth_pb2.pyi +44 -0
- etcdrpc/auth_pb2_grpc.py +24 -0
- etcdrpc/kv.proto +43 -0
- etcdrpc/kv_pb2.py +40 -0
- etcdrpc/kv_pb2.pyi +39 -0
- etcdrpc/kv_pb2_grpc.py +24 -0
- etcdrpc/rpc.proto +924 -0
- etcdrpc/rpc_pb2.py +237 -0
- etcdrpc/rpc_pb2.pyi +811 -0
- etcdrpc/rpc_pb2_grpc.py +1893 -0
- python_etcd3_async-0.1.0.dist-info/METADATA +328 -0
- python_etcd3_async-0.1.0.dist-info/RECORD +42 -0
- python_etcd3_async-0.1.0.dist-info/WHEEL +4 -0
etcd3/observability.py
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
"""Unified observability configuration for etcd3 client.
|
|
2
|
+
|
|
3
|
+
This module provides a centralized configuration for tracing, logging, and
|
|
4
|
+
metrics, allowing users to enable and configure observability features
|
|
5
|
+
through a single interface.
|
|
6
|
+
|
|
7
|
+
Environment Variables (all prefixed with ETCD_OBS_):
|
|
8
|
+
- ETCD_OBS_AUTO_INIT: Auto-initialize on first client create (default: true)
|
|
9
|
+
- ETCD_OBS_LOG_LEVEL: Log level (DEBUG, INFO, WARNING, ERROR)
|
|
10
|
+
- ETCD_OBS_LOG_FORMAT: Log format (text, json, colored)
|
|
11
|
+
- ETCD_OBS_LOG_SAMPLE_RATE: Log sampling rate (0.0-1.0)
|
|
12
|
+
- ETCD_OBS_LOG_SAMPLE_STRATEGY: Sampling strategy (always, rate, dynamic, level_based)
|
|
13
|
+
- ETCD_OBS_TRACING_ENABLED: Enable OpenTelemetry tracing (true/false)
|
|
14
|
+
- ETCD_OBS_TRACING_SERVICE_NAME: Service name for tracing
|
|
15
|
+
- ETCD_OBS_TRACING_SAMPLE_RATE: Trace sampling rate (0.0-1.0)
|
|
16
|
+
- ETCD_OBS_METRICS_ENABLED: Enable metrics collection (true/false)
|
|
17
|
+
- ETCD_OBS_PROMETHEUS_ENABLED: Enable Prometheus export (true/false)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass, field
|
|
21
|
+
from typing import Dict, Optional
|
|
22
|
+
import logging
|
|
23
|
+
import os
|
|
24
|
+
|
|
25
|
+
from etcd3.log_sampling import LogSampler, SamplingStrategy, set_sampler
|
|
26
|
+
from etcd3.log_formatters import configure_formatter
|
|
27
|
+
|
|
28
|
+
ENV_PREFIX = "ETCD_OBS_"
|
|
29
|
+
|
|
30
|
+
_observability_initialized: bool = False
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _parse_bool(value: str | None, default: bool) -> bool:
|
|
34
|
+
"""Parse boolean from environment variable."""
|
|
35
|
+
if value is None:
|
|
36
|
+
return default
|
|
37
|
+
return value.lower() in ("true", "1", "yes", "on")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _parse_float(value: str | None, default: float) -> float:
|
|
41
|
+
"""Parse float from environment variable."""
|
|
42
|
+
if value is None:
|
|
43
|
+
return default
|
|
44
|
+
try:
|
|
45
|
+
return float(value)
|
|
46
|
+
except (ValueError, TypeError):
|
|
47
|
+
return default
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _parse_strategy(value: str | None) -> SamplingStrategy:
|
|
51
|
+
"""Parse sampling strategy from environment variable."""
|
|
52
|
+
if value is None:
|
|
53
|
+
return SamplingStrategy.ALWAYS
|
|
54
|
+
strategy_map = {
|
|
55
|
+
"always": SamplingStrategy.ALWAYS,
|
|
56
|
+
"rate": SamplingStrategy.RATE,
|
|
57
|
+
"dynamic": SamplingStrategy.DYNAMIC,
|
|
58
|
+
"level_based": SamplingStrategy.LEVEL_BASED,
|
|
59
|
+
}
|
|
60
|
+
return strategy_map.get(value.lower(), SamplingStrategy.ALWAYS)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class ObservabilityConfig:
|
|
65
|
+
"""Unified observability configuration.
|
|
66
|
+
|
|
67
|
+
Provides a single point of configuration for all observability features
|
|
68
|
+
including distributed tracing, structured logging, and metrics.
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
>>> config = ObservabilityConfig(
|
|
72
|
+
... tracing_enabled=True,
|
|
73
|
+
... log_format="json",
|
|
74
|
+
... log_level="INFO",
|
|
75
|
+
... )
|
|
76
|
+
>>> config.apply()
|
|
77
|
+
|
|
78
|
+
Attributes:
|
|
79
|
+
tracing_enabled: Enable OpenTelemetry tracing
|
|
80
|
+
tracing_service_name: Service name for tracing
|
|
81
|
+
tracing_sample_rate: Trace sampling rate (0.0-1.0)
|
|
82
|
+
log_level: Log level (DEBUG, INFO, WARNING, ERROR)
|
|
83
|
+
log_format: Log format (text, json, colored)
|
|
84
|
+
log_sample_rate: Log sampling rate (0.0-1.0)
|
|
85
|
+
log_sampling_strategy: Log sampling strategy
|
|
86
|
+
log_level_overrides: Per-operation log level overrides
|
|
87
|
+
metrics_enabled: Enable metrics collection
|
|
88
|
+
inject_trace_to_logs: Include trace context in logs
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
# ===== Tracing Configuration =====
|
|
92
|
+
tracing_enabled: bool = True
|
|
93
|
+
tracing_service_name: str = "etcd3-client"
|
|
94
|
+
tracing_sample_rate: float = 1.0
|
|
95
|
+
|
|
96
|
+
# ===== Logging Configuration =====
|
|
97
|
+
log_level: str = "INFO"
|
|
98
|
+
log_format: str = "text" # text, json, colored
|
|
99
|
+
log_sample_rate: float = 1.0
|
|
100
|
+
log_sampling_strategy: SamplingStrategy = SamplingStrategy.ALWAYS
|
|
101
|
+
log_level_overrides: Dict[str, str] = field(default_factory=dict)
|
|
102
|
+
|
|
103
|
+
# ===== Metrics Configuration =====
|
|
104
|
+
metrics_enabled: bool = True
|
|
105
|
+
prometheus_enabled: bool = False
|
|
106
|
+
|
|
107
|
+
# ===== Integration Configuration =====
|
|
108
|
+
inject_trace_to_logs: bool = True
|
|
109
|
+
|
|
110
|
+
def __post_init__(self):
|
|
111
|
+
"""Validate configuration and initialize components."""
|
|
112
|
+
self._validate()
|
|
113
|
+
self._init_sampler()
|
|
114
|
+
|
|
115
|
+
def _validate(self):
|
|
116
|
+
"""Validate configuration values.
|
|
117
|
+
|
|
118
|
+
:raises ValueError: If configuration is invalid
|
|
119
|
+
"""
|
|
120
|
+
valid_formats = ["text", "json", "colored"]
|
|
121
|
+
if self.log_format not in valid_formats:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"log_format must be one of {valid_formats}, got '{self.log_format}'"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if not 0.0 <= self.tracing_sample_rate <= 1.0:
|
|
127
|
+
raise ValueError(
|
|
128
|
+
f"tracing_sample_rate must be between 0.0 and 1.0, "
|
|
129
|
+
f"got {self.tracing_sample_rate}"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if not 0.0 <= self.log_sample_rate <= 1.0:
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f"log_sample_rate must be between 0.0 and 1.0, "
|
|
135
|
+
f"got {self.log_sample_rate}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Validate log level
|
|
139
|
+
valid_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
140
|
+
if self.log_level.upper() not in valid_levels:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
f"log_level must be one of {valid_levels}, got '{self.log_level}'"
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def _init_sampler(self):
|
|
146
|
+
"""Initialize log sampler based on configuration."""
|
|
147
|
+
self._sampler = LogSampler(
|
|
148
|
+
strategy=self.log_sampling_strategy,
|
|
149
|
+
rate=self.log_sample_rate,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def apply(self):
|
|
153
|
+
"""Apply configuration to all observability components.
|
|
154
|
+
|
|
155
|
+
This method configures:
|
|
156
|
+
- Logging level and format
|
|
157
|
+
- Tracing initialization
|
|
158
|
+
- Log sampling
|
|
159
|
+
- Prometheus metrics (if enabled)
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
>>> config = ObservabilityConfig(log_format="json")
|
|
163
|
+
>>> config.apply()
|
|
164
|
+
>>> # Now all etcd3 logs will be in JSON format
|
|
165
|
+
"""
|
|
166
|
+
from etcd3.log import get_logger, set_log_level
|
|
167
|
+
|
|
168
|
+
# 1. Configure log level
|
|
169
|
+
set_log_level(self.log_level)
|
|
170
|
+
|
|
171
|
+
# 2. Configure log format
|
|
172
|
+
logger = get_logger()
|
|
173
|
+
|
|
174
|
+
# Clear existing handlers and add new one
|
|
175
|
+
logger.handlers.clear()
|
|
176
|
+
handler = logging.StreamHandler()
|
|
177
|
+
handler.setFormatter(configure_formatter(self.log_format))
|
|
178
|
+
logger.addHandler(handler)
|
|
179
|
+
|
|
180
|
+
# 3. Initialize tracing if enabled
|
|
181
|
+
if self.tracing_enabled:
|
|
182
|
+
from etcd3.trace import init_tracing
|
|
183
|
+
|
|
184
|
+
init_tracing(
|
|
185
|
+
service_name=self.tracing_service_name,
|
|
186
|
+
sample_rate=self.tracing_sample_rate,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# 4. Set global log sampler
|
|
190
|
+
set_sampler(self._sampler)
|
|
191
|
+
|
|
192
|
+
# 5. Initialize Prometheus metrics if enabled
|
|
193
|
+
if self.prometheus_enabled:
|
|
194
|
+
self._init_prometheus()
|
|
195
|
+
|
|
196
|
+
# Mark as initialized
|
|
197
|
+
global _observability_initialized
|
|
198
|
+
_observability_initialized = True
|
|
199
|
+
|
|
200
|
+
def _init_prometheus(self):
|
|
201
|
+
"""Initialize Prometheus exporter."""
|
|
202
|
+
try:
|
|
203
|
+
from etcd3.exporters.prometheus import (
|
|
204
|
+
init_exporter,
|
|
205
|
+
is_prometheus_available,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if not is_prometheus_available():
|
|
209
|
+
import logging
|
|
210
|
+
|
|
211
|
+
logging.getLogger("etcd3").warning(
|
|
212
|
+
"prometheus_client not installed. "
|
|
213
|
+
"Install with: pip install prometheus-client"
|
|
214
|
+
)
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
init_exporter()
|
|
218
|
+
except Exception as e:
|
|
219
|
+
import logging
|
|
220
|
+
|
|
221
|
+
logging.getLogger("etcd3").warning(
|
|
222
|
+
f"Failed to initialize Prometheus exporter: {e}"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def get_prometheus_metrics(self) -> Optional[bytes]:
|
|
226
|
+
"""Get Prometheus metrics in text format.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
Prometheus metrics text format or None if not available
|
|
230
|
+
"""
|
|
231
|
+
if not self.prometheus_enabled:
|
|
232
|
+
return None
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
from etcd3.exporters.prometheus import get_exporter
|
|
236
|
+
|
|
237
|
+
exporter = get_exporter()
|
|
238
|
+
if exporter:
|
|
239
|
+
return exporter.metrics()
|
|
240
|
+
except Exception:
|
|
241
|
+
pass
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
def get_log_level_for_operation(self, operation: str) -> int:
|
|
245
|
+
"""Get the effective log level for a specific operation.
|
|
246
|
+
|
|
247
|
+
Checks for operation-specific overrides before returning the
|
|
248
|
+
default log level.
|
|
249
|
+
|
|
250
|
+
:param operation: Operation name (e.g., "kv.get", "lease.keepalive")
|
|
251
|
+
:returns: Log level as integer (logging.DEBUG, etc.)
|
|
252
|
+
|
|
253
|
+
Example:
|
|
254
|
+
>>> config = ObservabilityConfig(
|
|
255
|
+
... log_level="INFO",
|
|
256
|
+
... log_level_overrides={"lease.keepalive": "WARNING"}
|
|
257
|
+
... )
|
|
258
|
+
>>> config.get_log_level_for_operation("kv.get")
|
|
259
|
+
20 # INFO
|
|
260
|
+
>>> config.get_log_level_for_operation("lease.keepalive")
|
|
261
|
+
30 # WARNING
|
|
262
|
+
"""
|
|
263
|
+
level_str = self.log_level_overrides.get(operation)
|
|
264
|
+
if level_str:
|
|
265
|
+
return getattr(logging, level_str.upper(), logging.INFO)
|
|
266
|
+
return getattr(logging, self.log_level.upper(), logging.INFO)
|
|
267
|
+
|
|
268
|
+
def should_log_operation(self, operation: str, level: int) -> bool:
|
|
269
|
+
"""Check if an operation should be logged at the given level.
|
|
270
|
+
|
|
271
|
+
Combines operation-specific level configuration with global
|
|
272
|
+
sampling strategy.
|
|
273
|
+
|
|
274
|
+
:param operation: Operation name
|
|
275
|
+
:param level: Log level to check
|
|
276
|
+
:returns: True if should log
|
|
277
|
+
"""
|
|
278
|
+
effective_level = self.get_log_level_for_operation(operation)
|
|
279
|
+
if level < effective_level:
|
|
280
|
+
return False
|
|
281
|
+
|
|
282
|
+
return self._sampler.should_log(level, operation)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def get_default_config() -> ObservabilityConfig:
|
|
286
|
+
"""Get default observability configuration.
|
|
287
|
+
|
|
288
|
+
Creates a configuration with sensible defaults suitable for
|
|
289
|
+
most production deployments.
|
|
290
|
+
|
|
291
|
+
:returns: Default ObservabilityConfig
|
|
292
|
+
|
|
293
|
+
Example:
|
|
294
|
+
>>> config = get_default_config()
|
|
295
|
+
>>> config.apply()
|
|
296
|
+
"""
|
|
297
|
+
return ObservabilityConfig(
|
|
298
|
+
tracing_enabled=True,
|
|
299
|
+
tracing_service_name="etcd3-client",
|
|
300
|
+
log_level="INFO",
|
|
301
|
+
log_format="text",
|
|
302
|
+
log_sample_rate=1.0,
|
|
303
|
+
metrics_enabled=True,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def get_development_config() -> ObservabilityConfig:
|
|
308
|
+
"""Get development-friendly observability configuration.
|
|
309
|
+
|
|
310
|
+
Creates a configuration optimized for development with verbose
|
|
311
|
+
logging and full tracing.
|
|
312
|
+
|
|
313
|
+
:returns: Development ObservabilityConfig
|
|
314
|
+
|
|
315
|
+
Example:
|
|
316
|
+
>>> config = get_development_config()
|
|
317
|
+
>>> config.apply()
|
|
318
|
+
"""
|
|
319
|
+
return ObservabilityConfig(
|
|
320
|
+
tracing_enabled=True,
|
|
321
|
+
tracing_service_name="etcd3-client-dev",
|
|
322
|
+
log_level="DEBUG",
|
|
323
|
+
log_format="colored",
|
|
324
|
+
log_sample_rate=1.0,
|
|
325
|
+
metrics_enabled=True,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def get_production_config() -> ObservabilityConfig:
|
|
330
|
+
"""Get production-optimized observability configuration.
|
|
331
|
+
|
|
332
|
+
Creates a configuration optimized for production with structured
|
|
333
|
+
logging, sampling, and moderate log levels.
|
|
334
|
+
|
|
335
|
+
:returns: Production ObservabilityConfig
|
|
336
|
+
|
|
337
|
+
Example:
|
|
338
|
+
>>> config = get_production_config()
|
|
339
|
+
>>> config.apply()
|
|
340
|
+
"""
|
|
341
|
+
return ObservabilityConfig(
|
|
342
|
+
tracing_enabled=True,
|
|
343
|
+
tracing_service_name="etcd3-client",
|
|
344
|
+
tracing_sample_rate=0.1, # Sample 10% of traces
|
|
345
|
+
log_level="INFO",
|
|
346
|
+
log_format="json",
|
|
347
|
+
log_sample_rate=0.5, # Sample 50% of logs
|
|
348
|
+
log_sampling_strategy=SamplingStrategy.RATE,
|
|
349
|
+
log_level_overrides={
|
|
350
|
+
"lease.keepalive": "WARNING", # Reduce keepalive noise
|
|
351
|
+
},
|
|
352
|
+
metrics_enabled=True,
|
|
353
|
+
prometheus_enabled=True,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def configure_from_env() -> ObservabilityConfig:
|
|
358
|
+
"""Create configuration from environment variables.
|
|
359
|
+
|
|
360
|
+
All environment variables are prefixed with ETCD_OBS_:
|
|
361
|
+
- ETCD_OBS_AUTO_INIT: Auto-initialize on first client create (default: true)
|
|
362
|
+
- ETCD_OBS_LOG_LEVEL: DEBUG, INFO, WARNING, ERROR
|
|
363
|
+
- ETCD_OBS_LOG_FORMAT: text, json, colored
|
|
364
|
+
- ETCD_OBS_LOG_SAMPLE_RATE: Float 0.0-1.0
|
|
365
|
+
- ETCD_OBS_LOG_SAMPLE_STRATEGY: always, rate, dynamic, level_based
|
|
366
|
+
- ETCD_OBS_TRACING_ENABLED: true or false
|
|
367
|
+
- ETCD_OBS_TRACING_SERVICE_NAME: Service name
|
|
368
|
+
- ETCD_OBS_TRACING_SAMPLE_RATE: Float 0.0-1.0
|
|
369
|
+
- ETCD_OBS_METRICS_ENABLED: true or false
|
|
370
|
+
- ETCD_OBS_PROMETHEUS_ENABLED: true or false
|
|
371
|
+
|
|
372
|
+
:returns: Configured ObservabilityConfig
|
|
373
|
+
"""
|
|
374
|
+
return ObservabilityConfig(
|
|
375
|
+
log_level=os.environ.get(f"{ENV_PREFIX}LOG_LEVEL", "INFO"),
|
|
376
|
+
log_format=os.environ.get(f"{ENV_PREFIX}LOG_FORMAT", "text"),
|
|
377
|
+
log_sample_rate=_parse_float(
|
|
378
|
+
os.environ.get(f"{ENV_PREFIX}LOG_SAMPLE_RATE"), 1.0
|
|
379
|
+
),
|
|
380
|
+
log_sampling_strategy=_parse_strategy(
|
|
381
|
+
os.environ.get(f"{ENV_PREFIX}LOG_SAMPLE_STRATEGY")
|
|
382
|
+
),
|
|
383
|
+
tracing_enabled=_parse_bool(
|
|
384
|
+
os.environ.get(f"{ENV_PREFIX}TRACING_ENABLED"), True
|
|
385
|
+
),
|
|
386
|
+
tracing_service_name=os.environ.get(
|
|
387
|
+
f"{ENV_PREFIX}TRACING_SERVICE_NAME", "etcd3-client"
|
|
388
|
+
),
|
|
389
|
+
tracing_sample_rate=_parse_float(
|
|
390
|
+
os.environ.get(f"{ENV_PREFIX}TRACING_SAMPLE_RATE"), 1.0
|
|
391
|
+
),
|
|
392
|
+
metrics_enabled=_parse_bool(
|
|
393
|
+
os.environ.get(f"{ENV_PREFIX}METRICS_ENABLED"), True
|
|
394
|
+
),
|
|
395
|
+
prometheus_enabled=_parse_bool(
|
|
396
|
+
os.environ.get(f"{ENV_PREFIX}PROMETHEUS_ENABLED"), False
|
|
397
|
+
),
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def auto_init() -> bool:
|
|
402
|
+
"""Automatically initialize observability based on environment variables.
|
|
403
|
+
|
|
404
|
+
This function is called on first client creation to set up logging,
|
|
405
|
+
tracing, and metrics if not already configured.
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
True if initialization was performed, False if already initialized
|
|
409
|
+
"""
|
|
410
|
+
global _observability_initialized
|
|
411
|
+
|
|
412
|
+
if _observability_initialized:
|
|
413
|
+
return False
|
|
414
|
+
|
|
415
|
+
# Check if auto-init is enabled (default: true)
|
|
416
|
+
if not _parse_bool(os.environ.get(f"{ENV_PREFIX}AUTO_INIT"), True):
|
|
417
|
+
return False
|
|
418
|
+
|
|
419
|
+
config = configure_from_env()
|
|
420
|
+
config.apply()
|
|
421
|
+
_observability_initialized = True
|
|
422
|
+
return True
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def is_initialized() -> bool:
|
|
426
|
+
"""Check if observability has been initialized.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
True if observability has been initialized
|
|
430
|
+
"""
|
|
431
|
+
return _observability_initialized
|