LCOV - code coverage report
Current view: top level - lib/src/environment - otel_env.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 97.6 % 250 244
Test Date: 2026-08-27 23:42:02 Functions: - 0 0

            Line data    Source code
       1              : // Copyright The OpenTelemetry Authors
       2              : // SPDX-License-Identifier: Apache-2.0
       3              : 
       4              : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
       5              : import '../metrics/export/metrics_sdk_config.dart';
       6              : import '../util/header_redaction.dart';
       7              : import 'env_constants.dart';
       8              : import 'environment_service.dart';
       9              : 
      10              : /// Raw BLRP environment values parsed by [OTelEnv.getBlrpConfig].
      11              : ///
      12              : /// All fields are nullable — `null` means the corresponding env var was
      13              : /// unset or contained a non-numeric value (a warning is logged in that case).
      14              : /// Domain-level validation (e.g. "queue size must be > 0") belongs in
      15              : /// [BatchLogRecordProcessorConfig.fromEnvironment], not here.
      16              : typedef BlrpEnvironmentValues = ({
      17              :   /// Parsed from `OTEL_BLRP_SCHEDULE_DELAY`
      18              :   Duration? scheduleDelay,
      19              : 
      20              :   /// Parsed from `OTEL_BLRP_EXPORT_TIMEOUT`
      21              :   Duration? exportTimeout,
      22              : 
      23              :   /// Parsed from `OTEL_BLRP_MAX_QUEUE_SIZE`
      24              :   int? maxQueueSize,
      25              : 
      26              :   /// Parsed from `OTEL_BLRP_MAX_EXPORT_BATCH_SIZE`
      27              :   int? maxExportBatchSize,
      28              : });
      29              : 
      30              : /// Raw OTLP environment values parsed by [OTelEnv.getOtlpConfig].
      31              : ///
      32              : /// All fields are nullable — `null` means the corresponding env var was
      33              : /// unset or unparseable. Signal-specific variables take precedence over
      34              : /// general ones per the OTel specification.
      35              : typedef OtlpEnvironmentValues = ({
      36              :   /// Parsed from `OTEL_EXPORTER_OTLP_{SIGNAL}_ENDPOINT` or
      37              :   /// `OTEL_EXPORTER_OTLP_ENDPOINT`
      38              :   String? endpoint,
      39              : 
      40              :   /// Parsed from `OTEL_EXPORTER_OTLP_{SIGNAL}_PROTOCOL` or
      41              :   /// `OTEL_EXPORTER_OTLP_PROTOCOL`
      42              :   String? protocol,
      43              : 
      44              :   /// Parsed from `OTEL_EXPORTER_OTLP_{SIGNAL}_HEADERS` or
      45              :   /// `OTEL_EXPORTER_OTLP_HEADERS`
      46              :   Map<String, String>? headers,
      47              : 
      48              :   /// Parsed from `OTEL_EXPORTER_OTLP_{SIGNAL}_INSECURE` or
      49              :   /// `OTEL_EXPORTER_OTLP_INSECURE`
      50              :   bool? insecure,
      51              : 
      52              :   /// Parsed from `OTEL_EXPORTER_OTLP_{SIGNAL}_TIMEOUT` or
      53              :   /// `OTEL_EXPORTER_OTLP_TIMEOUT`
      54              :   Duration? timeout,
      55              : 
      56              :   /// Parsed from `OTEL_EXPORTER_OTLP_{SIGNAL}_COMPRESSION` or
      57              :   /// `OTEL_EXPORTER_OTLP_COMPRESSION`
      58              :   String? compression,
      59              : 
      60              :   /// Parsed from `OTEL_EXPORTER_OTLP_{SIGNAL}_CERTIFICATE` or
      61              :   /// `OTEL_EXPORTER_OTLP_CERTIFICATE`
      62              :   String? certificate,
      63              : 
      64              :   /// Parsed from `OTEL_EXPORTER_OTLP_{SIGNAL}_CLIENT_KEY` or
      65              :   /// `OTEL_EXPORTER_OTLP_CLIENT_KEY`
      66              :   String? clientKey,
      67              : 
      68              :   /// Parsed from `OTEL_EXPORTER_OTLP_{SIGNAL}_CLIENT_CERTIFICATE` or
      69              :   /// `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE`
      70              :   String? clientCertificate,
      71              : });
      72              : 
      73              : /// Raw BSP environment values parsed by [OTelEnv.getBspConfig].
      74              : ///
      75              : /// All fields are nullable — `null` means the corresponding env var was
      76              : /// unset or contained a non-numeric value (a warning is logged in that case).
      77              : /// Domain-level validation belongs in
      78              : /// [BatchSpanProcessorConfig.fromEnvironment], not here.
      79              : typedef BspEnvironmentValues = ({
      80              :   /// Parsed from `OTEL_BSP_SCHEDULE_DELAY`
      81              :   Duration? scheduleDelay,
      82              : 
      83              :   /// Parsed from `OTEL_BSP_EXPORT_TIMEOUT`
      84              :   Duration? exportTimeout,
      85              : 
      86              :   /// Parsed from `OTEL_BSP_MAX_QUEUE_SIZE`
      87              :   int? maxQueueSize,
      88              : 
      89              :   /// Parsed from `OTEL_BSP_MAX_EXPORT_BATCH_SIZE`
      90              :   int? maxExportBatchSize,
      91              : });
      92              : 
      93              : /// Raw service environment values parsed by [OTelEnv.getServiceConfig].
      94              : ///
      95              : /// All fields are nullable — `null` means the corresponding env var was
      96              : /// unset. `OTEL_SERVICE_NAME` takes precedence over `service.name` in
      97              : /// `OTEL_RESOURCE_ATTRIBUTES` per the spec.
      98              : typedef ServiceEnvironmentValues = ({
      99              :   /// Parsed from `OTEL_SERVICE_NAME` or `service.name` in
     100              :   /// `OTEL_RESOURCE_ATTRIBUTES`
     101              :   String? serviceName,
     102              : 
     103              :   /// Parsed from `service.version` in `OTEL_RESOURCE_ATTRIBUTES`
     104              :   String? serviceVersion,
     105              : });
     106              : 
     107              : /// Raw attribute limit values parsed by [OTelEnv.getAttributeLimits].
     108              : ///
     109              : /// All fields are nullable — `null` means the corresponding env var was
     110              : /// unset or contained a non-numeric value.
     111              : typedef AttributeLimitsEnvironmentValues = ({
     112              :   /// Parsed from `OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT`
     113              :   int? attributeValueLengthLimit,
     114              : 
     115              :   /// Parsed from `OTEL_ATTRIBUTE_COUNT_LIMIT`
     116              :   int? attributeCountLimit,
     117              : });
     118              : 
     119              : /// Raw metrics environment values parsed by [OTelEnv.getMetricsConfig].
     120              : ///
     121              : /// All fields are nullable — `null` means the corresponding env var was
     122              : /// unset. Domain-level validation and defaults belong in
     123              : /// [MetricsSdkConfig.fromEnvironment], not here.
     124              : typedef MetricsEnvironmentValues = ({
     125              :   /// Parsed from `OTEL_METRICS_EXEMPLAR_FILTER`
     126              :   String? exemplarFilter,
     127              : 
     128              :   /// Parsed from `OTEL_METRIC_EXPORT_INTERVAL`
     129              :   Duration? exportInterval,
     130              : 
     131              :   /// Parsed from `OTEL_METRIC_EXPORT_TIMEOUT`
     132              :   Duration? exportTimeout,
     133              : });
     134              : 
     135              : /// Utility class for handling OpenTelemetry environment variables.
     136              : ///
     137              : /// This class provides methods for reading standard OpenTelemetry environment
     138              : /// variables and applying their configuration to the SDK.
     139              : ///
     140              : /// OpenTelemetry standard environment variables:
     141              : /// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/
     142              : class OTelEnv {
     143              :   /// Initialize logging based on environment variables.
     144              :   ///
     145              :   /// This method reads the logging-related environment variables
     146              :   /// and configures the OTelLog accordingly.
     147              :   ///
     148              :   /// If a custom log function has already been set (e.g., by tests),
     149              :   /// this method will preserve it along with the current log level.
     150              :   /// This allows tests to fully control logging configuration without
     151              :   /// environment variables overriding their settings.
     152          155 :   static void initializeLogging() {
     153              :     // Save the current log function to check if it's custom
     154          155 :     final existingLogFunction = OTelLog.logFunction;
     155              : 
     156              :     // A custom function is one that's not null and not the default print function
     157              :     final hasCustomLogFunction =
     158          155 :         existingLogFunction != null && existingLogFunction != print;
     159              : 
     160              :     // Set log level and function based on environment variable,
     161              :     // but only if no custom log function is already configured.
     162          297 :     final logLevel = _getEnv(otelLogLevel)?.toLowerCase();
     163              :     if (logLevel != null && !hasCustomLogFunction) {
     164              :       switch (logLevel) {
     165          124 :         case 'trace':
     166          124 :           OTelLog.enableTraceLogging();
     167              :           break;
     168            1 :         case 'debug':
     169            1 :           OTelLog.enableDebugLogging();
     170              :           break;
     171            1 :         case 'info':
     172            1 :           OTelLog.enableInfoLogging();
     173              :           break;
     174            1 :         case 'warn':
     175            1 :           OTelLog.enableWarnLogging();
     176              :           break;
     177            1 :         case 'error':
     178            1 :           OTelLog.enableErrorLogging();
     179              :           break;
     180            1 :         case 'fatal':
     181            1 :           OTelLog.enableFatalLogging();
     182              :           break;
     183              :         default:
     184              :           // No change to logging if level not recognized
     185              :           break;
     186              :       }
     187              : 
     188              :       OTelLog.logFunction = print;
     189              :     }
     190              : 
     191              :     // Dart-specific per-signal diagnostic sinks, named per the spec's
     192              :     // language-specific env var convention (OTEL_{LANGUAGE}_{FEATURE}).
     193              :     // The spec's self-diagnostics section leaves this to language
     194              :     // conventions; OTEL_LOG_LEVEL governs only the internal logger level.
     195          155 :     if (_getEnvBool(otelDartLogMetrics) && OTelLog.metricLogFunction == null) {
     196              :       OTelLog.metricLogFunction = print;
     197              :     }
     198          155 :     if (_getEnvBool(otelDartLogSpans) && OTelLog.spanLogFunction == null) {
     199              :       OTelLog.spanLogFunction = print;
     200              :     }
     201          155 :     if (_getEnvBool(otelDartLogExport) && OTelLog.exportLogFunction == null) {
     202              :       OTelLog.exportLogFunction = print;
     203              :     }
     204              :   }
     205              : 
     206              :   /// Applies the OTLP header log allowlist.
     207              :   ///
     208              :   /// [headerNames] comes from `OTel.initialize`. When null,
     209              :   /// `OTEL_DART_HEADER_LOG_ALLOWLIST` is read instead; code wins over
     210              :   /// configuration and the two are never combined. With neither set, no header
     211              :   /// value is logged.
     212              :   ///
     213              :   /// The allowlist is read at log time, so call this before anything logs
     214              :   /// headers.
     215          153 :   static void applyHeaderLogAllowlist([Iterable<String>? headerNames]) {
     216              :     if (headerNames != null) {
     217            0 :       configureHeaderLogAllowlist(headerNames);
     218              :       return;
     219              :     }
     220          153 :     configureHeaderLogAllowlist(
     221          306 :       parseHeaderLogAllowlist(_getEnv(otelDartHeaderLogAllowlist)),
     222              :     );
     223              :   }
     224              : 
     225              :   /// Get OTLP configuration from environment variables.
     226              :   ///
     227              :   /// Returns an [OtlpEnvironmentValues] record containing the OTLP
     228              :   /// configuration read from environment variables. Signal-specific
     229              :   /// variables take precedence over general ones.
     230          155 :   static OtlpEnvironmentValues getOtlpConfig({String signal = 'traces'}) {
     231              :     // Get endpoint (signal-specific takes precedence)
     232              :     String? endpoint;
     233              :     switch (signal) {
     234          155 :       case 'traces':
     235          155 :         endpoint = _getEnv(otelExporterOtlpTracesEndpoint) ??
     236          155 :             _getEnv(otelExporterOtlpEndpoint);
     237              :         break;
     238          142 :       case 'metrics':
     239          133 :         endpoint = _getEnv(otelExporterOtlpMetricsEndpoint) ??
     240          133 :             _getEnv(otelExporterOtlpEndpoint);
     241              :         break;
     242          129 :       case 'logs':
     243          129 :         endpoint = _getEnv(otelExporterOtlpLogsEndpoint) ??
     244          129 :             _getEnv(otelExporterOtlpEndpoint);
     245              :         break;
     246              :     }
     247              : 
     248              :     // Get protocol (signal-specific takes precedence)
     249              :     String? protocol;
     250              :     switch (signal) {
     251          155 :       case 'traces':
     252          155 :         protocol = _getEnv(otelExporterOtlpTracesProtocol) ??
     253          155 :             _getEnv(otelExporterOtlpProtocol);
     254              :         break;
     255          142 :       case 'metrics':
     256          133 :         protocol = _getEnv(otelExporterOtlpMetricsProtocol) ??
     257          133 :             _getEnv(otelExporterOtlpProtocol);
     258              :         break;
     259          129 :       case 'logs':
     260          129 :         protocol = _getEnv(otelExporterOtlpLogsProtocol) ??
     261          129 :             _getEnv(otelExporterOtlpProtocol);
     262              :         break;
     263              :     }
     264              : 
     265              :     // Get headers (signal-specific takes precedence)
     266              :     Map<String, String>? parsedHeaders;
     267              :     String? rawHeaders;
     268              :     switch (signal) {
     269          155 :       case 'traces':
     270          155 :         rawHeaders = _getEnv(otelExporterOtlpTracesHeaders) ??
     271          155 :             _getEnv(otelExporterOtlpHeaders);
     272              :         break;
     273          142 :       case 'metrics':
     274          133 :         rawHeaders = _getEnv(otelExporterOtlpMetricsHeaders) ??
     275          133 :             _getEnv(otelExporterOtlpHeaders);
     276              :         break;
     277          129 :       case 'logs':
     278          129 :         rawHeaders = _getEnv(otelExporterOtlpLogsHeaders) ??
     279          129 :             _getEnv(otelExporterOtlpHeaders);
     280              :         break;
     281              :     }
     282              :     if (rawHeaders != null) {
     283            1 :       if (OTelLog.isDebug()) {
     284              :         // The raw value holds the Authorization header the loop below redacts.
     285            2 :         OTelLog.debug('OTelEnv: Parsing $signal headers from env');
     286              :       }
     287            1 :       parsedHeaders = _parseHeaders(rawHeaders);
     288            1 :       if (OTelLog.isDebug()) {
     289            3 :         OTelLog.debug('OTelEnv: Parsed ${parsedHeaders.length} header(s)');
     290            2 :         parsedHeaders.forEach((key, value) {
     291            3 :           OTelLog.debug('  ${formatHeaderForLog(key, value)}');
     292              :         });
     293              :       }
     294              :     }
     295              : 
     296              :     // Get insecure setting (signal-specific takes precedence)
     297              :     bool? insecure;
     298              :     switch (signal) {
     299          155 :       case 'traces':
     300          155 :         insecure = _getEnvBoolNullable(otelExporterOtlpTracesInsecure) ??
     301          155 :             _getEnvBoolNullable(otelExporterOtlpInsecure);
     302              :         break;
     303          142 :       case 'metrics':
     304          133 :         insecure = _getEnvBoolNullable(otelExporterOtlpMetricsInsecure) ??
     305          133 :             _getEnvBoolNullable(otelExporterOtlpInsecure);
     306              :         break;
     307          129 :       case 'logs':
     308          129 :         insecure = _getEnvBoolNullable(otelExporterOtlpLogsInsecure) ??
     309          129 :             _getEnvBoolNullable(otelExporterOtlpInsecure);
     310              :         break;
     311              :     }
     312              : 
     313              :     // Get timeout (signal-specific takes precedence)
     314              :     Duration? parsedTimeout;
     315              :     String? rawTimeout;
     316              :     switch (signal) {
     317          155 :       case 'traces':
     318          155 :         rawTimeout = _getEnv(otelExporterOtlpTracesTimeout) ??
     319          155 :             _getEnv(otelExporterOtlpTimeout);
     320              :         break;
     321          142 :       case 'metrics':
     322          133 :         rawTimeout = _getEnv(otelExporterOtlpMetricsTimeout) ??
     323          133 :             _getEnv(otelExporterOtlpTimeout);
     324              :         break;
     325          129 :       case 'logs':
     326          129 :         rawTimeout = _getEnv(otelExporterOtlpLogsTimeout) ??
     327          129 :             _getEnv(otelExporterOtlpTimeout);
     328              :         break;
     329              :     }
     330              :     if (rawTimeout != null) {
     331            1 :       final timeoutMs = int.tryParse(rawTimeout);
     332              :       if (timeoutMs != null) {
     333            1 :         parsedTimeout = Duration(milliseconds: timeoutMs);
     334              :       }
     335              :     }
     336              : 
     337              :     // Get compression (signal-specific takes precedence)
     338              :     String? compression;
     339              :     switch (signal) {
     340          155 :       case 'traces':
     341          155 :         compression = _getEnv(otelExporterOtlpTracesCompression) ??
     342          155 :             _getEnv(otelExporterOtlpCompression);
     343              :         break;
     344          142 :       case 'metrics':
     345          133 :         compression = _getEnv(otelExporterOtlpMetricsCompression) ??
     346          133 :             _getEnv(otelExporterOtlpCompression);
     347              :         break;
     348          129 :       case 'logs':
     349          129 :         compression = _getEnv(otelExporterOtlpLogsCompression) ??
     350          129 :             _getEnv(otelExporterOtlpCompression);
     351              :         break;
     352              :     }
     353              : 
     354              :     // Get certificate (signal-specific takes precedence)
     355              :     String? certificate;
     356              :     switch (signal) {
     357          155 :       case 'traces':
     358          155 :         certificate = _getEnv(otelExporterOtlpTracesCertificate) ??
     359          155 :             _getEnv(otelExporterOtlpCertificate);
     360              :         break;
     361          142 :       case 'metrics':
     362          133 :         certificate = _getEnv(otelExporterOtlpMetricsCertificate) ??
     363          133 :             _getEnv(otelExporterOtlpCertificate);
     364              :         break;
     365          129 :       case 'logs':
     366          129 :         certificate = _getEnv(otelExporterOtlpLogsCertificate) ??
     367          129 :             _getEnv(otelExporterOtlpCertificate);
     368              :         break;
     369              :     }
     370              : 
     371              :     // Get client key (signal-specific takes precedence)
     372              :     String? clientKey;
     373              :     switch (signal) {
     374          155 :       case 'traces':
     375          155 :         clientKey = _getEnv(otelExporterOtlpTracesClientKey) ??
     376          155 :             _getEnv(otelExporterOtlpClientKey);
     377              :         break;
     378          142 :       case 'metrics':
     379          133 :         clientKey = _getEnv(otelExporterOtlpMetricsClientKey) ??
     380          133 :             _getEnv(otelExporterOtlpClientKey);
     381              :         break;
     382          129 :       case 'logs':
     383          129 :         clientKey = _getEnv(otelExporterOtlpLogsClientKey) ??
     384          129 :             _getEnv(otelExporterOtlpClientKey);
     385              :         break;
     386              :     }
     387              : 
     388              :     // Get client certificate (signal-specific takes precedence)
     389              :     String? clientCertificate;
     390              :     switch (signal) {
     391          155 :       case 'traces':
     392          155 :         clientCertificate = _getEnv(otelExporterOtlpTracesClientCertificate) ??
     393          155 :             _getEnv(otelExporterOtlpClientCertificate);
     394              :         break;
     395          142 :       case 'metrics':
     396          133 :         clientCertificate = _getEnv(otelExporterOtlpMetricsClientCertificate) ??
     397          133 :             _getEnv(otelExporterOtlpClientCertificate);
     398              :         break;
     399          129 :       case 'logs':
     400          129 :         clientCertificate = _getEnv(otelExporterOtlpLogsClientCertificate) ??
     401          129 :             _getEnv(otelExporterOtlpClientCertificate);
     402              :         break;
     403              :     }
     404              : 
     405              :     return (
     406              :       endpoint: endpoint,
     407              :       protocol: protocol,
     408              :       headers: parsedHeaders,
     409              :       insecure: insecure,
     410              :       timeout: parsedTimeout,
     411              :       compression: compression,
     412              :       certificate: certificate,
     413              :       clientKey: clientKey,
     414              :       clientCertificate: clientCertificate,
     415              :     );
     416              :   }
     417              : 
     418              :   /// Get service configuration from environment variables.
     419              :   ///
     420              :   /// Returns a [ServiceEnvironmentValues] record containing the service
     421              :   /// configuration read from environment variables.
     422              :   ///
     423              :   /// Handles the spec precedence rules:
     424              :   /// - If `service.name` is in OTEL_RESOURCE_ATTRIBUTES, it's used as the base value
     425              :   /// - OTEL_SERVICE_NAME takes precedence over `service.name` in OTEL_RESOURCE_ATTRIBUTES
     426              :   /// - `service.version` comes from OTEL_RESOURCE_ATTRIBUTES only
     427              :   /// Parses a resource attributes string into a map, handling escaping,
     428              :   /// percent-encoding, and dropping malformed entries.
     429            5 :   static Map<String, String> parseResourceAttributesString(String resourceStr) {
     430            5 :     final resourceAttrs = <String, String>{};
     431           10 :     final parts = resourceStr.split(RegExp(r'(?<!\\),'));
     432           10 :     for (var part in parts) {
     433            5 :       part = part.trim();
     434            5 :       final equalIndex = part.indexOf('=');
     435           10 :       if (equalIndex == -1) continue;
     436              : 
     437           10 :       final key = part.substring(0, equalIndex).trim();
     438           15 :       var value = part.substring(equalIndex + 1).trim();
     439              : 
     440              :       // Handle percent-encoded characters safely
     441              :       try {
     442            5 :         value = Uri.decodeComponent(value);
     443              :       } catch (e) {
     444            1 :         if (OTelLog.isWarn()) {
     445            1 :           OTelLog.warn(
     446            1 :               'OTelEnv: Dropped malformed resource attribute "$key": $e');
     447              :         }
     448              :         continue; // Drop the malformed attribute per spec
     449              :       }
     450              : 
     451              :       // Remove escape characters
     452            5 :       value = value.replaceAll(r'\,', ',');
     453              : 
     454            5 :       resourceAttrs[key] = value;
     455              :     }
     456              :     return resourceAttrs;
     457              :   }
     458              : 
     459          155 :   static ServiceEnvironmentValues getServiceConfig() {
     460              :     String? parsedServiceName;
     461              :     String? parsedServiceVersion;
     462              : 
     463              :     // First, parse service.name and service.version from OTEL_RESOURCE_ATTRIBUTES
     464          155 :     final resourceStr = _getEnv(otelResourceAttributes);
     465              :     if (resourceStr != null) {
     466            4 :       final attrs = parseResourceAttributesString(resourceStr);
     467            8 :       parsedServiceName = attrs[Service.serviceName.key];
     468            8 :       parsedServiceVersion = attrs[Service.serviceVersion.key];
     469              :     }
     470              : 
     471              :     // OTEL_SERVICE_NAME takes precedence over service.name from resource attributes
     472          155 :     final serviceName = _getEnv(otelServiceName);
     473              :     if (serviceName != null) {
     474              :       parsedServiceName = serviceName;
     475              :     }
     476              : 
     477              :     return (
     478              :       serviceName: parsedServiceName,
     479              :       serviceVersion: parsedServiceVersion,
     480              :     );
     481              :   }
     482              : 
     483              :   /// Get resource attributes from environment variables.
     484              :   ///
     485              :   /// Parses the OTEL_RESOURCE_ATTRIBUTES environment variable which should be
     486              :   /// a comma-separated list of key=value pairs.
     487            2 :   static Map<String, Object> getResourceAttributes() {
     488            2 :     final resourceStr = _getEnv(otelResourceAttributes);
     489              :     if (resourceStr != null) {
     490            1 :       return parseResourceAttributesString(resourceStr);
     491              :     }
     492            1 :     return <String, Object>{};
     493              :   }
     494              : 
     495              :   /// Whether `OTEL_SDK_DISABLED` is set to a truthy value.
     496              :   ///
     497              :   /// Per the OTel spec, when this is true the SDK acts as a no-op for all
     498              :   /// signals — no span processors, metric readers, or log record processors
     499              :   /// should be installed.
     500          308 :   static bool isSdkDisabled() => _getEnvBool(otelSdkDisabled);
     501              : 
     502              :   /// Get the selected exporter for a signal.
     503              :   ///
     504              :   /// Returns the exporter type configured via environment variables.
     505          155 :   static String? getExporter({String signal = 'traces'}) {
     506              :     switch (signal) {
     507          155 :       case 'traces':
     508          155 :         return _getEnv(otelTracesExporter);
     509          142 :       case 'metrics':
     510          133 :         return _getEnv(otelMetricsExporter);
     511          129 :       case 'logs':
     512          129 :         return _getEnv(otelLogsExporter);
     513              :       default:
     514              :         return null;
     515              :     }
     516              :   }
     517              : 
     518              :   /// Reads `OTEL_<SIGNAL>_EXPORTER` as the spec's comma-separated list
     519              :   /// (sdk-environment-variables.md, "Exporter Selection": "The
     520              :   /// implementation MAY accept a comma-separated list to enable setting
     521              :   /// multiple exporters"). Returns normalized (trimmed, lowercased,
     522              :   /// deduplicated) names, or null when the variable is unset or empty.
     523          154 :   static List<String>? getExporters({String signal = 'traces'}) {
     524          154 :     final raw = getExporter(signal: signal);
     525              :     if (raw == null) return null;
     526           12 :     final names = <String>[];
     527           24 :     for (final part in raw.split(',')) {
     528           24 :       final name = part.trim().toLowerCase();
     529           24 :       if (name.isNotEmpty && !names.contains(name)) {
     530           12 :         names.add(name);
     531              :       }
     532              :     }
     533           12 :     return names.isEmpty ? null : names;
     534              :   }
     535              : 
     536              :   /// Get Batch Span Processor (BSP) configuration from environment variables.
     537              :   ///
     538              :   /// Returns a [BspEnvironmentValues] record containing the raw parsed BSP
     539              :   /// values from environment variables. Fields are `null` when the
     540              :   /// corresponding env var is unset or contains a non-numeric value.
     541              :   ///
     542              :   /// Domain-level defaults, validation, and clamping belong in
     543              :   /// [BatchSpanProcessorConfig.fromEnvironment], not here.
     544          154 :   static BspEnvironmentValues getBspConfig() {
     545              :     // Get schedule delay
     546              :     Duration? parsedScheduleDelay;
     547          154 :     final scheduleDelay = _getEnv(otelBspScheduleDelay);
     548              :     if (scheduleDelay != null) {
     549            2 :       final delayMs = int.tryParse(scheduleDelay);
     550              :       if (delayMs != null) {
     551            2 :         parsedScheduleDelay = Duration(milliseconds: delayMs);
     552              :       } else {
     553            1 :         if (OTelLog.isWarn()) {
     554            2 :           OTelLog.warn('OTelEnv: Invalid OTEL_BSP_SCHEDULE_DELAY value '
     555              :               '"$scheduleDelay", ignoring.');
     556              :         }
     557              :       }
     558              :     }
     559              : 
     560              :     // Get export timeout
     561              :     Duration? parsedExportTimeout;
     562          154 :     final exportTimeout = _getEnv(otelBspExportTimeout);
     563              :     if (exportTimeout != null) {
     564            2 :       final timeoutMs = int.tryParse(exportTimeout);
     565              :       if (timeoutMs != null) {
     566            2 :         parsedExportTimeout = Duration(milliseconds: timeoutMs);
     567              :       } else {
     568            1 :         if (OTelLog.isWarn()) {
     569            2 :           OTelLog.warn('OTelEnv: Invalid OTEL_BSP_EXPORT_TIMEOUT value '
     570              :               '"$exportTimeout", ignoring.');
     571              :         }
     572              :       }
     573              :     }
     574              : 
     575              :     // Get max queue size
     576              :     int? parsedMaxQueueSize;
     577          154 :     final maxQueueSize = _getEnv(otelBspMaxQueueSize);
     578              :     if (maxQueueSize != null) {
     579            2 :       final size = int.tryParse(maxQueueSize);
     580              :       if (size != null) {
     581              :         parsedMaxQueueSize = size;
     582              :       } else {
     583            1 :         if (OTelLog.isWarn()) {
     584            2 :           OTelLog.warn('OTelEnv: Invalid OTEL_BSP_MAX_QUEUE_SIZE value '
     585              :               '"$maxQueueSize", ignoring.');
     586              :         }
     587              :       }
     588              :     }
     589              : 
     590              :     // Get max export batch size
     591              :     int? parsedMaxExportBatchSize;
     592          154 :     final maxExportBatchSize = _getEnv(otelBspMaxExportBatchSize);
     593              :     if (maxExportBatchSize != null) {
     594            2 :       final size = int.tryParse(maxExportBatchSize);
     595              :       if (size != null) {
     596              :         parsedMaxExportBatchSize = size;
     597              :       } else {
     598            1 :         if (OTelLog.isWarn()) {
     599            2 :           OTelLog.warn('OTelEnv: Invalid OTEL_BSP_MAX_EXPORT_BATCH_SIZE '
     600              :               'value "$maxExportBatchSize", ignoring.');
     601              :         }
     602              :       }
     603              :     }
     604              : 
     605              :     return (
     606              :       scheduleDelay: parsedScheduleDelay,
     607              :       exportTimeout: parsedExportTimeout,
     608              :       maxQueueSize: parsedMaxQueueSize,
     609              :       maxExportBatchSize: parsedMaxExportBatchSize,
     610              :     );
     611              :   }
     612              : 
     613              :   /// Reads `OTEL_PROPAGATORS` (sdk-environment-variables.md, "General SDK
     614              :   /// Configuration"): a comma-separated list of propagator names. Returns
     615              :   /// the normalized (trimmed, lowercased) names, defaulting to the spec
     616              :   /// default `[tracecontext, baggage]` when unset or empty.
     617          154 :   static List<String> getPropagators() {
     618          154 :     final raw = _getEnv(otelPropagators);
     619            4 :     if (raw == null || raw.trim().isEmpty) {
     620              :       return const ['tracecontext', 'baggage'];
     621              :     }
     622              :     return raw
     623            2 :         .split(',')
     624            8 :         .map((name) => name.trim().toLowerCase())
     625            6 :         .where((name) => name.isNotEmpty)
     626            2 :         .toList();
     627              :   }
     628              : 
     629              :   /// Get Batch LogRecord Processor (BLRP) configuration from environment variables.
     630              :   ///
     631              :   /// Returns a [BlrpEnvironmentValues] record containing the raw parsed BLRP
     632              :   /// values from environment variables. Fields are `null` when the
     633              :   /// corresponding env var is unset or contains a non-numeric value.
     634              :   ///
     635              :   /// Domain-level defaults, validation, and clamping belong in
     636              :   /// [BatchLogRecordProcessorConfig.fromEnvironment], not here.
     637          128 :   static BlrpEnvironmentValues getBlrpConfig() {
     638              :     // Get schedule delay — 0 is valid ("export as fast as possible")
     639              :     final scheduleDelayMs =
     640          128 :         getPositiveIntEnv(otelBlrpScheduleDelay, minInclusive: 0);
     641              : 
     642              :     // Get export timeout — 0 is valid ("no limit")
     643              :     final exportTimeoutMs =
     644          128 :         getPositiveIntEnv(otelBlrpExportTimeout, minInclusive: 0);
     645              : 
     646              :     // Get queue and batch sizes — just parse, no domain validation here
     647              :     final parsedMaxQueueSize =
     648          128 :         getPositiveIntEnv(otelBlrpMaxQueueSize, minInclusive: 0);
     649              :     final parsedMaxExportBatchSize =
     650          128 :         getPositiveIntEnv(otelBlrpMaxExportBatchSize, minInclusive: 0);
     651              : 
     652              :     return (
     653              :       scheduleDelay: scheduleDelayMs != null
     654            3 :           ? Duration(milliseconds: scheduleDelayMs)
     655              :           : null,
     656              :       exportTimeout: exportTimeoutMs != null
     657            2 :           ? Duration(milliseconds: exportTimeoutMs)
     658              :           : null,
     659              :       maxQueueSize: parsedMaxQueueSize,
     660              :       maxExportBatchSize: parsedMaxExportBatchSize,
     661              :     );
     662              :   }
     663              : 
     664            3 :   static AttributeLimitsEnvironmentValues _parseAttributeLimits({
     665              :     required String lengthVar,
     666              :     required String countVar,
     667              :     String? fallbackLengthVar,
     668              :     String? fallbackCountVar,
     669              :   }) {
     670            3 :     int? parseLimit(String primaryVar, String? fallbackVar) {
     671            3 :       var val = _getEnv(primaryVar);
     672              :       var varName = primaryVar;
     673              : 
     674              :       if (val == null && fallbackVar != null) {
     675            3 :         val = _getEnv(fallbackVar);
     676              :         varName = fallbackVar;
     677              :       }
     678              : 
     679              :       if (val != null) {
     680            1 :         final limit = int.tryParse(val);
     681            1 :         if (limit != null && limit >= 0) {
     682              :           return limit;
     683              :         }
     684            1 :         if (OTelLog.isWarn()) {
     685            2 :           OTelLog.warn('OTelEnv: Invalid value "$val" for $varName. '
     686              :               'Limit must be a non-negative integer.');
     687              :         }
     688              :       }
     689              :       return null;
     690              :     }
     691              : 
     692              :     return (
     693            3 :       attributeValueLengthLimit: parseLimit(lengthVar, fallbackLengthVar),
     694            3 :       attributeCountLimit: parseLimit(countVar, fallbackCountVar),
     695              :     );
     696              :   }
     697              : 
     698              :   /// Get general attribute limits from environment variables.
     699              :   ///
     700              :   /// These limits apply globally to all telemetry signals (traces, metrics,
     701              :   /// logs) unless overridden by signal-specific limits (e.g., span or
     702              :   /// log record attribute limits).
     703              :   ///
     704              :   /// Returns an [AttributeLimitsEnvironmentValues] containing the general attribute
     705              :   /// limits. Fields that are not set via environment variables will be `null`.
     706              :   ///
     707              :   /// Per the OpenTelemetry specification:
     708              :   /// - Values exceeding the length limit should be truncated.
     709              :   /// - Attributes exceeding the count limit should be dropped.
     710              :   /// - Warnings should be logged when limits are exceeded.
     711              :   ///
     712              :   /// See: https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#attribute-limits
     713            2 :   static AttributeLimitsEnvironmentValues getAttributeLimits() {
     714            2 :     return _parseAttributeLimits(
     715              :       lengthVar: otelAttributeValueLengthLimit,
     716              :       countVar: otelAttributeCountLimit,
     717              :     );
     718              :   }
     719              : 
     720              :   /// Get LogRecord attribute limits from environment variables.
     721              :   ///
     722              :   /// Returns a [AttributeLimitsEnvironmentValues] record containing the
     723              :   /// log record attribute limits. Fields are `null` when the corresponding
     724              :   /// env var is unset or contains a non-numeric value.
     725            3 :   static AttributeLimitsEnvironmentValues getLogRecordLimits() {
     726            3 :     return _parseAttributeLimits(
     727              :       lengthVar: otelLogrecordAttributeValueLengthLimit,
     728              :       countVar: otelLogrecordAttributeCountLimit,
     729              :       fallbackLengthVar: otelAttributeValueLengthLimit,
     730              :       fallbackCountVar: otelAttributeCountLimit,
     731              :     );
     732              :   }
     733              : 
     734              :   /// Get metrics SDK configuration from environment variables.
     735          131 :   static MetricsEnvironmentValues getMetricsConfig() {
     736              :     final exportIntervalMs =
     737          131 :         getPositiveIntEnv(otelMetricExportInterval, minInclusive: 0);
     738              :     final exportTimeoutMs =
     739          131 :         getPositiveIntEnv(otelMetricExportTimeout, minInclusive: 0);
     740              : 
     741              :     return (
     742          131 :       exemplarFilter: _getEnv(otelMetricsExemplarFilter),
     743              :       exportInterval: exportIntervalMs != null
     744            0 :           ? Duration(milliseconds: exportIntervalMs)
     745              :           : null,
     746              :       exportTimeout: exportTimeoutMs != null
     747            0 :           ? Duration(milliseconds: exportTimeoutMs)
     748              :           : null,
     749              :     );
     750              :   }
     751              : 
     752              :   /// Resolves whether an OTLP connection should use TLS, per the OTLP
     753              :   /// exporter spec's precedence:
     754              :   ///
     755              :   /// 1. if [endpoint] carries an `http://` or `https://` scheme, that scheme
     756              :   ///    alone decides — the spec says a scheme "takes precedence over the
     757              :   ///    `insecure` configuration setting", from either channel;
     758              :   /// 2. otherwise the endpoint is scheme-less (gRPC's native
     759              :   ///    `my-collector:4317` form), which is the only case where the insecure
     760              :   ///    setting applies at all. There, an explicit programmatic choice
     761              :   ///    ([explicitSecure]) wins, being the code equivalent of the
     762              :   ///    environment variable;
     763              :   /// 3. otherwise `OTEL_EXPORTER_OTLP_INSECURE` (or its per-signal
     764              :   ///    variant, passed as [envInsecure]) applies;
     765              :   /// 4. otherwise [fallback] (secure by default).
     766              :   ///
     767              :   /// Note that the setting is meaningful only for OTLP/gRPC: OTLP/HTTP
     768              :   /// always takes its security from the endpoint scheme.
     769              :   ///
     770              :   /// Bare `host:port` endpoints parse with a bogus scheme (`host`), so
     771              :   /// only exact `http`/`https` schemes participate in step 2.
     772          153 :   static bool resolveOtlpSecure({
     773              :     bool? explicitSecure,
     774              :     bool? envInsecure,
     775              :     String? endpoint,
     776              :     bool fallback = true,
     777              :   }) {
     778              :     // The endpoint scheme outranks the insecure setting from either
     779              :     // channel: "A scheme of https indicates a secure connection and takes
     780              :     // precedence over the insecure configuration setting" (and likewise
     781              :     // for http) - protocol/exporter.md, Endpoint (OTLP/gRPC).
     782              :     final scheme =
     783          456 :         endpoint == null ? null : Uri.tryParse(endpoint)?.scheme.toLowerCase();
     784          153 :     if (scheme == 'http') {
     785              :       return false;
     786              :     }
     787          129 :     if (scheme == 'https') {
     788              :       return true;
     789              :     }
     790              :     // Scheme-less endpoint: the insecure setting decides. The programmatic
     791              :     // choice is the code equivalent of OTEL_EXPORTER_OTLP_INSECURE and wins
     792              :     // over it, per "The environment-based configuration MUST have a direct
     793              :     // code configuration equivalent" - configuration/sdk-environment-variables.md.
     794              :     if (explicitSecure != null) {
     795              :       return explicitSecure;
     796              :     }
     797              :     if (envInsecure != null) {
     798              :       return !envInsecure;
     799              :     }
     800              :     return fallback;
     801              :   }
     802              : 
     803              :   /// Parse headers from the environment variable format.
     804              :   ///
     805              :   /// Headers are expected in the format: key1=value1,key2=value2
     806              :   /// Note: Header values can contain '=' characters (e.g., base64), so we only
     807              :   /// split on the first '=' for each pair.
     808            1 :   static Map<String, String> _parseHeaders(String headerStr) {
     809            1 :     final headers = <String, String>{};
     810              : 
     811            1 :     final pairs = headerStr.split(',');
     812            2 :     for (final pair in pairs) {
     813            1 :       final equalIndex = pair.indexOf('=');
     814            4 :       if (equalIndex > 0 && equalIndex < pair.length - 1) {
     815            2 :         final key = pair.substring(0, equalIndex).trim();
     816            3 :         final value = pair.substring(equalIndex + 1).trim();
     817            1 :         headers[key] = value;
     818              :       }
     819              :     }
     820              : 
     821              :     return headers;
     822              :   }
     823              : 
     824              :   /// Get environment variable value.
     825              :   ///
     826              :   /// This method safely retrieves an environment variable value,
     827              :   /// handling exceptions that might occur in environments where
     828              :   /// Platform is not available (e.g., browsers).
     829              :   ///
     830              :   /// @param name The name of the environment variable
     831              :   /// @return The value of the environment variable, or null if not found
     832          155 :   static String? _getEnv(String name) {
     833          310 :     return EnvironmentService.instance.getValue(name);
     834              :   }
     835              : 
     836              :   /// Get boolean environment variable value.
     837              :   ///
     838              :   /// This method converts an environment variable value to a boolean.
     839              :   /// Values of '1', 'true', 'yes', and 'on' (case-insensitive) are considered true.
     840              :   ///
     841              :   /// @param name The name of the environment variable
     842              :   /// @return true if the environment variable has a truthy value, false otherwise
     843          155 :   static bool _getEnvBool(String name) {
     844          155 :     final rawValue = _getEnv(name);
     845          143 :     if (rawValue == null || rawValue.isEmpty) return false;
     846              : 
     847          143 :     final value = rawValue.toLowerCase();
     848          143 :     if (value == 'true') {
     849              :       return true;
     850            1 :     } else if (value == 'false') {
     851              :       return false;
     852              :     } else {
     853            1 :       if (OTelLog.isWarn()) {
     854            2 :         OTelLog.warn('OTelEnv: Invalid boolean value for $name: "$rawValue". '
     855              :             'Expected "true" or "false". Treating as false.');
     856              :       }
     857              :       return false;
     858              :     }
     859              :   }
     860              : 
     861              :   /// Get boolean environment variable value that can be null.
     862              :   ///
     863              :   /// This method converts an environment variable value to a boolean.
     864              :   /// Only the case-insensitive string 'true' is considered true.
     865              :   /// All other values are considered false.
     866              :   /// Returns null only when the variable is unset or empty.
     867              :   ///
     868              :   /// @param name The name of the environment variable
     869              :   /// @return true/false if the environment variable is set, null otherwise
     870          155 :   static bool? _getEnvBoolNullable(String name) {
     871          155 :     final rawValue = _getEnv(name);
     872            3 :     if (rawValue == null || rawValue.isEmpty) return null;
     873              : 
     874            3 :     final value = rawValue.toLowerCase();
     875            3 :     if (value == 'true') {
     876              :       return true;
     877            2 :     } else if (value == 'false') {
     878              :       return false;
     879              :     } else {
     880            1 :       if (OTelLog.isWarn()) {
     881            2 :         OTelLog.warn('OTelEnv: Invalid boolean value for $name: "$rawValue". '
     882              :             'Expected "true" or "false". Treating as false.');
     883              :       }
     884              :       return false;
     885              :     }
     886              :   }
     887              : 
     888              :   /// Get a non-negative integer environment variable value.
     889              :   ///
     890              :   /// Returns null when not set, non-numeric, or outside the accepted range.
     891              :   /// Warns via [OTelLog.warn] when the raw value is present but unusable
     892              :   /// (non-numeric, below [minInclusive], or above [maxInclusive]).
     893          141 :   static int? getPositiveIntEnv(
     894              :     String name, {
     895              :     required int minInclusive,
     896              :     int? maxInclusive,
     897              :   }) {
     898          141 :     final rawValue = _getEnv(name);
     899              :     if (rawValue == null) {
     900              :       return null;
     901              :     }
     902              : 
     903            3 :     final value = int.tryParse(rawValue);
     904              :     if (value == null) {
     905            1 :       if (OTelLog.isWarn()) {
     906            2 :         OTelLog.warn('OTelEnv: Illegal non-numeric value for $name: '
     907              :             '"$rawValue", ignoring.');
     908              :       }
     909              :       return null;
     910              :     }
     911              : 
     912            3 :     if (value < minInclusive) {
     913            1 :       if (OTelLog.isWarn()) {
     914            2 :         OTelLog.warn('OTelEnv: Illegal value for $name: $value is below '
     915              :             'minimum $minInclusive, ignoring.');
     916              :       }
     917              :       return null;
     918              :     }
     919              : 
     920            0 :     if (maxInclusive != null && value > maxInclusive) {
     921            0 :       if (OTelLog.isWarn()) {
     922            0 :         OTelLog.warn('OTelEnv: Illegal value for $name: $value exceeds '
     923              :             'maximum $maxInclusive, ignoring.');
     924              :       }
     925              :       return null;
     926              :     }
     927              : 
     928              :     return value;
     929              :   }
     930              : }
        

Generated by: LCOV version 2.0-1