LCOV - code coverage report
Current view: top level - lib/src/environment - otel_env.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 60.5 % 205 124
Test Date: 2026-07-11 13:35:19 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 'env_constants.dart';
       6              : import 'environment_service.dart';
       7              : 
       8              : /// Utility class for handling OpenTelemetry environment variables.
       9              : ///
      10              : /// This class provides methods for reading standard OpenTelemetry environment
      11              : /// variables and applying their configuration to the SDK.
      12              : ///
      13              : /// OpenTelemetry standard environment variables:
      14              : /// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/
      15              : class OTelEnv {
      16              :   /// Initialize logging based on environment variables.
      17              :   ///
      18              :   /// This method reads the logging-related environment variables
      19              :   /// and configures the OTelLog accordingly.
      20              :   ///
      21              :   /// If a custom log function has already been set (e.g., by tests),
      22              :   /// this method will preserve it along with the current log level.
      23              :   /// This allows tests to fully control logging configuration without
      24              :   /// environment variables overriding their settings.
      25          129 :   static void initializeLogging() {
      26              :     // Save the current log function to check if it's custom
      27          129 :     final existingLogFunction = OTelLog.logFunction;
      28              : 
      29              :     // A custom function is one that's not null and not the default print function
      30              :     final hasCustomLogFunction =
      31          129 :         existingLogFunction != null && existingLogFunction != print;
      32              : 
      33              :     // Set log level and function based on environment variable,
      34              :     // but only if no custom log function is already configured.
      35          258 :     final logLevel = _getEnv(otelLogLevel)?.toLowerCase();
      36              :     if (logLevel != null && !hasCustomLogFunction) {
      37              :       switch (logLevel) {
      38          116 :         case 'trace':
      39          116 :           OTelLog.enableTraceLogging();
      40              :           break;
      41            0 :         case 'debug':
      42            0 :           OTelLog.enableDebugLogging();
      43              :           break;
      44            0 :         case 'info':
      45            0 :           OTelLog.enableInfoLogging();
      46              :           break;
      47            0 :         case 'warn':
      48            0 :           OTelLog.enableWarnLogging();
      49              :           break;
      50            0 :         case 'error':
      51            0 :           OTelLog.enableErrorLogging();
      52              :           break;
      53            0 :         case 'fatal':
      54            0 :           OTelLog.enableFatalLogging();
      55              :           break;
      56              :         default:
      57              :           // No change to logging if level not recognized
      58              :           break;
      59              :       }
      60              : 
      61              :       OTelLog.logFunction = print;
      62              :     }
      63              : 
      64              :     // Enable metrics logging based on environment variable
      65          129 :     if (_getEnvBool(otelLogMetrics) && OTelLog.metricLogFunction == null) {
      66              :       OTelLog.metricLogFunction = print;
      67              :     }
      68              : 
      69              :     // Enable spans logging based on environment variable
      70          129 :     if (_getEnvBool(otelLogSpans) && OTelLog.spanLogFunction == null) {
      71              :       OTelLog.spanLogFunction = print;
      72              :     }
      73              : 
      74              :     // Enable export logging based on environment variable
      75          129 :     if (_getEnvBool(otelLogExport) && OTelLog.exportLogFunction == null) {
      76              :       OTelLog.exportLogFunction = print;
      77              :     }
      78              :   }
      79              : 
      80              :   /// Get OTLP configuration from environment variables.
      81              :   ///
      82              :   /// Returns a map containing the OTLP configuration read from environment variables.
      83              :   /// Signal-specific variables take precedence over general ones.
      84          129 :   static Map<String, dynamic> getOtlpConfig({String signal = 'traces'}) {
      85          129 :     final config = <String, dynamic>{};
      86              : 
      87              :     // Get endpoint (signal-specific takes precedence)
      88              :     String? endpoint;
      89              :     switch (signal) {
      90          129 :       case 'traces':
      91          129 :         endpoint = _getEnv(otelExporterOtlpTracesEndpoint) ??
      92          129 :             _getEnv(otelExporterOtlpEndpoint);
      93              :         break;
      94          129 :       case 'metrics':
      95          121 :         endpoint = _getEnv(otelExporterOtlpMetricsEndpoint) ??
      96          121 :             _getEnv(otelExporterOtlpEndpoint);
      97              :         break;
      98          117 :       case 'logs':
      99          117 :         endpoint = _getEnv(otelExporterOtlpLogsEndpoint) ??
     100          117 :             _getEnv(otelExporterOtlpEndpoint);
     101              :         break;
     102              :     }
     103              :     if (endpoint != null) {
     104            0 :       config['endpoint'] = endpoint;
     105              :     }
     106              : 
     107              :     // Get protocol (signal-specific takes precedence)
     108              :     String? protocol;
     109              :     switch (signal) {
     110          129 :       case 'traces':
     111          129 :         protocol = _getEnv(otelExporterOtlpTracesProtocol) ??
     112          129 :             _getEnv(otelExporterOtlpProtocol);
     113              :         break;
     114          129 :       case 'metrics':
     115          121 :         protocol = _getEnv(otelExporterOtlpMetricsProtocol) ??
     116          121 :             _getEnv(otelExporterOtlpProtocol);
     117              :         break;
     118          117 :       case 'logs':
     119          117 :         protocol = _getEnv(otelExporterOtlpLogsProtocol) ??
     120          117 :             _getEnv(otelExporterOtlpProtocol);
     121              :         break;
     122              :     }
     123              :     if (protocol != null) {
     124            0 :       config['protocol'] = protocol;
     125              :     }
     126              : 
     127              :     // Get headers (signal-specific takes precedence)
     128              :     String? headers;
     129              :     switch (signal) {
     130          129 :       case 'traces':
     131          129 :         headers = _getEnv(otelExporterOtlpTracesHeaders) ??
     132          129 :             _getEnv(otelExporterOtlpHeaders);
     133              :         break;
     134          129 :       case 'metrics':
     135          121 :         headers = _getEnv(otelExporterOtlpMetricsHeaders) ??
     136          121 :             _getEnv(otelExporterOtlpHeaders);
     137              :         break;
     138          117 :       case 'logs':
     139          117 :         headers = _getEnv(otelExporterOtlpLogsHeaders) ??
     140          117 :             _getEnv(otelExporterOtlpHeaders);
     141              :         break;
     142              :     }
     143              :     if (headers != null) {
     144            0 :       if (OTelLog.isDebug()) {
     145            0 :         OTelLog.debug('OTelEnv: Parsing $signal headers from env: $headers');
     146              :       }
     147            0 :       final parsedHeaders = _parseHeaders(headers);
     148            0 :       if (OTelLog.isDebug()) {
     149            0 :         OTelLog.debug('OTelEnv: Parsed ${parsedHeaders.length} header(s)');
     150            0 :         parsedHeaders.forEach((key, value) {
     151            0 :           if (key.toLowerCase() == 'authorization') {
     152            0 :             OTelLog.debug('  $key: [REDACTED - length: ${value.length}]');
     153              :           } else {
     154            0 :             OTelLog.debug('  $key: $value');
     155              :           }
     156              :         });
     157              :       }
     158            0 :       config['headers'] = parsedHeaders;
     159              :     }
     160              : 
     161              :     // Get insecure setting (signal-specific takes precedence)
     162              :     bool? insecure;
     163              :     switch (signal) {
     164          129 :       case 'traces':
     165          129 :         insecure = _getEnvBoolNullable(otelExporterOtlpTracesInsecure) ??
     166          129 :             _getEnvBoolNullable(otelExporterOtlpInsecure);
     167              :         break;
     168          129 :       case 'metrics':
     169          121 :         insecure = _getEnvBoolNullable(otelExporterOtlpMetricsInsecure) ??
     170          121 :             _getEnvBoolNullable(otelExporterOtlpInsecure);
     171              :         break;
     172          117 :       case 'logs':
     173          117 :         insecure = _getEnvBoolNullable(otelExporterOtlpLogsInsecure) ??
     174          117 :             _getEnvBoolNullable(otelExporterOtlpInsecure);
     175              :         break;
     176              :     }
     177              :     if (insecure != null) {
     178            0 :       config['insecure'] = insecure;
     179              :     }
     180              : 
     181              :     // Get timeout (signal-specific takes precedence)
     182              :     String? timeout;
     183              :     switch (signal) {
     184          129 :       case 'traces':
     185          129 :         timeout = _getEnv(otelExporterOtlpTracesTimeout) ??
     186          129 :             _getEnv(otelExporterOtlpTimeout);
     187              :         break;
     188          129 :       case 'metrics':
     189          121 :         timeout = _getEnv(otelExporterOtlpMetricsTimeout) ??
     190          121 :             _getEnv(otelExporterOtlpTimeout);
     191              :         break;
     192          117 :       case 'logs':
     193          117 :         timeout = _getEnv(otelExporterOtlpLogsTimeout) ??
     194          117 :             _getEnv(otelExporterOtlpTimeout);
     195              :         break;
     196              :     }
     197              :     if (timeout != null) {
     198            0 :       final timeoutMs = int.tryParse(timeout);
     199              :       if (timeoutMs != null) {
     200            0 :         config['timeout'] = Duration(milliseconds: timeoutMs);
     201              :       }
     202              :     }
     203              : 
     204              :     // Get compression (signal-specific takes precedence)
     205              :     String? compression;
     206              :     switch (signal) {
     207          129 :       case 'traces':
     208          129 :         compression = _getEnv(otelExporterOtlpTracesCompression) ??
     209          129 :             _getEnv(otelExporterOtlpCompression);
     210              :         break;
     211          129 :       case 'metrics':
     212          121 :         compression = _getEnv(otelExporterOtlpMetricsCompression) ??
     213          121 :             _getEnv(otelExporterOtlpCompression);
     214              :         break;
     215          117 :       case 'logs':
     216          117 :         compression = _getEnv(otelExporterOtlpLogsCompression) ??
     217          117 :             _getEnv(otelExporterOtlpCompression);
     218              :         break;
     219              :     }
     220              :     if (compression != null) {
     221            0 :       config['compression'] = compression;
     222              :     }
     223              : 
     224              :     // Get certificate (signal-specific takes precedence)
     225              :     String? certificate;
     226              :     switch (signal) {
     227          129 :       case 'traces':
     228          129 :         certificate = _getEnv(otelExporterOtlpTracesCertificate) ??
     229          129 :             _getEnv(otelExporterOtlpCertificate);
     230              :         break;
     231          129 :       case 'metrics':
     232          121 :         certificate = _getEnv(otelExporterOtlpMetricsCertificate) ??
     233          121 :             _getEnv(otelExporterOtlpCertificate);
     234              :         break;
     235          117 :       case 'logs':
     236          117 :         certificate = _getEnv(otelExporterOtlpLogsCertificate) ??
     237          117 :             _getEnv(otelExporterOtlpCertificate);
     238              :         break;
     239              :     }
     240              :     if (certificate != null) {
     241            0 :       config['certificate'] = certificate;
     242              :     }
     243              : 
     244              :     // Get client key (signal-specific takes precedence)
     245              :     String? clientKey;
     246              :     switch (signal) {
     247          129 :       case 'traces':
     248          129 :         clientKey = _getEnv(otelExporterOtlpTracesClientKey) ??
     249          129 :             _getEnv(otelExporterOtlpClientKey);
     250              :         break;
     251          129 :       case 'metrics':
     252          121 :         clientKey = _getEnv(otelExporterOtlpMetricsClientKey) ??
     253          121 :             _getEnv(otelExporterOtlpClientKey);
     254              :         break;
     255          117 :       case 'logs':
     256          117 :         clientKey = _getEnv(otelExporterOtlpLogsClientKey) ??
     257          117 :             _getEnv(otelExporterOtlpClientKey);
     258              :         break;
     259              :     }
     260              :     if (clientKey != null) {
     261            0 :       config['clientKey'] = clientKey;
     262              :     }
     263              : 
     264              :     // Get client certificate (signal-specific takes precedence)
     265              :     String? clientCertificate;
     266              :     switch (signal) {
     267          129 :       case 'traces':
     268          129 :         clientCertificate = _getEnv(otelExporterOtlpTracesClientCertificate) ??
     269          129 :             _getEnv(otelExporterOtlpClientCertificate);
     270              :         break;
     271          129 :       case 'metrics':
     272          121 :         clientCertificate = _getEnv(otelExporterOtlpMetricsClientCertificate) ??
     273          121 :             _getEnv(otelExporterOtlpClientCertificate);
     274              :         break;
     275          117 :       case 'logs':
     276          117 :         clientCertificate = _getEnv(otelExporterOtlpLogsClientCertificate) ??
     277          117 :             _getEnv(otelExporterOtlpClientCertificate);
     278              :         break;
     279              :     }
     280              :     if (clientCertificate != null) {
     281            0 :       config['clientCertificate'] = clientCertificate;
     282              :     }
     283              : 
     284              :     return config;
     285              :   }
     286              : 
     287              :   /// Get service configuration from environment variables.
     288              :   ///
     289              :   /// Returns a map containing the service configuration read from environment variables.
     290              :   ///
     291              :   /// Handles the spec precedence rules:
     292              :   /// - If `service.name` is in OTEL_RESOURCE_ATTRIBUTES, it's used as the base value
     293              :   /// - OTEL_SERVICE_NAME takes precedence over `service.name` in OTEL_RESOURCE_ATTRIBUTES
     294              :   /// - `service.version` comes from OTEL_RESOURCE_ATTRIBUTES only
     295          108 :   static Map<String, dynamic> getServiceConfig() {
     296          108 :     final config = <String, dynamic>{};
     297              : 
     298              :     // First, parse service.name and service.version from OTEL_RESOURCE_ATTRIBUTES
     299          108 :     final resourceStr = _getEnv(otelResourceAttributes);
     300              :     if (resourceStr != null) {
     301            0 :       final pairs = resourceStr.split(',');
     302            0 :       for (final pair in pairs) {
     303            0 :         final equalIndex = pair.indexOf('=');
     304            0 :         if (equalIndex > 0 && equalIndex < pair.length - 1) {
     305            0 :           final key = pair.substring(0, equalIndex).trim();
     306            0 :           final value = pair.substring(equalIndex + 1).trim();
     307              : 
     308            0 :           if (key == 'service.name') {
     309            0 :             config['serviceName'] = value;
     310            0 :           } else if (key == 'service.version') {
     311            0 :             config['serviceVersion'] = value;
     312              :           }
     313              :         }
     314              :       }
     315              :     }
     316              : 
     317              :     // OTEL_SERVICE_NAME takes precedence over service.name from resource attributes
     318          108 :     final serviceName = _getEnv(otelServiceName);
     319              :     if (serviceName != null) {
     320            0 :       config['serviceName'] = serviceName;
     321              :     }
     322              : 
     323              :     return config;
     324              :   }
     325              : 
     326              :   /// Get resource attributes from environment variables.
     327              :   ///
     328              :   /// Parses the OTEL_RESOURCE_ATTRIBUTES environment variable which should be
     329              :   /// a comma-separated list of key=value pairs.
     330          129 :   static Map<String, Object> getResourceAttributes() {
     331          129 :     final resourceAttrs = <String, Object>{};
     332              : 
     333          129 :     final resourceStr = _getEnv(otelResourceAttributes);
     334              :     if (resourceStr != null) {
     335            0 :       final pairs = resourceStr.split(',');
     336            0 :       for (final pair in pairs) {
     337            0 :         final parts = pair.split('=');
     338            0 :         if (parts.length == 2) {
     339            0 :           final key = parts[0].trim();
     340            0 :           final value = parts[1].trim();
     341              :           // Try to parse as number if possible
     342            0 :           final intValue = int.tryParse(value);
     343              :           if (intValue != null) {
     344            0 :             resourceAttrs[key] = intValue;
     345              :           } else {
     346            0 :             final doubleValue = double.tryParse(value);
     347              :             if (doubleValue != null) {
     348            0 :               resourceAttrs[key] = doubleValue;
     349              :             } else {
     350              :               // Handle boolean values
     351            0 :               if (value.toLowerCase() == 'true') {
     352            0 :                 resourceAttrs[key] = true;
     353            0 :               } else if (value.toLowerCase() == 'false') {
     354            0 :                 resourceAttrs[key] = false;
     355              :               } else {
     356            0 :                 resourceAttrs[key] = value;
     357              :               }
     358              :             }
     359              :           }
     360              :         }
     361              :       }
     362              :     }
     363              : 
     364              :     return resourceAttrs;
     365              :   }
     366              : 
     367              :   /// Whether `OTEL_SDK_DISABLED` is set to a truthy value.
     368              :   ///
     369              :   /// Per the OTel spec, when this is true the SDK acts as a no-op for all
     370              :   /// signals — no span processors, metric readers, or log record processors
     371              :   /// should be installed.
     372          256 :   static bool isSdkDisabled() => _getEnvBool(otelSdkDisabled);
     373              : 
     374              :   /// Get the selected exporter for a signal.
     375              :   ///
     376              :   /// Returns the exporter type configured via environment variables.
     377          128 :   static String? getExporter({String signal = 'traces'}) {
     378              :     switch (signal) {
     379          128 :       case 'traces':
     380          122 :         return _getEnv(otelTracesExporter);
     381          128 :       case 'metrics':
     382          107 :         return _getEnv(otelMetricsExporter);
     383          119 :       case 'logs':
     384          119 :         return _getEnv(otelLogsExporter);
     385              :       default:
     386              :         return null;
     387              :     }
     388              :   }
     389              : 
     390              :   /// Get Batch LogRecord Processor (BLRP) configuration from environment variables.
     391              :   ///
     392              :   /// Returns a map containing the BLRP configuration read from environment variables.
     393              :   /// Keys returned:
     394              :   /// - 'scheduleDelay': Duration for the schedule delay
     395              :   /// - 'exportTimeout': Duration for the export timeout
     396              :   /// - 'maxQueueSize': int for maximum queue size
     397              :   /// - 'maxExportBatchSize': int for maximum export batch size
     398          118 :   static Map<String, dynamic> getBlrpConfig() {
     399          118 :     final config = <String, dynamic>{};
     400              : 
     401              :     // Get schedule delay
     402          118 :     final scheduleDelay = _getEnv(otelBlrpScheduleDelay);
     403              :     if (scheduleDelay != null) {
     404            0 :       final delayMs = int.tryParse(scheduleDelay);
     405              :       if (delayMs != null) {
     406            0 :         config['scheduleDelay'] = Duration(milliseconds: delayMs);
     407              :       }
     408              :     }
     409              : 
     410              :     // Get export timeout
     411          118 :     final exportTimeout = _getEnv(otelBlrpExportTimeout);
     412              :     if (exportTimeout != null) {
     413            0 :       final timeoutMs = int.tryParse(exportTimeout);
     414              :       if (timeoutMs != null) {
     415            0 :         config['exportTimeout'] = Duration(milliseconds: timeoutMs);
     416              :       }
     417              :     }
     418              : 
     419              :     // Get max queue size
     420          118 :     final maxQueueSize = _getEnv(otelBlrpMaxQueueSize);
     421              :     if (maxQueueSize != null) {
     422            0 :       final size = int.tryParse(maxQueueSize);
     423              :       if (size != null) {
     424            0 :         config['maxQueueSize'] = size;
     425              :       }
     426              :     }
     427              : 
     428              :     // Get max export batch size
     429          118 :     final maxExportBatchSize = _getEnv(otelBlrpMaxExportBatchSize);
     430              :     if (maxExportBatchSize != null) {
     431            0 :       final size = int.tryParse(maxExportBatchSize);
     432              :       if (size != null) {
     433            0 :         config['maxExportBatchSize'] = size;
     434              :       }
     435              :     }
     436              : 
     437              :     return config;
     438              :   }
     439              : 
     440              :   /// Get LogRecord attribute limits from environment variables.
     441              :   ///
     442              :   /// Returns a map containing the log record attribute limits.
     443              :   /// Keys returned:
     444              :   /// - 'attributeValueLengthLimit': int for max attribute value length
     445              :   /// - 'attributeCountLimit': int for max number of attributes
     446            2 :   static Map<String, dynamic> getLogRecordLimits() {
     447            2 :     final config = <String, dynamic>{};
     448              : 
     449              :     // Get attribute value length limit
     450            2 :     final valueLengthLimit = _getEnv(otelLogrecordAttributeValueLengthLimit);
     451              :     if (valueLengthLimit != null) {
     452            0 :       final limit = int.tryParse(valueLengthLimit);
     453              :       if (limit != null) {
     454            0 :         config['attributeValueLengthLimit'] = limit;
     455              :       }
     456              :     }
     457              : 
     458              :     // Get attribute count limit
     459            2 :     final countLimit = _getEnv(otelLogrecordAttributeCountLimit);
     460              :     if (countLimit != null) {
     461            0 :       final limit = int.tryParse(countLimit);
     462              :       if (limit != null) {
     463            0 :         config['attributeCountLimit'] = limit;
     464              :       }
     465              :     }
     466              : 
     467              :     return config;
     468              :   }
     469              : 
     470              :   /// Parse headers from the environment variable format.
     471              :   ///
     472              :   /// Headers are expected in the format: key1=value1,key2=value2
     473              :   /// Note: Header values can contain '=' characters (e.g., base64), so we only
     474              :   /// split on the first '=' for each pair.
     475            0 :   static Map<String, String> _parseHeaders(String headerStr) {
     476            0 :     final headers = <String, String>{};
     477              : 
     478            0 :     final pairs = headerStr.split(',');
     479            0 :     for (final pair in pairs) {
     480            0 :       final equalIndex = pair.indexOf('=');
     481            0 :       if (equalIndex > 0 && equalIndex < pair.length - 1) {
     482            0 :         final key = pair.substring(0, equalIndex).trim();
     483            0 :         final value = pair.substring(equalIndex + 1).trim();
     484            0 :         headers[key] = value;
     485              :       }
     486              :     }
     487              : 
     488              :     return headers;
     489              :   }
     490              : 
     491              :   /// Get environment variable value.
     492              :   ///
     493              :   /// This method safely retrieves an environment variable value,
     494              :   /// handling exceptions that might occur in environments where
     495              :   /// Platform is not available (e.g., browsers).
     496              :   ///
     497              :   /// @param name The name of the environment variable
     498              :   /// @return The value of the environment variable, or null if not found
     499          129 :   static String? _getEnv(String name) {
     500          258 :     return EnvironmentService.instance.getValue(name);
     501              :   }
     502              : 
     503              :   /// Get boolean environment variable value.
     504              :   ///
     505              :   /// This method converts an environment variable value to a boolean.
     506              :   /// Values of '1', 'true', 'yes', and 'on' (case-insensitive) are considered true.
     507              :   ///
     508              :   /// @param name The name of the environment variable
     509              :   /// @return true if the environment variable has a truthy value, false otherwise
     510          129 :   static bool _getEnvBool(String name) {
     511          258 :     final value = _getEnv(name)?.toLowerCase();
     512          514 :     return value == '1' || value == 'true' || value == 'yes' || value == 'on';
     513              :   }
     514              : 
     515              :   /// Get boolean environment variable value that can be null.
     516              :   ///
     517              :   /// This method converts an environment variable value to a boolean.
     518              :   /// Values of '1', 'true', 'yes', and 'on' (case-insensitive) are considered true.
     519              :   /// Values of '0', 'false', 'no', and 'off' (case-insensitive) are considered false.
     520              :   ///
     521              :   /// @param name The name of the environment variable
     522              :   /// @return true/false if the environment variable has a valid boolean value, null otherwise
     523          129 :   static bool? _getEnvBoolNullable(String name) {
     524          129 :     final value = _getEnv(name)?.toLowerCase();
     525              :     if (value == null) return null;
     526              : 
     527            0 :     if (value == '1' || value == 'true' || value == 'yes' || value == 'on') {
     528              :       return true;
     529            0 :     } else if (value == '0' ||
     530            0 :         value == 'false' ||
     531            0 :         value == 'no' ||
     532            0 :         value == 'off') {
     533              :       return false;
     534              :     }
     535              : 
     536              :     return null;
     537              :   }
     538              : }
        

Generated by: LCOV version 2.0-1