LCOV - code coverage report
Current view: top level - lib/src/trace/export/otlp - otlp_grpc_span_exporter_config.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 85.3 % 68 58
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 'certificate_utils.dart';
       5              : 
       6              : /// Configuration for the OtlpGrpcSpanExporter.
       7              : ///
       8              : /// This class configures how the OTLP gRPC span exporter connects to and communicates
       9              : /// with the OpenTelemetry collector or backend. It allows customization of connection
      10              : /// parameters such as endpoint, security settings, timeouts, and retry behavior.
      11              : class OtlpGrpcExporterConfig {
      12              :   /// The endpoint to which the exporter will send spans, in the format 'host:port'.
      13              :   final String endpoint;
      14              : 
      15              :   /// Custom headers to include in each gRPC request, for authentication or metadata.
      16              :   final Map<String, String> headers;
      17              : 
      18              :   /// Timeout for gRPC operations, after which they'll fail.
      19              :   final Duration timeout;
      20              : 
      21              :   /// Whether to enable gRPC compression for requests.
      22              :   final bool compression;
      23              : 
      24              :   /// Whether to use an insecure connection (true) or TLS (false).
      25              :   final bool insecure;
      26              : 
      27              :   /// Maximum number of retry attempts for failed exports.
      28              :   final int maxRetries;
      29              : 
      30              :   /// Base delay for retry backoff calculation.
      31              :   final Duration baseDelay;
      32              : 
      33              :   /// Maximum delay between retry attempts.
      34              :   final Duration maxDelay;
      35              : 
      36              :   /// Path to the TLS certificate file for secure connections.
      37              :   final String? certificate;
      38              : 
      39              :   /// Path to the client key file for secure connections with client authentication.
      40              :   final String? clientKey;
      41              : 
      42              :   /// Path to the client certificate file for secure connections with client authentication.
      43              :   final String? clientCertificate;
      44              : 
      45              :   /// Creates a new OtlpGrpcExporterConfig with the specified parameters.
      46              :   ///
      47              :   /// This configuration controls the connection and behavior settings for the
      48              :   /// OTLP gRPC exporter.
      49              :   ///
      50              :   /// @param endpoint The endpoint to connect to (default: localhost:4317)
      51              :   /// @param headers Custom headers to include in the requests
      52              :   /// @param timeout Timeout for gRPC operations
      53              :   /// @param compression Whether to enable gRPC compression
      54              :   /// @param insecure Whether to use an insecure connection
      55              :   /// @param maxRetries Maximum number of retry attempts
      56              :   /// @param baseDelay Base delay for retry backoff
      57              :   /// @param maxDelay Maximum delay between retry attempts
      58              :   /// @param certificate Path to the TLS certificate file
      59              :   /// @param clientKey Path to the client key file
      60              :   /// @param clientCertificate Path to the client certificate file
      61            7 :   OtlpGrpcExporterConfig({
      62              :     String endpoint = 'localhost:4317',
      63              :     Map<String, String>? headers,
      64              :     Duration timeout = const Duration(seconds: 10),
      65              :     this.compression = false,
      66              :     this.insecure = false,
      67              :     int maxRetries = 3,
      68              :     Duration baseDelay = const Duration(milliseconds: 100),
      69              :     Duration maxDelay = const Duration(seconds: 1),
      70              :     this.certificate,
      71              :     this.clientKey,
      72              :     this.clientCertificate,
      73            7 :   })  : endpoint = _validateEndpoint(endpoint),
      74           14 :         headers = _validateHeaders(headers ?? {}),
      75            7 :         timeout = _validateTimeout(timeout),
      76            7 :         maxRetries = _validateRetries(maxRetries),
      77            7 :         baseDelay = _validateDelay(baseDelay, 'baseDelay'),
      78            7 :         maxDelay = _validateDelay(maxDelay, 'maxDelay') {
      79           14 :     if (baseDelay.compareTo(maxDelay) > 0) {
      80            3 :       throw ArgumentError('maxDelay cannot be less than baseDelay');
      81              :     }
      82           28 :     _validateCertificates(certificate, clientKey, clientCertificate);
      83              :   }
      84              : 
      85            7 :   static Map<String, String> _validateHeaders(Map<String, String> headers) {
      86            7 :     final normalized = <String, String>{};
      87           11 :     for (final entry in headers.entries) {
      88           14 :       if (entry.key.isEmpty || entry.value.isEmpty) {
      89            3 :         throw ArgumentError('Header keys and values cannot be empty');
      90              :       }
      91           12 :       normalized[entry.key.toLowerCase()] = entry.value;
      92              :     }
      93              :     return normalized;
      94              :   }
      95              : 
      96            7 :   static String _validateEndpoint(String endpoint) {
      97            7 :     if (endpoint.isEmpty) {
      98            2 :       throw ArgumentError('Endpoint cannot be empty');
      99              :     }
     100              : 
     101              :     // Handle common localhost variants and validate basic format
     102            7 :     endpoint = endpoint.trim();
     103              : 
     104              :     // First check for invalid formats
     105            7 :     if (endpoint.contains(' ')) {
     106            6 :       throw ArgumentError('Endpoint cannot contain spaces: $endpoint');
     107              :     }
     108              : 
     109              :     // Check for specific invalid formats that might parse but are invalid
     110           14 :     if (endpoint.contains(':port') || endpoint.contains('://port')) {
     111            2 :       throw ArgumentError('Invalid port specification in endpoint: $endpoint');
     112              :     }
     113              : 
     114            7 :     final lcEndpoint = endpoint.toLowerCase();
     115           14 :     if (lcEndpoint == 'localhost' || lcEndpoint == '127.0.0.1') {
     116            2 :       return '$endpoint:4317'; // Add default port if missing
     117              :     }
     118              : 
     119              :     // Handle URL format validation more carefully
     120           11 :     if (endpoint.startsWith('http://') || endpoint.startsWith('https://')) {
     121              :       try {
     122            6 :         final uri = Uri.parse(endpoint);
     123           12 :         if (uri.host.isEmpty) {
     124            2 :           throw ArgumentError('Invalid host in endpoint: $endpoint');
     125              :         }
     126           12 :         if (uri.port == 0 && !endpoint.contains(':')) {
     127              :           // No port specified in URL format, add default
     128            0 :           return '${uri.scheme}://${uri.host}:4317${uri.path}';
     129              :         }
     130           12 :         if (uri.port == 0 &&
     131            0 :             endpoint.contains(':') &&
     132            0 :             !endpoint.contains('://:')) {
     133              :           // Port part exists but might be invalid
     134            0 :           final portStr = endpoint.split(':').last;
     135            0 :           if (int.tryParse(portStr) == null) {
     136            0 :             throw ArgumentError(
     137            0 :               'Invalid port format in endpoint URL: $endpoint',
     138              :             );
     139              :           }
     140              :         }
     141              :         return endpoint;
     142              :       } catch (e) {
     143            1 :         if (e is ArgumentError) rethrow;
     144            0 :         throw ArgumentError('Invalid URL format in endpoint: $endpoint');
     145              :       }
     146              :     }
     147              : 
     148              :     // Try to parse as URI or host:port
     149              :     try {
     150            4 :       final parts = endpoint.split(':');
     151           12 :       if (parts.length == 1 && parts[0].isNotEmpty) {
     152              :         // Only host provided, add default port
     153            4 :         return '${parts[0]}:4317';
     154           16 :       } else if (parts.length == 2 && parts[0].isNotEmpty) {
     155              :         // Validate port is a number if specified
     156            8 :         if (parts[1].isEmpty) {
     157            4 :           throw ArgumentError('Invalid port format in endpoint: $endpoint');
     158              :         }
     159            8 :         if (int.tryParse(parts[1]) == null) {
     160            2 :           throw ArgumentError('Invalid port format in endpoint: $endpoint');
     161              :         }
     162              :         // Host and port provided
     163              :         return endpoint;
     164              :       }
     165              : 
     166            1 :       throw ArgumentError(
     167            1 :         'Invalid endpoint format: $endpoint. Expected format: "host:port" or a valid URI',
     168              :       );
     169              :     } catch (e) {
     170            2 :       if (e is ArgumentError) rethrow; // Re-throw our own errors
     171              : 
     172              :       // Any other parsing error
     173            0 :       throw ArgumentError(
     174            0 :         'Invalid endpoint format: $endpoint. Expected format: "host:port" or a valid URI',
     175              :       );
     176              :     }
     177              :   }
     178              : 
     179            7 :   static Duration _validateTimeout(Duration timeout) {
     180            7 :     if (timeout < const Duration(milliseconds: 1) ||
     181            7 :         timeout > const Duration(minutes: 10)) {
     182            3 :       throw ArgumentError('Timeout must be between 1ms and 10 minutes');
     183              :     }
     184              :     return timeout;
     185              :   }
     186              : 
     187            7 :   static int _validateRetries(int retries) {
     188            7 :     if (retries < 0) {
     189            3 :       throw ArgumentError('maxRetries cannot be negative');
     190              :     }
     191              :     return retries;
     192              :   }
     193              : 
     194            7 :   static Duration _validateDelay(Duration delay, String name) {
     195            7 :     if (delay < const Duration(milliseconds: 1) ||
     196            7 :         delay > const Duration(minutes: 5)) {
     197            4 :       throw ArgumentError('$name must be between 1ms and 5 minutes');
     198              :     }
     199              :     return delay;
     200              :   }
     201              : 
     202            7 :   static void _validateCertificates(
     203              :     String? cert,
     204              :     String? key,
     205              :     String? clientCert,
     206              :   ) {
     207            7 :     CertificateUtils.validateCertificates(
     208              :       certificate: cert,
     209              :       clientKey: key,
     210              :       clientCertificate: clientCert,
     211              :     );
     212              :   }
     213              : }
        

Generated by: LCOV version 2.0-1