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

1# -*- coding: utf-8 -*- 

2# SPDX-FileCopyrightText: 2025 Anaconda, Inc 

3# SPDX-License-Identifier: Apache-2.0 

4 

5# config.py 

6""" 

7Anaconda Telemetry - Configuration Module 

8 

9This module provides the configuration setting from a file or a dictionary (or both) 

10""" 

11 

12from typing import Dict, Any, List 

13import re, os, grpc, warnings, functools 

14 

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""" 

20 

21 

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 

33 

34 

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. 

41 

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. 

61 

62 To initializes the Configuration instance. 

63 

64 config = Configuration(default_endpoint='example.com:4317').set_auth_token('<token_here>') 

65 

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_' 

71 

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' 

94 

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 ] 

119 

120 _endpoint_names: List[str] = [ 

121 DEFAULT_ENDPOINT_NAME, 

122 LOGGING_ENDPOINT_NAME, 

123 TRACING_ENDPOINT_NAME, 

124 METRICS_ENDPOINT_NAME 

125 ] 

126 

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 ] 

133 

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 ] 

140 

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 ] 

148 

149 _int_value_names: List[str] = [ 

150 METRICS_EXPORT_INTERVAL_MS_NAME 

151 ] 

152 

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. 

157 

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 {}. 

163 

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) 

171 

172 if default_endpoint is not None: 

173 endpoint = self._Endpoint(default_endpoint) 

174 self._config[self.DEFAULT_ENDPOINT_NAME] = endpoint.url 

175 

176 if default_auth_token is not None: 

177 self._config[self.DEFAULT_AUTH_TOKEN_NAME] = default_auth_token 

178 

179 if default_private_ca_cert_file is not None: 

180 self._config[self.DEFAULT_CA_CERT_NAME] = default_private_ca_cert_file 

181 

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() 

188 

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.") 

192 

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 

201 

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'] 

206 

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 

210 

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]}") 

218 

219 self._metric_defs: Dict[str,Configuration._MetricInfo] = {} 

220 

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. 

227 

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. 

232 

233 Returns: 

234 Self 

235 

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 

246 

247 return self 

248 

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. 

255 

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. 

260 

261 Returns: 

262 Self 

263 

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 

275 

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. 

282 

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. 

287 

288 Returns: 

289 Self 

290 

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 

302 

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. 

309 

310 $ export OTEL_USE_CONSOLE_EXPORTER=TRUE 

311 

312 Args: 

313 use_console (bool): True to use console exporter, False otherwise. 

314 

315 Returns: 

316 Self 

317 """ 

318 self._config[self.USE_CONSOLE_EXPORTER_NAME] = use_console 

319 return self 

320 

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. 

327 

328 Args: 

329 auth_token (str): Authentication token to be used with the endpoints. 

330 

331 Returns: 

332 Self 

333 """ 

334 self._config[self.DEFAULT_AUTH_TOKEN_NAME] = auth_token 

335 return self 

336 

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. 

342 

343 Args: 

344 auth_token (str): Authentication token to be used with the endpoints. 

345 

346 Returns: 

347 Self 

348 """ 

349 self._config[self.LOGGING_AUTH_TOKEN_NAME] = auth_token 

350 return self 

351 

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. 

357 

358 Args: 

359 auth_token (str): Authentication token to be used with the endpoints. 

360 

361 Returns: 

362 Self 

363 """ 

364 self._config[self.TRACING_AUTH_TOKEN_NAME] = auth_token 

365 return self 

366 

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. 

372 

373 Args: 

374 auth_token (str): Authentication token to be used with the endpoints. 

375 

376 Returns: 

377 Self 

378 """ 

379 self._config[self.METRICS_AUTH_TOKEN_NAME] = auth_token 

380 return self 

381 

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. 

391 

392 Args: 

393 cert_file (str): File location of CA cert file intended for use 

394 

395 Returns: 

396 Self 

397 """ 

398 self._config[self.DEFAULT_CA_CERT_NAME] = cert_file 

399 return self 

400 

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. 

410 

411 Args: 

412 cert_file (str): File location of CA cert file intended for use 

413 

414 Returns: 

415 Self 

416 """ 

417 self._config[self.LOGGING_CA_CERT_NAME] = cert_file 

418 return self 

419 

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. 

429 

430 Args: 

431 cert_file (str): File location of CA cert file intended for use 

432 

433 Returns: 

434 Self 

435 """ 

436 self._config[self.TRACING_CA_CERT_NAME] = cert_file 

437 return self 

438 

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. 

448 

449 Args: 

450 cert_file (str): File location of CA cert file intended for use 

451 

452 Returns: 

453 Self 

454 """ 

455 self._config[self.METRICS_CA_CERT_NAME] = cert_file 

456 return self 

457 

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. 

464 

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. 

467 

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 

475 

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. 

482 

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. 

486 

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 

494 

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. 

501 

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. 

505 

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 

513 

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. 

519 

520 Args: 

521 session_entropy (Any): Session entropy to be used for tracing. 

522 

523 Returns: 

524 Self 

525 """ 

526 self._config[self.SESSION_ENTROPY_VALUE_NAME] = session_entropy 

527 return self 

528 

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. 

533 

534 Args: 

535 value (bool): True to skip the internet check, False otherwise. 

536 

537 Returns: 

538 Self 

539 """ 

540 self._config[self.SKIP_INTERNET_CHECK_NAME] = value 

541 return self 

542 

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). 

547 

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. 

551 

552 Args: 

553 value (bool): True turns on cumulative aggregation, False (the default) is to send 

554 deltas (no aggregation). 

555 

556 Returns: 

557 Self 

558 """ 

559 self._config[self.USE_CUMULATIVE_METRICS_NAME] = value 

560 return self 

561 

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'. 

568 

569 Args: 

570 proxy_url (str): The proxy URL (e.g. 'http://proxy.example.com:8080'). 

571 

572 Returns: 

573 Self 

574 """ 

575 self._config[self.PROXY_URL_NAME] = proxy_url 

576 return self 

577 

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'. 

585 

586 Args: 

587 value (bool): True to register atexit handlers, False to manage shutdown manually. 

588 

589 Returns: 

590 Self 

591 """ 

592 self._config[self.SHUTDOWN_ON_EXIT_NAME] = value 

593 return self 

594 

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'. 

601 

602 Args: 

603 value (bool): True to show export errors, False to suppress them. 

604 

605 Returns: 

606 Self 

607 """ 

608 self._config[self.VERBOSE_EXPORT_ERRORS_NAME] = value 

609 return self 

610 

611 def _get_proxy_url(self) -> str: 

612 return self._config.get(self.PROXY_URL_NAME, None) 

613 

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 

625 

626 

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()) 

637 

638 # Getters for configuration settings (internal only for the package) 

639 def _parse_endpoint(self, url: str): 

640 self._validate_endpoint(url) 

641 

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 

649 

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 

656 

657 # prepare whole url 

658 url = f"{self.protocol}://{self.host}" 

659 if self.port: 

660 url += f":{self.port}" 

661 url += self.path 

662 

663 self.url = url 

664 

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 ) 

681 

682 match = pattern.match(endpoint) 

683 if not match: 

684 raise ValueError(f"Invalid endpoint format: {endpoint}") 

685 

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 "" 

691 

692 # Extract protocol 

693 self.protocol = protocol_str.rstrip('://') 

694 # Determine tls 

695 self.tls = True if self.protocol[-1] == 's' else False 

696 

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}") 

707 

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() 

713 

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 

723 

724 def _get_default_endpoint(self) -> str: 

725 return self._config.get(self.DEFAULT_ENDPOINT_NAME, '') 

726 

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") 

730 

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") 

734 

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") 

738 

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 

752 

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) 

756 

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) 

760 

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) 

764 

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) 

768 

769 def _get_console_exporter(self) -> bool: 

770 return self._config.get(self.USE_CONSOLE_EXPORTER_NAME, False) 

771 

772 def _get_auth_token_default(self) -> str: 

773 return self._config.get(self.DEFAULT_AUTH_TOKEN_NAME, None) 

774 

775 def _get_auth_token_logging(self) -> str: 

776 return self._config.get(self.LOGGING_AUTH_TOKEN_NAME, self._get_auth_token_default()) 

777 

778 def _get_auth_token_tracing(self) -> str: 

779 return self._config.get(self.TRACING_AUTH_TOKEN_NAME, self._get_auth_token_default()) 

780 

781 def _get_auth_token_metrics(self) -> str: 

782 return self._config.get(self.METRICS_AUTH_TOKEN_NAME, self._get_auth_token_default()) 

783 

784 def _get_logging_level(self) -> str: 

785 return self._config.get(self.LOGGING_LEVEL_NAME, 'warning') 

786 

787 def _get_metrics_export_interval_ms(self) -> int: 

788 return self._config.get(self.METRICS_EXPORT_INTERVAL_MS_NAME, 60_000) 

789 

790 def _get_tracing_export_interval_ms(self) -> int: 

791 return self._config.get(self.TRACING_EXPORT_INTERVAL_MS_NAME, 60_000) 

792 

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) 

798 

799 def _get_skip_internet_check(self) -> bool: 

800 return self._config.get(self.SKIP_INTERNET_CHECK_NAME, False) 

801 

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] 

805 

806 def _get_TLS_logging(self) -> str: 

807 return self._endpoints.get(self.LOGGING_ENDPOINT_NAME, self._get_TLS_default()).tls 

808 

809 def _get_TLS_metrics(self) -> str: 

810 return self._endpoints.get(self.METRICS_ENDPOINT_NAME, self._get_TLS_default()).tls 

811 

812 def _get_TLS_tracing(self) -> str: 

813 return self._endpoints.get(self.TRACING_ENDPOINT_NAME, self._get_TLS_default()).tls 

814 

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] 

818 

819 def _get_request_protocol_logging(self) -> str: 

820 return self._endpoints.get(self.LOGGING_ENDPOINT_NAME, self._get_request_protocol_default()).protocol 

821 

822 def _get_request_protocol_metrics(self) -> str: 

823 return self._endpoints.get(self.METRICS_ENDPOINT_NAME, self._get_request_protocol_default()).protocol 

824 

825 def _get_request_protocol_tracing(self) -> str: 

826 return self._endpoints.get(self.TRACING_ENDPOINT_NAME, self._get_request_protocol_default()).protocol 

827 

828 def _get_use_cumulative_metrics(self) -> bool: 

829 return self._config.get(self.USE_CUMULATIVE_METRICS_NAME, False) 

830 

831 def _get_shutdown_on_exit(self) -> bool: 

832 return self._config.get(self.SHUTDOWN_ON_EXIT_NAME, True) 

833 

834 def _get_verbose_export_errors(self) -> bool: 

835 return self._config.get(self.VERBOSE_EXPORT_ERRORS_NAME, False)