Coverage for anaconda_opentelemetry/config.py: 99%

316 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-01 20:06 +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 - DISABLE_SESSION_ID_NAME - If True, disables automatic session ID generation. If False (default), a session ID is generated and added to telemetry. 

62 

63 To initializes the Configuration instance. 

64 

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

66 

67 Args: 

68 default_endpoint (str): Default endpoint in the form '<IPv4|domain_name>:<port>'. 

69 config_dict (Dict[str,Any], optional): Optional dictionary containing configuration settings. 

70 """ 

71 __PREFIX__ = 'ATEL_' 

72 

73 DEFAULT_ENDPOINT_NAME = 'default_endpoint' 

74 LOGGING_ENDPOINT_NAME = 'logging_endpoint' 

75 TRACING_ENDPOINT_NAME = 'tracing_endpoint' 

76 METRICS_ENDPOINT_NAME = 'metrics_endpoint' 

77 USE_CONSOLE_EXPORTER_NAME = 'use_console_exporter' 

78 DEFAULT_AUTH_TOKEN_NAME = 'default_auth_token' 

79 LOGGING_AUTH_TOKEN_NAME = 'logging_auth_token' 

80 TRACING_AUTH_TOKEN_NAME = 'tracing_auth_token' 

81 METRICS_AUTH_TOKEN_NAME = 'metrics_auth_token' 

82 METRICS_EXPORT_INTERVAL_MS_NAME = 'metrics_export_interval_ms' 

83 TRACING_EXPORT_INTERVAL_MS_NAME = 'tracing_export_interval_ms' 

84 LOGGING_LEVEL_NAME = 'logging_level' 

85 SESSION_ENTROPY_VALUE_NAME = 'session_entropy_value' 

86 DEFAULT_CA_CERT_NAME = 'default_credentials' 

87 LOGGING_CA_CERT_NAME = 'logging_credentials' 

88 TRACING_CA_CERT_NAME = 'tracing_credentials' 

89 METRICS_CA_CERT_NAME = 'metrics_credentials' 

90 SKIP_INTERNET_CHECK_NAME = 'skip_internet_check' 

91 USE_CUMULATIVE_METRICS_NAME = 'use_cumulative_metrics' 

92 PROXY_URL_NAME = 'proxy_url' 

93 SHUTDOWN_ON_EXIT_NAME = 'shutdown_on_exit' 

94 VERBOSE_EXPORT_ERRORS_NAME = 'verbose_export_errors' 

95 DISABLE_SESSION_ID_NAME = 'disable_session_id' 

96 

97 _base_names: List[str] = [ 

98 DEFAULT_ENDPOINT_NAME, 

99 LOGGING_ENDPOINT_NAME, 

100 TRACING_ENDPOINT_NAME, 

101 METRICS_ENDPOINT_NAME, 

102 USE_CONSOLE_EXPORTER_NAME, 

103 DEFAULT_AUTH_TOKEN_NAME, 

104 LOGGING_AUTH_TOKEN_NAME, 

105 TRACING_AUTH_TOKEN_NAME, 

106 METRICS_AUTH_TOKEN_NAME, 

107 METRICS_EXPORT_INTERVAL_MS_NAME, 

108 TRACING_EXPORT_INTERVAL_MS_NAME, 

109 LOGGING_LEVEL_NAME, 

110 SESSION_ENTROPY_VALUE_NAME, 

111 DEFAULT_CA_CERT_NAME, 

112 LOGGING_CA_CERT_NAME, 

113 TRACING_CA_CERT_NAME, 

114 METRICS_CA_CERT_NAME, 

115 SKIP_INTERNET_CHECK_NAME, 

116 USE_CUMULATIVE_METRICS_NAME, 

117 PROXY_URL_NAME, 

118 SHUTDOWN_ON_EXIT_NAME, 

119 VERBOSE_EXPORT_ERRORS_NAME, 

120 DISABLE_SESSION_ID_NAME, 

121 ] 

122 

123 _endpoint_names: List[str] = [ 

124 DEFAULT_ENDPOINT_NAME, 

125 LOGGING_ENDPOINT_NAME, 

126 TRACING_ENDPOINT_NAME, 

127 METRICS_ENDPOINT_NAME 

128 ] 

129 

130 _credential_names: List[str] = [ 

131 DEFAULT_CA_CERT_NAME, 

132 LOGGING_CA_CERT_NAME, 

133 TRACING_CA_CERT_NAME, 

134 METRICS_CA_CERT_NAME 

135 ] 

136 

137 _auth_token_names: List[str] = [ 

138 DEFAULT_AUTH_TOKEN_NAME, 

139 LOGGING_AUTH_TOKEN_NAME, 

140 TRACING_AUTH_TOKEN_NAME, 

141 METRICS_AUTH_TOKEN_NAME 

142 ] 

143 

144 _bool_value_names: List[str] = [ 

145 USE_CONSOLE_EXPORTER_NAME, 

146 SKIP_INTERNET_CHECK_NAME, 

147 USE_CUMULATIVE_METRICS_NAME, 

148 SHUTDOWN_ON_EXIT_NAME, 

149 VERBOSE_EXPORT_ERRORS_NAME, 

150 DISABLE_SESSION_ID_NAME, 

151 ] 

152 

153 _int_value_names: List[str] = [ 

154 METRICS_EXPORT_INTERVAL_MS_NAME 

155 ] 

156 

157 def __init__(self, default_endpoint: str = None, default_auth_token: str = None, 

158 default_private_ca_cert_file: str = None, config_dict: Dict[str, Any] = {}, 

159 ignore_environment_variables: bool = False): 

160 """ 

161 Creates the configuration object passed to initialize_telemetry. 

162 

163 Args: 

164 default_endpoint (str): The endpoint used when not specifying a specific endpoint for a specific signal type. May be None. 

165 default_auth_token (str): The default auth token use for the default_endpoint or None. 

166 default_private_ca_cert_file (str): File name for the private cert file if used or None. Not used frequently. 

167 config_dict (Dict[str,any]): An initialization map to configure the object in bulk or {}. 

168 ignore_environment_variables (bool): If True, all ATEL_* environment variables and OTEL_SDK_DISABLED are 

169 ignored. Only values passed via constructor arguments and config_dict 

170 are used. Defaults to False. 

171 

172 Raises: 

173 ValueError: If there is no `default_endpoint` value passed to its arguments or in the `config_dict` kwarg, 

174 and no `ATEL_DEFAULT_ENDPOINT` environment variable set. 

175 ValueError: Non integer value set for `ATEL_METRICS_EXPORT_INTERVAL_MS_NAME` 

176 """ 

177 self._config: Dict[str, Any] = {} 

178 self._config.update(config_dict) 

179 

180 if default_endpoint is not None: 

181 endpoint = self._Endpoint(default_endpoint) 

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

183 

184 if default_auth_token is not None: 

185 self._config[self.DEFAULT_AUTH_TOKEN_NAME] = default_auth_token 

186 

187 if default_private_ca_cert_file is not None: 

188 self._config[self.DEFAULT_CA_CERT_NAME] = default_private_ca_cert_file 

189 

190 # Merge environment variables into the config 

191 if not ignore_environment_variables: 

192 for base_name in self._base_names: 

193 env_name = f"{self.__PREFIX__}{base_name.upper()}" 

194 env_value = os.environ.get(env_name, None) 

195 if env_value is not None: 

196 self._config[base_name] = env_value.strip() 

197 

198 # Ensure default endpoint is set 

199 if self.DEFAULT_ENDPOINT_NAME not in self._config.keys(): 

200 raise ValueError(f"A '{self.DEFAULT_ENDPOINT_NAME}' must be provided or set in the configuration.") 

201 

202 # Check environment vars for endpoints and normalize endpoints 

203 self._endpoints = {} 

204 for endpoint_name in self._endpoint_names: 

205 if endpoint_name in self._config: 

206 # set endpoint object for this signal (or default) 

207 self._endpoints[endpoint_name] = self._Endpoint(self._config[endpoint_name]) 

208 # set endpoint config value for this signal (or default) 

209 self._config[endpoint_name] = self._endpoints[endpoint_name].url 

210 

211 # Normalize bool values 

212 for bool_name in self._bool_value_names: 

213 if bool_name in self._config and isinstance(self._config[bool_name], str): 

214 self._config[bool_name] = self._config[bool_name].lower().strip() in ['true', 'yes', '1', 'on'] 

215 

216 # Special case OTEL_SDK_DISABLED... 

217 if not ignore_environment_variables and \ 

218 os.environ.get('OTEL_SDK_DISABLED', '').lower().strip() in ['true', 'yes', '1', 'on'] and \ 

219 os.environ.get(self.SKIP_INTERNET_CHECK_NAME, None) is None: 

220 self._config[self.SKIP_INTERNET_CHECK_NAME] = True 

221 

222 # Normalize the int values 

223 for int_name in self._int_value_names: 

224 if int_name in self._config and isinstance(self._config[int_name], str): 

225 try: 

226 self._config[int_name] = int(self._config[int_name].strip()) 

227 except ValueError: 

228 raise ValueError(f"Invalid value for '{int_name}': {self._config[int_name]}") 

229 

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

231 

232 def set_logging_endpoint(self, endpoint: str, auth_token: str = None, cert_ca_file: str = None): 

233 """ 

234 Sets the logging endpoint. Intended for usage prior to calling initialize_telemetry(). If this method is 

235 called after the initialize_telemetry() call, it will not work. The change_signal_endpoint must be used. 

236 If passed in a dict in the constructor, use predefined name LOGGING_ENDPOINT_NAME. If not set, 

237 the default endpoint will be used. 

238 

239 Args: 

240 endpoint (str): Logging endpoint in the form '<IPv4|domain_name>:<port>'. 

241 auth_token (str): Bearer auth token for the logging endpoint or None. 

242 cert_ca_file (str): Absolute file path to the private cert file for logging or None. Rarely used. 

243 

244 Returns: 

245 Self 

246 

247 Raises: 

248 ValueError: If the endpoint format is invalid. 

249 """ 

250 logging_endpoint = self._Endpoint(endpoint) 

251 self._config[self.LOGGING_ENDPOINT_NAME] = logging_endpoint.url 

252 self._endpoints[self.LOGGING_ENDPOINT_NAME] = logging_endpoint 

253 if auth_token is not None: 

254 self._config[self.LOGGING_AUTH_TOKEN_NAME] = auth_token 

255 if cert_ca_file is not None: 

256 self._config[self.LOGGING_CA_CERT_NAME] = cert_ca_file 

257 

258 return self 

259 

260 def set_tracing_endpoint(self, endpoint: str, auth_token: str = None, cert_ca_file: str = None): 

261 """ 

262 Sets the tracing endpoint. Intended for usage prior to calling initialize_telemetry(). If this method is 

263 called after the initialize_telemetry() call, it will not work. The change_signal_endpoint must be used. 

264 If passed in a dict in the constructor, use predefined name TRACING_ENDPOINT_NAME. If not set, 

265 the default endpoint is used. 

266 

267 Args: 

268 endpoint (str): Tracing endpoint in the form '<IPv4|domain_name>:<port>'. 

269 auth_token (str): Bearer auth token for the tracing endpoint or None. 

270 cert_ca_file (str): Absolute file path to the private cert file for tracing or None. Rarely used. 

271 

272 Returns: 

273 Self 

274 

275 Raises: 

276 ValueError: If the endpoint format is invalid. 

277 """ 

278 tracing_endpoint = self._Endpoint(endpoint) 

279 self._config[self.TRACING_ENDPOINT_NAME] = tracing_endpoint.url 

280 self._endpoints[self.TRACING_ENDPOINT_NAME] = tracing_endpoint 

281 if auth_token is not None: 

282 self._config[self.TRACING_AUTH_TOKEN_NAME] = auth_token 

283 if cert_ca_file is not None: 

284 self._config[self.TRACING_CA_CERT_NAME] = cert_ca_file 

285 return self 

286 

287 def set_metrics_endpoint(self, endpoint: str, auth_token: str = None, cert_ca_file: str = None): 

288 """ 

289 Sets the metrics endpoint. Intended for usage prior to calling initialize_telemetry(). If this method is 

290 called after the initialize_telemetry() call, it will not work. The change_signal_endpoint must be used. 

291 If passed in a dict in the constructor, use predefined name METRICS_ENDPOINT_NAME. If not set, 

292 the default endpoint will be used. 

293 

294 Args: 

295 endpoint (str): Metrics endpoint in the form '<IPv4|domain_name>:<port>'. 

296 auth_token (str): Bearer auth token for the metrics endpoint or None. 

297 cert_ca_file (str): Absolute file path to the private cert file for metrics or None. Rarely used. 

298 

299 Returns: 

300 Self 

301 

302 Raises: 

303 ValueError: If the endpoint format is invalid. 

304 """ 

305 metrics_endpoint = self._Endpoint(endpoint) 

306 self._config[self.METRICS_ENDPOINT_NAME] = metrics_endpoint.url 

307 self._endpoints[self.METRICS_ENDPOINT_NAME] = metrics_endpoint 

308 if auth_token is not None: 

309 self._config[self.METRICS_AUTH_TOKEN_NAME] = auth_token 

310 if cert_ca_file is not None: 

311 self._config[self.METRICS_CA_CERT_NAME] = cert_ca_file 

312 return self 

313 

314 def set_console_exporter(self, use_console: bool = True): 

315 """ 

316 Sets whether to use console exporter for output. If passed in a dict in the constructor, use predefined name 

317 USE_CONSOLE_EXPORTER_NAME. It applies to all exporters (logging, tracing, metrics). This is a convenience 

318 used for testing only. Do not set in produiction. Also to set this value without modifying your code use the 

319 environment variable 'OTEL_USE_CONSOLE_EXPORTER'. Set this to true, yes, or 1. Case doesn't matter. 

320 

321 $ export OTEL_USE_CONSOLE_EXPORTER=TRUE 

322 

323 Args: 

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

325 

326 Returns: 

327 Self 

328 """ 

329 self._config[self.USE_CONSOLE_EXPORTER_NAME] = use_console 

330 return self 

331 

332 @deprecated 

333 def set_auth_token(self, auth_token: str): 

334 """ 

335 Sets the default authentication token for the endpoints (default endpoint). It is a fallback for all endpoints (default, logging, 

336 tracing, metrics). If passed in a dict in the constructor, use predefined name 

337 DEFAULT_AUTH_TOKEN_NAME. 

338 

339 Args: 

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

341 

342 Returns: 

343 Self 

344 """ 

345 self._config[self.DEFAULT_AUTH_TOKEN_NAME] = auth_token 

346 return self 

347 

348 @deprecated 

349 def set_auth_token_logging(self, auth_token: str): 

350 """ 

351 Sets the authentication token for the logging endpoint. If passed in a dict in the constructor, use predefined name 

352 LOGGING_AUTH_TOKEN_NAME. 

353 

354 Args: 

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

356 

357 Returns: 

358 Self 

359 """ 

360 self._config[self.LOGGING_AUTH_TOKEN_NAME] = auth_token 

361 return self 

362 

363 @deprecated 

364 def set_auth_token_tracing(self, auth_token: str): 

365 """ 

366 Sets the authentication token for the tracing endpoint. If passed in a dict in the constructor, use predefined name 

367 TRACING_AUTH_TOKEN_NAME. 

368 

369 Args: 

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

371 

372 Returns: 

373 Self 

374 """ 

375 self._config[self.TRACING_AUTH_TOKEN_NAME] = auth_token 

376 return self 

377 

378 @deprecated 

379 def set_auth_token_metrics(self, auth_token: str): 

380 """ 

381 Sets the authentication token for the metrics endpoint. If passed in a dict in the constructor, use predefined name 

382 METRICS_AUTH_TOKEN_NAME. 

383 

384 Args: 

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

386 

387 Returns: 

388 Self 

389 """ 

390 self._config[self.METRICS_AUTH_TOKEN_NAME] = auth_token 

391 return self 

392 

393 @deprecated 

394 def set_tls_private_ca_cert(self, cert_file: str): 

395 """ 

396 TLS certificate used for default endpoint only. 

397 Sets the actual TLS private CA certificate to be used for secure connections. 

398 This is used to verify the server's certificate when using TLS. If passed in 

399 a dict in the constructor, use predefined name DEFAULT_CA_CERT_NAME. 

400 The caller must pass a file path that will later be utilized to find a cert. 

401 Can be used to set CA to None is cert_file is None. 

402 

403 Args: 

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

405 

406 Returns: 

407 Self 

408 """ 

409 self._config[self.DEFAULT_CA_CERT_NAME] = cert_file 

410 return self 

411 

412 @deprecated 

413 def set_tls_private_ca_cert_logging(self, cert_file: str): 

414 """ 

415 TLS certificate used for logging endpoint only. 

416 Sets the actual TLS private CA certificate to be used for secure connections. 

417 This is used to verify the server's certificate when using TLS. If passed in 

418 a dict in the constructor, use predefined name LOGGING_CA_CERT_NAME. 

419 The caller must pass a file path that will later be utilized to find a cert. 

420 Can be used to set CA to None is cert_file is None. 

421 

422 Args: 

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

424 

425 Returns: 

426 Self 

427 """ 

428 self._config[self.LOGGING_CA_CERT_NAME] = cert_file 

429 return self 

430 

431 @deprecated 

432 def set_tls_private_ca_cert_tracing(self, cert_file: str): 

433 """ 

434 TLS certificate used for tracing endpoint only. 

435 Sets the actual TLS private CA certificate to be used for secure connections. 

436 This is used to verify the server's certificate when using TLS. If passed in 

437 a dict in the constructor, use predefined name TRACING_CA_CERT_NAME. 

438 The caller must pass a file path that will later be utilized to find a cert. 

439 Can be used to set CA to None is cert_file is None. 

440 

441 Args: 

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

443 

444 Returns: 

445 Self 

446 """ 

447 self._config[self.TRACING_CA_CERT_NAME] = cert_file 

448 return self 

449 

450 @deprecated 

451 def set_tls_private_ca_cert_metrics(self, cert_file: str): 

452 """ 

453 TLS certificate used for metrics endpoint only. 

454 Sets the actual TLS private CA certificate to be used for secure connections. 

455 This is used to verify the server's certificate when using TLS. If passed in 

456 a dict in the constructor, use predefined name METRICS_CA_CERT_NAME. 

457 The caller must pass a file path that will later be utilized to find a cert. 

458 Can be used to set CA to None is cert_file is None. 

459 

460 Args: 

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

462 

463 Returns: 

464 Self 

465 """ 

466 self._config[self.METRICS_CA_CERT_NAME] = cert_file 

467 return self 

468 

469 def set_logging_level(self, level: str): 

470 """ 

471 Sets the logging level for the telemetry logging to the collector. The built-in Python 

472 logging module must be used or logging will not get sent to the server. If passed in a 

473 dict in the constructor, use predefined name LOGGING_LEVEL_NAME. This will not affect 

474 the logging level of the root logger, only what is sent to OTel. 

475 

476 Args: 

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

478 

479 Returns: 

480 Self 

481 """ 

482 if level not in ['debug', 'info', 'warn', 'warning', 'error', 'fatal', 'critical']: 

483 return self 

484 self._config[self.LOGGING_LEVEL_NAME] = level 

485 return self 

486 

487 def set_metrics_export_interval_ms(self, interval_ms: int): 

488 """ 

489 Sets the metrics export interval in milliseconds. If this value is not set, 

490 the default is 60,000 milliseconds (1 minute). If passed in a dict in the constructor, 

491 use predefined name METRICS_EXPORT_INTERVAL_NAME. This dictates how long the batching 

492 inside OpenTelemetry lasts before sending to the collector. 

493 

494 Args: 

495 interval (int): Interval in milliseconds for exporting metrics. If this is zero or 

496 negative then the export interval is not set. 

497 

498 Returns: 

499 Self 

500 """ 

501 if interval_ms <= 0: 

502 return self 

503 self._config[self.METRICS_EXPORT_INTERVAL_MS_NAME] = interval_ms 

504 return self 

505 

506 def set_tracing_export_interval_ms(self, interval_ms: int): 

507 """ 

508 Sets the tracing export interval in milliseconds. If this value is not set, 

509 the default is 60,000 milliseconds (1 minute). If passed in a dict in the constructor, 

510 use predefined name TRACING_EXPORT_INTERVAL_NAME. This dictates how long the batching 

511 inside OpenTelemetry lasts before sending to the collector. 

512 

513 Args: 

514 interval (int): Interval in milliseconds for exporting metrics. If this is zero or 

515 negative then the export interval is not set. 

516 

517 Returns: 

518 Self 

519 """ 

520 if interval_ms <= 0: 

521 return self 

522 self._config[self.TRACING_EXPORT_INTERVAL_MS_NAME] = interval_ms 

523 return self 

524 

525 def set_tracing_session_entropy(self, session_entropy): 

526 """ 

527 Sets the session entropy for tracing. This is used to ensure that traces are unique 

528 across different sessions. If this value is not set, a default value will be used. If 

529 passed in a dict in the constructor, use predefined name SESSION_ENTROPY_VALUE_NAME. 

530 

531 Args: 

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

533 

534 Returns: 

535 Self 

536 """ 

537 self._config[self.SESSION_ENTROPY_VALUE_NAME] = session_entropy 

538 return self 

539 

540 def set_skip_internet_check(self, value: bool): 

541 """ 

542 Sets whether to skip the internet check. This is useful for environments that do not have 

543 internet access. If passed in a dict in the constructor, use predefined name SKIP_INTERNET_CHECK_NAME. 

544 

545 Args: 

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

547 

548 Returns: 

549 Self 

550 """ 

551 self._config[self.SKIP_INTERNET_CHECK_NAME] = value 

552 return self 

553 

554 def set_use_cumulative_metrics(self, value: bool): 

555 """ 

556 Sets the use of cumulative aggregation temporality if True. The default (False) is delta 

557 (not aggregated). 

558 

559 Cumulative counters report a measurement consistently for each export interval. The would result in "duplicate" 

560 metrics. To get metric readings only for the difference between the current count and the previous count, use delta 

561 aggregation. 

562 

563 Args: 

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

565 deltas (no aggregation). 

566 

567 Returns: 

568 Self 

569 """ 

570 self._config[self.USE_CUMULATIVE_METRICS_NAME] = value 

571 return self 

572 

573 def set_proxy_url(self, proxy_url: str): 

574 """ 

575 Sets the proxy URL to use for HTTP OTLP exporters. This applies to all HTTP-based 

576 signal exporters (logging, tracing, metrics). gRPC exporters are not affected. 

577 If passed in a dict in the constructor, use predefined name PROXY_URL_NAME. 

578 The environment variable is 'ATEL_PROXY_URL'. 

579 

580 Args: 

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

582 

583 Returns: 

584 Self 

585 """ 

586 self._config[self.PROXY_URL_NAME] = proxy_url 

587 return self 

588 

589 def set_shutdown_on_exit(self, value: bool): 

590 """ 

591 Sets whether providers register atexit handlers that flush on interpreter exit. 

592 If True (default), each provider registers an ``atexit`` handler that flushes on interpreter exit. 

593 If False, no atexit handlers are registered and the caller becomes responsible for flushing: 

594 call ``shutdown_telemetry()`` (or ``flush_telemetry()``) before the process exits, otherwise 

595 buffered telemetry is silently dropped.The environment variable is 'ATEL_SHUTDOWN_ON_EXIT'. 

596 

597 Args: 

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

599 

600 Returns: 

601 Self 

602 """ 

603 self._config[self.SHUTDOWN_ON_EXIT_NAME] = value 

604 return self 

605 

606 def set_verbose_export_errors(self, value: bool): 

607 """ 

608 Sets whether OpenTelemetry export errors are logged to stdout/stderr. 

609 If False (default), export errors from the OpenTelemetry SDK are suppressed. 

610 If True, export errors such as "Transient error" will be logged by the OpenTelemetry SDK. 

611 The environment variable is 'ATEL_VERBOSE_EXPORT_ERRORS'. This will bubble up to global 

612 OpenTelemetry logging, so make sure to set this to True off if those logs are important, or 

613 in development. 

614 

615 Args: 

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

617 

618 Returns: 

619 Self 

620 """ 

621 self._config[self.VERBOSE_EXPORT_ERRORS_NAME] = value 

622 return self 

623 

624 def set_disable_session_id(self, value: bool): 

625 """ 

626 Sets whether to disable automatic session ID generation. 

627 If False (default), a session ID is automatically generated and added to telemetry. 

628 If True, no session ID will be generated or added to telemetry resource attributes. 

629 The environment variable is 'ATEL_DISABLE_SESSION_ID'. 

630 

631 Args: 

632 value (bool): True to disable session ID generation, False to enable it. 

633 

634 Returns: 

635 Self 

636 """ 

637 self._config[self.DISABLE_SESSION_ID_NAME] = value 

638 return self 

639 

640 def _get_proxy_url(self) -> str: 

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

642 

643 def _create_proxy_session(self): 

644 proxy_url = self._get_proxy_url() 

645 if proxy_url is None: 

646 return None 

647 import requests 

648 session = requests.Session() 

649 session.proxies = { 

650 'http': proxy_url, 

651 'https': proxy_url 

652 } 

653 return session 

654 

655 

656 class _Endpoint: 

657 def __init__(self, endpoint: str): 

658 # Properties: 

659 # - protocol - protocol of the endpoint passed to the constructor 

660 # - host - host of the endpoint passed to the constructor 

661 # - port - port of the endpoint passed to the constructor 

662 # - path - path of the endpoint passed to the constructor 

663 # - valid - whether or not the endpoint is valid 

664 # - _internet_check_port - internet check port used for connection check 

665 self._parse_endpoint(endpoint.strip()) 

666 

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

668 def _parse_endpoint(self, url: str): 

669 self._validate_endpoint(url) 

670 

671 # Default port for internet check 

672 if self.port is None: 

673 if self.protocol == 'http': 

674 self._internet_check_port = 80 

675 else: 

676 # HTTPS and gRPC(s) use 443 by default 

677 self._internet_check_port = 443 

678 

679 # allow default port usage from user specification 

680 elif self.port not in (80, 443) and not (1024 <= self.port <= 65535): 

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

682 # Internet check port is user port if one is specified and valid 

683 else: 

684 self._internet_check_port = self.port 

685 

686 # prepare whole url 

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

688 if self.port: 

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

690 url += self.path 

691 

692 self.url = url 

693 

694 def _validate_endpoint(self, endpoint: str): 

695 if endpoint == '': 

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

697 pattern = re.compile( 

698 r"^" 

699 r"(https?://|grpcs?://)" # capture group 1: optional protocol 

700 r"(" # capture group 2: host 

701 r"(?!0\.)" # Disallow IPs starting with 0. 

702 r"(?:\d{1,3}\.){3}\d{1,3}" # IPv4 format (non-capturing group) 

703 r"|" 

704 r"(?:[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*" # domain segment 

705 r"(?:\.[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*)*)" # more segments 

706 r")" 

707 r"(?::(\d{1,5}))?" # capture group 3: optional port 

708 r"(/.*)?$" # capture group 4: optional path 

709 ) 

710 

711 match = pattern.match(endpoint) 

712 if not match: 

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

714 

715 protocol_str = match.group(1) 

716 self.host = match.group(2) 

717 port = match.group(3) 

718 self.port = int(port) if port is not None else None 

719 self.path = match.group(4) or "" 

720 

721 # Extract protocol 

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

723 # Determine tls 

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

725 

726 # If it's an IP, validate each octet 

727 if re.match(r"^(\d{1,3}\.)+\d{1,3}$", self.host): 

728 quads = list(map(int, self.host.split('.'))) 

729 if len(quads) != 4: 

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

731 if quads[0] == 0 or quads[0] == 255 or quads[3] == 0 or quads[3] == 255: 

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

733 for q in quads: 

734 if q > 255: 

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

736 

737 def _change_signal_endpoint(self, signal: str, new_endpoint: str, auth_token: str=None): 

738 set_endpoint = getattr(self, f"set_{signal}_endpoint", None) 

739 set_endpoint(new_endpoint, auth_token=auth_token) 

740 get_endpoint = getattr(self, f"_get_{signal}_endpoint", None) 

741 return get_endpoint() 

742 

743 def _set_otel_signal_endpoint(self, endpoint: str, signal: str) -> str: 

744 if endpoint.lower().startswith("grpc"): 

745 return endpoint 

746 endpoint_str = f"v1/{signal}" 

747 if not endpoint.endswith(endpoint_str): 

748 endpoint_str = "/" + endpoint_str if endpoint[-1] != "/" else endpoint_str 

749 return endpoint + endpoint_str 

750 else: 

751 return endpoint 

752 

753 def _get_default_endpoint(self) -> str: 

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

755 

756 def _get_logging_endpoint(self) -> str: 

757 endpoint = self._config.get(self.LOGGING_ENDPOINT_NAME, self._get_default_endpoint()) 

758 return self._set_otel_signal_endpoint(endpoint, "logs") 

759 

760 def _get_tracing_endpoint(self) -> str: 

761 endpoint = self._config.get(self.TRACING_ENDPOINT_NAME, self._get_default_endpoint()) 

762 return self._set_otel_signal_endpoint(endpoint, "traces") 

763 

764 def _get_metrics_endpoint(self) -> str: 

765 endpoint = self._config.get(self.METRICS_ENDPOINT_NAME, self._get_default_endpoint()) 

766 return self._set_otel_signal_endpoint(endpoint, "metrics") 

767 

768 def _prepare_ca_cert(self, protocol: str, cert_file: str) -> str: 

769 if protocol in ['http', 'https']: 

770 return cert_file # just return cert file for HTTP exporter ca_cert 

771 else: 

772 if cert_file: 

773 with open(cert_file, 'rb') as f: 

774 ca_cert_bytes = f.read() # gRPC exporter requires a bytes string 

775 creds = grpc.ssl_channel_credentials(root_certificates=ca_cert_bytes) 

776 return creds 

777 else: 

778 # Use system trust store (for public CAs) 

779 creds = grpc.ssl_channel_credentials() 

780 return creds 

781 

782 def _get_ca_cert_default(self) -> str: 

783 cert_file = self._config.get(self.DEFAULT_CA_CERT_NAME, None) 

784 return self._prepare_ca_cert(self._get_request_protocol_default().protocol, cert_file) 

785 

786 def _get_ca_cert_logging(self) -> str: 

787 cert_file = self._config.get(self.LOGGING_CA_CERT_NAME, self._get_ca_cert_default()) 

788 return self._prepare_ca_cert(self._get_request_protocol_logging(), cert_file) 

789 

790 def _get_ca_cert_tracing(self) -> str: 

791 cert_file = self._config.get(self.TRACING_CA_CERT_NAME, self._get_ca_cert_default()) 

792 return self._prepare_ca_cert(self._get_request_protocol_tracing(), cert_file) 

793 

794 def _get_ca_cert_metrics(self) -> str: 

795 cert_file = self._config.get(self.METRICS_CA_CERT_NAME, self._get_ca_cert_default()) 

796 return self._prepare_ca_cert(self._get_request_protocol_metrics(), cert_file) 

797 

798 def _get_console_exporter(self) -> bool: 

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

800 

801 def _get_auth_token_default(self) -> str: 

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

803 

804 def _get_auth_token_logging(self) -> str: 

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

806 

807 def _get_auth_token_tracing(self) -> str: 

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

809 

810 def _get_auth_token_metrics(self) -> str: 

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

812 

813 def _get_logging_level(self) -> str: 

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

815 

816 def _get_metrics_export_interval_ms(self) -> int: 

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

818 

819 def _get_tracing_export_interval_ms(self) -> int: 

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

821 

822 def _get_tracing_session_entropy(self): 

823 if self._config.get(self.SESSION_ENTROPY_VALUE_NAME, None) is None: 

824 import time 

825 self._config[self.SESSION_ENTROPY_VALUE_NAME] = int(time.time() * 1e9) 

826 return self._config.get(self.SESSION_ENTROPY_VALUE_NAME) 

827 

828 def _get_skip_internet_check(self) -> bool: 

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

830 

831 def _get_TLS_default(self) -> bool: 

832 # will raise if there is no default tls (means there is no default endpoint) 

833 return self._endpoints[self.DEFAULT_ENDPOINT_NAME] 

834 

835 def _get_TLS_logging(self) -> str: 

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

837 

838 def _get_TLS_metrics(self) -> str: 

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

840 

841 def _get_TLS_tracing(self) -> str: 

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

843 

844 def _get_request_protocol_default(self) -> str: 

845 # will raise if there is no default protocol (means there is no default endpoint) 

846 return self._endpoints[self.DEFAULT_ENDPOINT_NAME] 

847 

848 def _get_request_protocol_logging(self) -> str: 

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

850 

851 def _get_request_protocol_metrics(self) -> str: 

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

853 

854 def _get_request_protocol_tracing(self) -> str: 

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

856 

857 def _get_use_cumulative_metrics(self) -> bool: 

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

859 

860 def _get_shutdown_on_exit(self) -> bool: 

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

862 

863 def _get_verbose_export_errors(self) -> bool: 

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

865 

866 def _get_disable_session_id(self) -> bool: 

867 return self._config.get(self.DISABLE_SESSION_ID_NAME, False)