LCOV - code coverage report
Current view: top level - src/trace/export/otlp - otlp_grpc_span_exporter_config.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 80.9 % 68 55
Test Date: 2025-11-15 13:23:01 Functions: - 0 0

            Line data    Source code
       1              : // Licensed under the Apache License, Version 2.0
       2              : // Copyright 2025, Michael Bushe, All rights reserved.
       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            2 :   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            2 :   })  : endpoint = _validateEndpoint(endpoint),
      74            4 :         headers = _validateHeaders(headers ?? {}),
      75            2 :         timeout = _validateTimeout(timeout),
      76            2 :         maxRetries = _validateRetries(maxRetries),
      77            2 :         baseDelay = _validateDelay(baseDelay, 'baseDelay'),
      78            2 :         maxDelay = _validateDelay(maxDelay, 'maxDelay') {
      79            4 :     if (baseDelay.compareTo(maxDelay) > 0) {
      80            1 :       throw ArgumentError('maxDelay cannot be less than baseDelay');
      81              :     }
      82            8 :     _validateCertificates(certificate, clientKey, clientCertificate);
      83              :   }
      84              : 
      85            2 :   static Map<String, String> _validateHeaders(Map<String, String> headers) {
      86            2 :     final normalized = <String, String>{};
      87            3 :     for (final entry in headers.entries) {
      88            4 :       if (entry.key.isEmpty || entry.value.isEmpty) {
      89            1 :         throw ArgumentError('Header keys and values cannot be empty');
      90              :       }
      91            4 :       normalized[entry.key.toLowerCase()] = entry.value;
      92              :     }
      93              :     return normalized;
      94              :   }
      95              : 
      96            2 :   static String _validateEndpoint(String endpoint) {
      97            2 :     if (endpoint.isEmpty) {
      98            1 :       throw ArgumentError('Endpoint cannot be empty');
      99              :     }
     100              : 
     101              :     // Handle common localhost variants and validate basic format
     102            2 :     endpoint = endpoint.trim();
     103              : 
     104              :     // First check for invalid formats
     105            2 :     if (endpoint.contains(' ')) {
     106            2 :       throw ArgumentError('Endpoint cannot contain spaces: $endpoint');
     107              :     }
     108              : 
     109              :     // Check for specific invalid formats that might parse but are invalid
     110            4 :     if (endpoint.contains(':port') || endpoint.contains('://port')) {
     111            2 :       throw ArgumentError('Invalid port specification in endpoint: $endpoint');
     112              :     }
     113              : 
     114            2 :     final lcEndpoint = endpoint.toLowerCase();
     115            4 :     if (lcEndpoint == 'localhost' || lcEndpoint == '127.0.0.1') {
     116            0 :       return '$endpoint:4317'; // Add default port if missing
     117              :     }
     118              : 
     119              :     // Handle URL format validation more carefully
     120            3 :     if (endpoint.startsWith('http://') || endpoint.startsWith('https://')) {
     121              :       try {
     122            2 :         final uri = Uri.parse(endpoint);
     123            4 :         if (uri.host.isEmpty) {
     124            2 :           throw ArgumentError('Invalid host in endpoint: $endpoint');
     125              :         }
     126            4 :         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            4 :         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              :         return endpoint;
     141              :       } catch (e) {
     142            1 :         if (e is ArgumentError) rethrow;
     143            0 :         throw ArgumentError('Invalid URL format in endpoint: $endpoint');
     144              :       }
     145              :     }
     146              : 
     147              :     // Try to parse as URI or host:port
     148              :     try {
     149            1 :       final parts = endpoint.split(':');
     150            2 :       if (parts.length == 1 && parts[0].isNotEmpty) {
     151              :         // Only host provided, add default port
     152            0 :         return '${parts[0]}:4317';
     153            4 :       } else if (parts.length == 2 && parts[0].isNotEmpty) {
     154              :         // Validate port is a number if specified
     155            2 :         if (parts[1].isEmpty) {
     156            2 :           throw ArgumentError('Invalid port format in endpoint: $endpoint');
     157              :         }
     158            2 :         if (int.tryParse(parts[1]) == null) {
     159            0 :           throw ArgumentError('Invalid port format in endpoint: $endpoint');
     160              :         }
     161              :         // Host and port provided
     162              :         return endpoint;
     163              :       }
     164              : 
     165            1 :       throw ArgumentError(
     166            1 :           'Invalid endpoint format: $endpoint. Expected format: "host:port" or a valid URI');
     167              :     } catch (e) {
     168            1 :       if (e is ArgumentError) rethrow; // Re-throw our own errors
     169              : 
     170              :       // Any other parsing error
     171            0 :       throw ArgumentError(
     172            0 :           'Invalid endpoint format: $endpoint. Expected format: "host:port" or a valid URI');
     173              :     }
     174              :   }
     175              : 
     176            2 :   static Duration _validateTimeout(Duration timeout) {
     177            2 :     if (timeout < const Duration(milliseconds: 1) ||
     178            2 :         timeout > const Duration(minutes: 10)) {
     179            1 :       throw ArgumentError('Timeout must be between 1ms and 10 minutes');
     180              :     }
     181              :     return timeout;
     182              :   }
     183              : 
     184            2 :   static int _validateRetries(int retries) {
     185            2 :     if (retries < 0) {
     186            1 :       throw ArgumentError('maxRetries cannot be negative');
     187              :     }
     188              :     return retries;
     189              :   }
     190              : 
     191            2 :   static Duration _validateDelay(Duration delay, String name) {
     192            2 :     if (delay < const Duration(milliseconds: 1) ||
     193            2 :         delay > const Duration(minutes: 5)) {
     194            2 :       throw ArgumentError('$name must be between 1ms and 5 minutes');
     195              :     }
     196              :     return delay;
     197              :   }
     198              : 
     199            2 :   static void _validateCertificates(
     200              :       String? cert, String? key, String? clientCert) {
     201            2 :     CertificateUtils.validateCertificates(
     202              :       certificate: cert,
     203              :       clientKey: key,
     204              :       clientCertificate: clientCert,
     205              :     );
     206              :   }
     207              : }
        

Generated by: LCOV version 2.0-1