Coverage for anaconda_opentelemetry/config.py: 99%
309 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-11 20:51 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-11 20:51 +0000
1# -*- coding: utf-8 -*-
2# SPDX-FileCopyrightText: 2025 Anaconda, Inc
3# SPDX-License-Identifier: Apache-2.0
5# config.py
6"""
7Anaconda Telemetry - Configuration Module
9This module provides the configuration setting from a file or a dictionary (or both)
10"""
12from typing import Dict, Any, List
13import re, os, grpc, warnings, functools
15"""
16Configuration class to supply settings for Anaconda Telemetry.
17It allows loading configuration from a JSON or YAML file, or from a dictionary.
18It validates the format of endpoints and ensures they conform to the expected structure.
19"""
22def deprecated(func):
23 # This is a decorator to mark functions as deprecated.
24 @functools.wraps(func)
25 def wrapper(*args, **kwargs):
26 warnings.warn(
27 f"{func.__name__} is deprecated and will be removed in a future version.",
28 category=DeprecationWarning,
29 stacklevel=2
30 )
31 return func(*args, **kwargs)
32 return wrapper
35class Configuration:
36 """
37 Configuration class to supply settings for Anaconda Telemetry. For environment variables make these capitalized and
38 prepend with 'ATEL\\_' and remove suffix '\\_NAME'. For example, the environment variable for the default endpoint would
39 be 'ATEL_DEFAULT_ENDPOINT'. The environment variable for the logging endpoint would be 'ATEL_LOGGING_ENDPOINT'. The bool
40 values can be represented by "1", "yes", "true" case-insensitive and all other values are considered False.
42 - DEFAULT_ENDPOINT_NAME - Name for the default endpoint in the configuration files or dictionaries passed into this class.
43 - LOGGING_ENDPOINT_NAME - Name for the logging endpoint in the configuration files or dictionaries passed into this class.
44 - TRACING_ENDPOINT_NAME - Name for the tracing endpoint in the configuration files or dictionaries passed into this class.
45 - METRICS_ENDPOINT_NAME - Name for the metrics endpoint in the configuration files or dictionaries passed into this class.
46 - USE_CONSOLE_EXPORTER_NAME - Name for the console exporter flag in the configuration files or dictionaries passed into this class.
47 - DEFAULT_AUTH_TOKEN_NAME - Name for the default authentication token in the configuration files or dictionaries passed into this class.
48 - LOGGING_AUTH_TOKEN_NAME - Name for the logging authentication token in the configuration files or dictionaries passed into this class.
49 - TRACING_AUTH_TOKEN_NAME - Name for the tracing authentication token in the configuration files or dictionaries passed into this class.
50 - METRICS_AUTH_TOKEN_NAME - Name for the metrics authentication token in the configuration files or dictionaries passed into this class.
51 - METRICS_EXPORT_INTERVAL_MS_NAME - Name for the metrics export interval in milliseconds in the configuration files or dictionaries passed into this class.
52 - TRACING_EXPORT_INTERVAL_MS_NAME - Name for the tracing export interval in milliseconds in the configuration files or dictionaries passed into this class.
53 - LOGGING_LEVEL_NAME - Name for the logging level in the configuration files or dictionaries passed into this class.
54 - SESSION_ENTROPY_VALUE_NAME - Name for the session entropy value in the configuration files or dictionaries passed into this class.
55 - TLS_PRIVATE_CA_CERT_FILE_NAME - File name for the TLS private CA certificate in the configuration files or dictionaries passed into this class.
56 - SKIP_INTERNET_CHECK_NAME - If you are running in an environment that does not have access to the internet, set this to True.
57 - USE_CUMULATIVE_METRICS_NAME - If aggregating data in the client is required for Counter, or Histogram set this to a True state.
58 - PROXY_URL_NAME - Used to set the proxy for telemetry exporters in this package
59 - SHUTDOWN_ON_EXIT_NAME - If True (default), providers register atexit handlers that flush on interpreter exit. If False, the caller must manually call shutdown_telemetry() or flush_telemetry() before the process exits.
60 - VERBOSE_EXPORT_ERRORS_NAME - If False (default), OpenTelemetry export errors are suppressed. If True, export errors such as "Transient error" will be logged.
62 To initializes the Configuration instance.
64 config = Configuration(default_endpoint='example.com:4317').set_auth_token('<token_here>')
66 Args:
67 default_endpoint (str): Default endpoint in the form '<IPv4|domain_name>:<port>'.
68 config_dict (Dict[str,Any], optional): Optional dictionary containing configuration settings.
69 """
70 __PREFIX__ = 'ATEL_'
72 DEFAULT_ENDPOINT_NAME = 'default_endpoint'
73 LOGGING_ENDPOINT_NAME = 'logging_endpoint'
74 TRACING_ENDPOINT_NAME = 'tracing_endpoint'
75 METRICS_ENDPOINT_NAME = 'metrics_endpoint'
76 USE_CONSOLE_EXPORTER_NAME = 'use_console_exporter'
77 DEFAULT_AUTH_TOKEN_NAME = 'default_auth_token'
78 LOGGING_AUTH_TOKEN_NAME = 'logging_auth_token'
79 TRACING_AUTH_TOKEN_NAME = 'tracing_auth_token'
80 METRICS_AUTH_TOKEN_NAME = 'metrics_auth_token'
81 METRICS_EXPORT_INTERVAL_MS_NAME = 'metrics_export_interval_ms'
82 TRACING_EXPORT_INTERVAL_MS_NAME = 'tracing_export_interval_ms'
83 LOGGING_LEVEL_NAME = 'logging_level'
84 SESSION_ENTROPY_VALUE_NAME = 'session_entropy_value'
85 DEFAULT_CA_CERT_NAME = 'default_credentials'
86 LOGGING_CA_CERT_NAME = 'logging_credentials'
87 TRACING_CA_CERT_NAME = 'tracing_credentials'
88 METRICS_CA_CERT_NAME = 'metrics_credentials'
89 SKIP_INTERNET_CHECK_NAME = 'skip_internet_check'
90 USE_CUMULATIVE_METRICS_NAME = 'use_cumulative_metrics'
91 PROXY_URL_NAME = 'proxy_url'
92 SHUTDOWN_ON_EXIT_NAME = 'shutdown_on_exit'
93 VERBOSE_EXPORT_ERRORS_NAME = 'verbose_export_errors'
95 _base_names: List[str] = [
96 DEFAULT_ENDPOINT_NAME,
97 LOGGING_ENDPOINT_NAME,
98 TRACING_ENDPOINT_NAME,
99 METRICS_ENDPOINT_NAME,
100 USE_CONSOLE_EXPORTER_NAME,
101 DEFAULT_AUTH_TOKEN_NAME,
102 LOGGING_AUTH_TOKEN_NAME,
103 TRACING_AUTH_TOKEN_NAME,
104 METRICS_AUTH_TOKEN_NAME,
105 METRICS_EXPORT_INTERVAL_MS_NAME,
106 TRACING_EXPORT_INTERVAL_MS_NAME,
107 LOGGING_LEVEL_NAME,
108 SESSION_ENTROPY_VALUE_NAME,
109 DEFAULT_CA_CERT_NAME,
110 LOGGING_CA_CERT_NAME,
111 TRACING_CA_CERT_NAME,
112 METRICS_CA_CERT_NAME,
113 SKIP_INTERNET_CHECK_NAME,
114 USE_CUMULATIVE_METRICS_NAME,
115 PROXY_URL_NAME,
116 SHUTDOWN_ON_EXIT_NAME,
117 VERBOSE_EXPORT_ERRORS_NAME,
118 ]
120 _endpoint_names: List[str] = [
121 DEFAULT_ENDPOINT_NAME,
122 LOGGING_ENDPOINT_NAME,
123 TRACING_ENDPOINT_NAME,
124 METRICS_ENDPOINT_NAME
125 ]
127 _credential_names: List[str] = [
128 DEFAULT_CA_CERT_NAME,
129 LOGGING_CA_CERT_NAME,
130 TRACING_CA_CERT_NAME,
131 METRICS_CA_CERT_NAME
132 ]
134 _auth_token_names: List[str] = [
135 DEFAULT_AUTH_TOKEN_NAME,
136 LOGGING_AUTH_TOKEN_NAME,
137 TRACING_AUTH_TOKEN_NAME,
138 METRICS_AUTH_TOKEN_NAME
139 ]
141 _bool_value_names: List[str] = [
142 USE_CONSOLE_EXPORTER_NAME,
143 SKIP_INTERNET_CHECK_NAME,
144 USE_CUMULATIVE_METRICS_NAME,
145 SHUTDOWN_ON_EXIT_NAME,
146 VERBOSE_EXPORT_ERRORS_NAME,
147 ]
149 _int_value_names: List[str] = [
150 METRICS_EXPORT_INTERVAL_MS_NAME
151 ]
153 def __init__(self, default_endpoint: str = None, default_auth_token: str = None,
154 default_private_ca_cert_file: str = None, config_dict: Dict[str, Any] = {}):
155 """
156 Creates the configuration object passed to initialize_telemetry.
158 Args:
159 default_endpoint (str): The endpoint used when not specifying a specific endpoint for a specific signal type. May be None.
160 default_auth_token (str): The default auth token use for the default_endpoint or None.
161 default_private_ca_cert_file (str): File name for the private cert file if used or None. Not used frequently.
162 config_dict (Dict[str,any]): An initialization map to configure the object in bulk or {}.
164 Raises:
165 ValueError: If there is no `default_endpoint` value passed to its arguments or in the `config_dict` kwarg,
166 and no `ATEL_DEFAULT_ENDPOINT` environment variable set.
167 ValueError: Non integer value set for `ATEL_METRICS_EXPORT_INTERVAL_MS_NAME`
168 """
169 self._config: Dict[str, Any] = {}
170 self._config.update(config_dict)
172 if default_endpoint is not None:
173 endpoint = self._Endpoint(default_endpoint)
174 self._config[self.DEFAULT_ENDPOINT_NAME] = endpoint.url
176 if default_auth_token is not None:
177 self._config[self.DEFAULT_AUTH_TOKEN_NAME] = default_auth_token
179 if default_private_ca_cert_file is not None:
180 self._config[self.DEFAULT_CA_CERT_NAME] = default_private_ca_cert_file
182 # Merge environment variables into the config
183 for base_name in self._base_names:
184 env_name = f"{self.__PREFIX__}{base_name.upper()}"
185 env_value = os.environ.get(env_name, None)
186 if env_value is not None:
187 self._config[base_name] = env_value.strip()
189 # Ensure default endpoint is set
190 if self.DEFAULT_ENDPOINT_NAME not in self._config.keys():
191 raise ValueError(f"A '{self.DEFAULT_ENDPOINT_NAME}' must be provided or set in the configuration.")
193 # Check environment vars for endpoints and normalize endpoints
194 self._endpoints = {}
195 for endpoint_name in self._endpoint_names:
196 if endpoint_name in self._config:
197 # set endpoint object for this signal (or default)
198 self._endpoints[endpoint_name] = self._Endpoint(self._config[endpoint_name])
199 # set endpoint config value for this signal (or default)
200 self._config[endpoint_name] = self._endpoints[endpoint_name].url
202 # Normalize bool values
203 for bool_name in self._bool_value_names:
204 if bool_name in self._config and isinstance(self._config[bool_name], str):
205 self._config[bool_name] = self._config[bool_name].lower().strip() in ['true', 'yes', '1', 'on']
207 # Special case OTEL_SDK_DISABLED...
208 if os.environ.get('OTEL_SDK_DISABLED', '').lower().strip() in ['true', 'yes', '1', 'on'] and os.environ.get(self.SKIP_INTERNET_CHECK_NAME, None) is None:
209 self._config[self.SKIP_INTERNET_CHECK_NAME] = True
211 # Normalize the int values
212 for int_name in self._int_value_names:
213 if int_name in self._config and isinstance(self._config[int_name], str):
214 try:
215 self._config[int_name] = int(self._config[int_name].strip())
216 except ValueError:
217 raise ValueError(f"Invalid value for '{int_name}': {self._config[int_name]}")
219 self._metric_defs: Dict[str,Configuration._MetricInfo] = {}
221 def set_logging_endpoint(self, endpoint: str, auth_token: str = None, cert_ca_file: str = None):
222 """
223 Sets the logging endpoint. Intended for usage prior to calling initialize_telemetry(). If this method is
224 called after the initialize_telemetry() call, it will not work. The change_signal_endpoint must be used.
225 If passed in a dict in the constructor, use predefined name LOGGING_ENDPOINT_NAME. If not set,
226 the default endpoint will be used.
228 Args:
229 endpoint (str): Logging endpoint in the form '<IPv4|domain_name>:<port>'.
230 auth_token (str): Bearer auth token for the logging endpoint or None.
231 cert_ca_file (str): Absolute file path to the private cert file for logging or None. Rarely used.
233 Returns:
234 Self
236 Raises:
237 ValueError: If the endpoint format is invalid.
238 """
239 logging_endpoint = self._Endpoint(endpoint)
240 self._config[self.LOGGING_ENDPOINT_NAME] = logging_endpoint.url
241 self._endpoints[self.LOGGING_ENDPOINT_NAME] = logging_endpoint
242 if auth_token is not None:
243 self._config[self.LOGGING_AUTH_TOKEN_NAME] = auth_token
244 if cert_ca_file is not None:
245 self._config[self.LOGGING_CA_CERT_NAME] = cert_ca_file
247 return self
249 def set_tracing_endpoint(self, endpoint: str, auth_token: str = None, cert_ca_file: str = None):
250 """
251 Sets the tracing endpoint. Intended for usage prior to calling initialize_telemetry(). If this method is
252 called after the initialize_telemetry() call, it will not work. The change_signal_endpoint must be used.
253 If passed in a dict in the constructor, use predefined name TRACING_ENDPOINT_NAME. If not set,
254 the default endpoint is used.
256 Args:
257 endpoint (str): Tracing endpoint in the form '<IPv4|domain_name>:<port>'.
258 auth_token (str): Bearer auth token for the tracing endpoint or None.
259 cert_ca_file (str): Absolute file path to the private cert file for tracing or None. Rarely used.
261 Returns:
262 Self
264 Raises:
265 ValueError: If the endpoint format is invalid.
266 """
267 tracing_endpoint = self._Endpoint(endpoint)
268 self._config[self.TRACING_ENDPOINT_NAME] = tracing_endpoint.url
269 self._endpoints[self.TRACING_ENDPOINT_NAME] = tracing_endpoint
270 if auth_token is not None:
271 self._config[self.TRACING_AUTH_TOKEN_NAME] = auth_token
272 if cert_ca_file is not None:
273 self._config[self.TRACING_CA_CERT_NAME] = cert_ca_file
274 return self
276 def set_metrics_endpoint(self, endpoint: str, auth_token: str = None, cert_ca_file: str = None):
277 """
278 Sets the metrics endpoint. Intended for usage prior to calling initialize_telemetry(). If this method is
279 called after the initialize_telemetry() call, it will not work. The change_signal_endpoint must be used.
280 If passed in a dict in the constructor, use predefined name METRICS_ENDPOINT_NAME. If not set,
281 the default endpoint will be used.
283 Args:
284 endpoint (str): Metrics endpoint in the form '<IPv4|domain_name>:<port>'.
285 auth_token (str): Bearer auth token for the metrics endpoint or None.
286 cert_ca_file (str): Absolute file path to the private cert file for metrics or None. Rarely used.
288 Returns:
289 Self
291 Raises:
292 ValueError: If the endpoint format is invalid.
293 """
294 metrics_endpoint = self._Endpoint(endpoint)
295 self._config[self.METRICS_ENDPOINT_NAME] = metrics_endpoint.url
296 self._endpoints[self.METRICS_ENDPOINT_NAME] = metrics_endpoint
297 if auth_token is not None:
298 self._config[self.METRICS_AUTH_TOKEN_NAME] = auth_token
299 if cert_ca_file is not None:
300 self._config[self.METRICS_CA_CERT_NAME] = cert_ca_file
301 return self
303 def set_console_exporter(self, use_console: bool = True):
304 """
305 Sets whether to use console exporter for output. If passed in a dict in the constructor, use predefined name
306 USE_CONSOLE_EXPORTER_NAME. It applies to all exporters (logging, tracing, metrics). This is a convenience
307 used for testing only. Do not set in produiction. Also to set this value without modifying your code use the
308 environment variable 'OTEL_USE_CONSOLE_EXPORTER'. Set this to true, yes, or 1. Case doesn't matter.
310 $ export OTEL_USE_CONSOLE_EXPORTER=TRUE
312 Args:
313 use_console (bool): True to use console exporter, False otherwise.
315 Returns:
316 Self
317 """
318 self._config[self.USE_CONSOLE_EXPORTER_NAME] = use_console
319 return self
321 @deprecated
322 def set_auth_token(self, auth_token: str):
323 """
324 Sets the default authentication token for the endpoints (default endpoint). It is a fallback for all endpoints (default, logging,
325 tracing, metrics). If passed in a dict in the constructor, use predefined name
326 DEFAULT_AUTH_TOKEN_NAME.
328 Args:
329 auth_token (str): Authentication token to be used with the endpoints.
331 Returns:
332 Self
333 """
334 self._config[self.DEFAULT_AUTH_TOKEN_NAME] = auth_token
335 return self
337 @deprecated
338 def set_auth_token_logging(self, auth_token: str):
339 """
340 Sets the authentication token for the logging endpoint. If passed in a dict in the constructor, use predefined name
341 LOGGING_AUTH_TOKEN_NAME.
343 Args:
344 auth_token (str): Authentication token to be used with the endpoints.
346 Returns:
347 Self
348 """
349 self._config[self.LOGGING_AUTH_TOKEN_NAME] = auth_token
350 return self
352 @deprecated
353 def set_auth_token_tracing(self, auth_token: str):
354 """
355 Sets the authentication token for the tracing endpoint. If passed in a dict in the constructor, use predefined name
356 TRACING_AUTH_TOKEN_NAME.
358 Args:
359 auth_token (str): Authentication token to be used with the endpoints.
361 Returns:
362 Self
363 """
364 self._config[self.TRACING_AUTH_TOKEN_NAME] = auth_token
365 return self
367 @deprecated
368 def set_auth_token_metrics(self, auth_token: str):
369 """
370 Sets the authentication token for the metrics endpoint. If passed in a dict in the constructor, use predefined name
371 METRICS_AUTH_TOKEN_NAME.
373 Args:
374 auth_token (str): Authentication token to be used with the endpoints.
376 Returns:
377 Self
378 """
379 self._config[self.METRICS_AUTH_TOKEN_NAME] = auth_token
380 return self
382 @deprecated
383 def set_tls_private_ca_cert(self, cert_file: str):
384 """
385 TLS certificate used for default endpoint only.
386 Sets the actual TLS private CA certificate to be used for secure connections.
387 This is used to verify the server's certificate when using TLS. If passed in
388 a dict in the constructor, use predefined name DEFAULT_CA_CERT_NAME.
389 The caller must pass a file path that will later be utilized to find a cert.
390 Can be used to set CA to None is cert_file is None.
392 Args:
393 cert_file (str): File location of CA cert file intended for use
395 Returns:
396 Self
397 """
398 self._config[self.DEFAULT_CA_CERT_NAME] = cert_file
399 return self
401 @deprecated
402 def set_tls_private_ca_cert_logging(self, cert_file: str):
403 """
404 TLS certificate used for logging endpoint only.
405 Sets the actual TLS private CA certificate to be used for secure connections.
406 This is used to verify the server's certificate when using TLS. If passed in
407 a dict in the constructor, use predefined name LOGGING_CA_CERT_NAME.
408 The caller must pass a file path that will later be utilized to find a cert.
409 Can be used to set CA to None is cert_file is None.
411 Args:
412 cert_file (str): File location of CA cert file intended for use
414 Returns:
415 Self
416 """
417 self._config[self.LOGGING_CA_CERT_NAME] = cert_file
418 return self
420 @deprecated
421 def set_tls_private_ca_cert_tracing(self, cert_file: str):
422 """
423 TLS certificate used for tracing endpoint only.
424 Sets the actual TLS private CA certificate to be used for secure connections.
425 This is used to verify the server's certificate when using TLS. If passed in
426 a dict in the constructor, use predefined name TRACING_CA_CERT_NAME.
427 The caller must pass a file path that will later be utilized to find a cert.
428 Can be used to set CA to None is cert_file is None.
430 Args:
431 cert_file (str): File location of CA cert file intended for use
433 Returns:
434 Self
435 """
436 self._config[self.TRACING_CA_CERT_NAME] = cert_file
437 return self
439 @deprecated
440 def set_tls_private_ca_cert_metrics(self, cert_file: str):
441 """
442 TLS certificate used for metrics endpoint only.
443 Sets the actual TLS private CA certificate to be used for secure connections.
444 This is used to verify the server's certificate when using TLS. If passed in
445 a dict in the constructor, use predefined name METRICS_CA_CERT_NAME.
446 The caller must pass a file path that will later be utilized to find a cert.
447 Can be used to set CA to None is cert_file is None.
449 Args:
450 cert_file (str): File location of CA cert file intended for use
452 Returns:
453 Self
454 """
455 self._config[self.METRICS_CA_CERT_NAME] = cert_file
456 return self
458 def set_logging_level(self, level: str):
459 """
460 Sets the logging level for the telemetry logging to the collector. The built-in Python
461 logging module must be used or logging will not get sent to the server. If passed in a
462 dict in the constructor, use predefined name LOGGING_LEVEL_NAME. This will not affect
463 the logging level of the root logger, only what is sent to OTel.
465 Args:
466 level (str): Logging level to be used. It can be 'debug', 'info', 'warn', 'warning', 'error', 'fatal' or 'critical'. If not one of these strings, the logger level is not set.
468 Returns:
469 Self
470 """
471 if level not in ['debug', 'info', 'warn', 'warning', 'error', 'fatal', 'critical']:
472 return self
473 self._config[self.LOGGING_LEVEL_NAME] = level
474 return self
476 def set_metrics_export_interval_ms(self, interval_ms: int):
477 """
478 Sets the metrics export interval in milliseconds. If this value is not set,
479 the default is 60,000 milliseconds (1 minute). If passed in a dict in the constructor,
480 use predefined name METRICS_EXPORT_INTERVAL_NAME. This dictates how long the batching
481 inside OpenTelemetry lasts before sending to the collector.
483 Args:
484 interval (int): Interval in milliseconds for exporting metrics. If this is zero or
485 negative then the export interval is not set.
487 Returns:
488 Self
489 """
490 if interval_ms <= 0:
491 return self
492 self._config[self.METRICS_EXPORT_INTERVAL_MS_NAME] = interval_ms
493 return self
495 def set_tracing_export_interval_ms(self, interval_ms: int):
496 """
497 Sets the tracing export interval in milliseconds. If this value is not set,
498 the default is 60,000 milliseconds (1 minute). If passed in a dict in the constructor,
499 use predefined name TRACING_EXPORT_INTERVAL_NAME. This dictates how long the batching
500 inside OpenTelemetry lasts before sending to the collector.
502 Args:
503 interval (int): Interval in milliseconds for exporting metrics. If this is zero or
504 negative then the export interval is not set.
506 Returns:
507 Self
508 """
509 if interval_ms <= 0:
510 return self
511 self._config[self.TRACING_EXPORT_INTERVAL_MS_NAME] = interval_ms
512 return self
514 def set_tracing_session_entropy(self, session_entropy):
515 """
516 Sets the session entropy for tracing. This is used to ensure that traces are unique
517 across different sessions. If this value is not set, a default value will be used. If
518 passed in a dict in the constructor, use predefined name SESSION_ENTROPY_VALUE_NAME.
520 Args:
521 session_entropy (Any): Session entropy to be used for tracing.
523 Returns:
524 Self
525 """
526 self._config[self.SESSION_ENTROPY_VALUE_NAME] = session_entropy
527 return self
529 def set_skip_internet_check(self, value: bool):
530 """
531 Sets whether to skip the internet check. This is useful for environments that do not have
532 internet access. If passed in a dict in the constructor, use predefined name SKIP_INTERNET_CHECK_NAME.
534 Args:
535 value (bool): True to skip the internet check, False otherwise.
537 Returns:
538 Self
539 """
540 self._config[self.SKIP_INTERNET_CHECK_NAME] = value
541 return self
543 def set_use_cumulative_metrics(self, value: bool):
544 """
545 Sets the use of cumulative aggregation temporality if True. The default (False) is delta
546 (not aggregated).
548 Cumulative counters report a measurement consistently for each export interval. The would result in "duplicate"
549 metrics. To get metric readings only for the difference between the current count and the previous count, use delta
550 aggregation.
552 Args:
553 value (bool): True turns on cumulative aggregation, False (the default) is to send
554 deltas (no aggregation).
556 Returns:
557 Self
558 """
559 self._config[self.USE_CUMULATIVE_METRICS_NAME] = value
560 return self
562 def set_proxy_url(self, proxy_url: str):
563 """
564 Sets the proxy URL to use for HTTP OTLP exporters. This applies to all HTTP-based
565 signal exporters (logging, tracing, metrics). gRPC exporters are not affected.
566 If passed in a dict in the constructor, use predefined name PROXY_URL_NAME.
567 The environment variable is 'ATEL_PROXY_URL'.
569 Args:
570 proxy_url (str): The proxy URL (e.g. 'http://proxy.example.com:8080').
572 Returns:
573 Self
574 """
575 self._config[self.PROXY_URL_NAME] = proxy_url
576 return self
578 def set_shutdown_on_exit(self, value: bool):
579 """
580 Sets whether providers register atexit handlers that flush on interpreter exit.
581 If True (default), each provider registers an ``atexit`` handler that flushes on interpreter exit.
582 If False, no atexit handlers are registered and the caller becomes responsible for flushing:
583 call ``shutdown_telemetry()`` (or ``flush_telemetry()``) before the process exits, otherwise
584 buffered telemetry is silently dropped.The environment variable is 'ATEL_SHUTDOWN_ON_EXIT'.
586 Args:
587 value (bool): True to register atexit handlers, False to manage shutdown manually.
589 Returns:
590 Self
591 """
592 self._config[self.SHUTDOWN_ON_EXIT_NAME] = value
593 return self
595 def set_verbose_export_errors(self, value: bool):
596 """
597 Sets whether OpenTelemetry export errors are logged to stdout/stderr.
598 If False (default), export errors from the OpenTelemetry SDK are suppressed.
599 If True, export errors such as "Transient error" will be logged by the OpenTelemetry SDK.
600 The environment variable is 'ATEL_VERBOSE_EXPORT_ERRORS'.
602 Args:
603 value (bool): True to show export errors, False to suppress them.
605 Returns:
606 Self
607 """
608 self._config[self.VERBOSE_EXPORT_ERRORS_NAME] = value
609 return self
611 def _get_proxy_url(self) -> str:
612 return self._config.get(self.PROXY_URL_NAME, None)
614 def _create_proxy_session(self):
615 proxy_url = self._get_proxy_url()
616 if proxy_url is None:
617 return None
618 import requests
619 session = requests.Session()
620 session.proxies = {
621 'http': proxy_url,
622 'https': proxy_url
623 }
624 return session
627 class _Endpoint:
628 def __init__(self, endpoint: str):
629 # Properties:
630 # - protocol - protocol of the endpoint passed to the constructor
631 # - host - host of the endpoint passed to the constructor
632 # - port - port of the endpoint passed to the constructor
633 # - path - path of the endpoint passed to the constructor
634 # - valid - whether or not the endpoint is valid
635 # - _internet_check_port - internet check port used for connection check
636 self._parse_endpoint(endpoint.strip())
638 # Getters for configuration settings (internal only for the package)
639 def _parse_endpoint(self, url: str):
640 self._validate_endpoint(url)
642 # Default port for internet check
643 if self.port is None:
644 if self.protocol == 'http':
645 self._internet_check_port = 80
646 else:
647 # HTTPS and gRPC(s) use 443 by default
648 self._internet_check_port = 443
650 # allow default port usage from user specification
651 elif self.port not in (80, 443) and not (1024 <= self.port <= 65535):
652 raise ValueError(f"Invalid endpoint format: {url}")
653 # Internet check port is user port if one is specified and valid
654 else:
655 self._internet_check_port = self.port
657 # prepare whole url
658 url = f"{self.protocol}://{self.host}"
659 if self.port:
660 url += f":{self.port}"
661 url += self.path
663 self.url = url
665 def _validate_endpoint(self, endpoint: str):
666 if endpoint == '':
667 raise ValueError(f"Invalid endpoint format: {endpoint}")
668 pattern = re.compile(
669 r"^"
670 r"(https?://|grpcs?://)" # capture group 1: optional protocol
671 r"(" # capture group 2: host
672 r"(?!0\.)" # Disallow IPs starting with 0.
673 r"(?:\d{1,3}\.){3}\d{1,3}" # IPv4 format (non-capturing group)
674 r"|"
675 r"(?:[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*" # domain segment
676 r"(?:\.[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)*)" # more segments
677 r")"
678 r"(?::(\d{1,5}))?" # capture group 3: optional port
679 r"(/.*)?$" # capture group 4: optional path
680 )
682 match = pattern.match(endpoint)
683 if not match:
684 raise ValueError(f"Invalid endpoint format: {endpoint}")
686 protocol_str = match.group(1)
687 self.host = match.group(2)
688 port = match.group(3)
689 self.port = int(port) if port is not None else None
690 self.path = match.group(4) or ""
692 # Extract protocol
693 self.protocol = protocol_str.rstrip('://')
694 # Determine tls
695 self.tls = True if self.protocol[-1] == 's' else False
697 # If it's an IP, validate each octet
698 if re.match(r"^(\d{1,3}\.)+\d{1,3}$", self.host):
699 quads = list(map(int, self.host.split('.')))
700 if len(quads) != 4:
701 raise ValueError(f"Invalid endpoint format: {endpoint}")
702 if quads[0] == 0 or quads[0] == 255 or quads[3] == 0 or quads[3] == 255:
703 raise ValueError(f"Invalid endpoint format: {endpoint}")
704 for q in quads:
705 if q > 255:
706 raise ValueError(f"Invalid endpoint format: {endpoint}")
708 def _change_signal_endpoint(self, signal: str, new_endpoint: str, auth_token: str=None):
709 set_endpoint = getattr(self, f"set_{signal}_endpoint", None)
710 set_endpoint(new_endpoint, auth_token=auth_token)
711 get_endpoint = getattr(self, f"_get_{signal}_endpoint", None)
712 return get_endpoint()
714 def _set_otel_signal_endpoint(self, endpoint: str, signal: str) -> str:
715 if endpoint.lower().startswith("grpc"):
716 return endpoint
717 endpoint_str = f"v1/{signal}"
718 if not endpoint.endswith(endpoint_str):
719 endpoint_str = "/" + endpoint_str if endpoint[-1] != "/" else endpoint_str
720 return endpoint + endpoint_str
721 else:
722 return endpoint
724 def _get_default_endpoint(self) -> str:
725 return self._config.get(self.DEFAULT_ENDPOINT_NAME, '')
727 def _get_logging_endpoint(self) -> str:
728 endpoint = self._config.get(self.LOGGING_ENDPOINT_NAME, self._get_default_endpoint())
729 return self._set_otel_signal_endpoint(endpoint, "logs")
731 def _get_tracing_endpoint(self) -> str:
732 endpoint = self._config.get(self.TRACING_ENDPOINT_NAME, self._get_default_endpoint())
733 return self._set_otel_signal_endpoint(endpoint, "traces")
735 def _get_metrics_endpoint(self) -> str:
736 endpoint = self._config.get(self.METRICS_ENDPOINT_NAME, self._get_default_endpoint())
737 return self._set_otel_signal_endpoint(endpoint, "metrics")
739 def _prepare_ca_cert(self, protocol: str, cert_file: str) -> str:
740 if protocol in ['http', 'https']:
741 return cert_file # just return cert file for HTTP exporter ca_cert
742 else:
743 if cert_file:
744 with open(cert_file, 'rb') as f:
745 ca_cert_bytes = f.read() # gRPC exporter requires a bytes string
746 creds = grpc.ssl_channel_credentials(root_certificates=ca_cert_bytes)
747 return creds
748 else:
749 # Use system trust store (for public CAs)
750 creds = grpc.ssl_channel_credentials()
751 return creds
753 def _get_ca_cert_default(self) -> str:
754 cert_file = self._config.get(self.DEFAULT_CA_CERT_NAME, None)
755 return self._prepare_ca_cert(self._get_request_protocol_default().protocol, cert_file)
757 def _get_ca_cert_logging(self) -> str:
758 cert_file = self._config.get(self.LOGGING_CA_CERT_NAME, self._get_ca_cert_default())
759 return self._prepare_ca_cert(self._get_request_protocol_logging(), cert_file)
761 def _get_ca_cert_tracing(self) -> str:
762 cert_file = self._config.get(self.TRACING_CA_CERT_NAME, self._get_ca_cert_default())
763 return self._prepare_ca_cert(self._get_request_protocol_tracing(), cert_file)
765 def _get_ca_cert_metrics(self) -> str:
766 cert_file = self._config.get(self.METRICS_CA_CERT_NAME, self._get_ca_cert_default())
767 return self._prepare_ca_cert(self._get_request_protocol_metrics(), cert_file)
769 def _get_console_exporter(self) -> bool:
770 return self._config.get(self.USE_CONSOLE_EXPORTER_NAME, False)
772 def _get_auth_token_default(self) -> str:
773 return self._config.get(self.DEFAULT_AUTH_TOKEN_NAME, None)
775 def _get_auth_token_logging(self) -> str:
776 return self._config.get(self.LOGGING_AUTH_TOKEN_NAME, self._get_auth_token_default())
778 def _get_auth_token_tracing(self) -> str:
779 return self._config.get(self.TRACING_AUTH_TOKEN_NAME, self._get_auth_token_default())
781 def _get_auth_token_metrics(self) -> str:
782 return self._config.get(self.METRICS_AUTH_TOKEN_NAME, self._get_auth_token_default())
784 def _get_logging_level(self) -> str:
785 return self._config.get(self.LOGGING_LEVEL_NAME, 'warning')
787 def _get_metrics_export_interval_ms(self) -> int:
788 return self._config.get(self.METRICS_EXPORT_INTERVAL_MS_NAME, 60_000)
790 def _get_tracing_export_interval_ms(self) -> int:
791 return self._config.get(self.TRACING_EXPORT_INTERVAL_MS_NAME, 60_000)
793 def _get_tracing_session_entropy(self):
794 if self._config.get(self.SESSION_ENTROPY_VALUE_NAME, None) is None:
795 import time
796 self._config[self.SESSION_ENTROPY_VALUE_NAME] = int(time.time() * 1e9)
797 return self._config.get(self.SESSION_ENTROPY_VALUE_NAME)
799 def _get_skip_internet_check(self) -> bool:
800 return self._config.get(self.SKIP_INTERNET_CHECK_NAME, False)
802 def _get_TLS_default(self) -> bool:
803 # will raise if there is no default tls (means there is no default endpoint)
804 return self._endpoints[self.DEFAULT_ENDPOINT_NAME]
806 def _get_TLS_logging(self) -> str:
807 return self._endpoints.get(self.LOGGING_ENDPOINT_NAME, self._get_TLS_default()).tls
809 def _get_TLS_metrics(self) -> str:
810 return self._endpoints.get(self.METRICS_ENDPOINT_NAME, self._get_TLS_default()).tls
812 def _get_TLS_tracing(self) -> str:
813 return self._endpoints.get(self.TRACING_ENDPOINT_NAME, self._get_TLS_default()).tls
815 def _get_request_protocol_default(self) -> str:
816 # will raise if there is no default protocol (means there is no default endpoint)
817 return self._endpoints[self.DEFAULT_ENDPOINT_NAME]
819 def _get_request_protocol_logging(self) -> str:
820 return self._endpoints.get(self.LOGGING_ENDPOINT_NAME, self._get_request_protocol_default()).protocol
822 def _get_request_protocol_metrics(self) -> str:
823 return self._endpoints.get(self.METRICS_ENDPOINT_NAME, self._get_request_protocol_default()).protocol
825 def _get_request_protocol_tracing(self) -> str:
826 return self._endpoints.get(self.TRACING_ENDPOINT_NAME, self._get_request_protocol_default()).protocol
828 def _get_use_cumulative_metrics(self) -> bool:
829 return self._config.get(self.USE_CUMULATIVE_METRICS_NAME, False)
831 def _get_shutdown_on_exit(self) -> bool:
832 return self._config.get(self.SHUTDOWN_ON_EXIT_NAME, True)
834 def _get_verbose_export_errors(self) -> bool:
835 return self._config.get(self.VERBOSE_EXPORT_ERRORS_NAME, False)