LCOV - code coverage report
Current view: top level - src/resource - resource_detector.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 56.9 % 65 37
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 'dart:io' as io;
       5              : 
       6              : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
       7              : 
       8              : import '../environment/environment_service.dart';
       9              : import 'resource.dart';
      10              : import 'web_detector.dart';
      11              : 
      12              : /// Interface for resource detectors that automatically discover resource information.
      13              : ///
      14              : /// Resource detectors are used to automatically populate resource attributes
      15              : /// based on the environment (operating system, platform, etc.).
      16              : ///
      17              : /// More information:
      18              : /// https://opentelemetry.io/docs/specs/otel/resource/sdk/#detecting-resource-information-from-the-environment
      19              : abstract class ResourceDetector {
      20              :   /// Detects resource information from the environment.
      21              :   ///
      22              :   /// @return A resource containing the detected attributes
      23              :   Future<Resource> detect();
      24              : }
      25              : 
      26              : /// Detects process-related resource information.
      27              : ///
      28              : /// This detector populates resource attributes with information about the
      29              : /// current process, such as executable name, command line, and runtime information.
      30              : ///
      31              : /// Semantic conventions:
      32              : /// https://opentelemetry.io/docs/specs/semconv/resource/process/
      33              : class ProcessResourceDetector implements ResourceDetector {
      34           45 :   @override
      35              :   Future<Resource> detect() async {
      36              :     if (OTelFactory.otelFactory == null) {
      37              :       throw 'OTel initialize must be called first.';
      38              :     }
      39          135 :     return ResourceCreate.create(OTelFactory.otelFactory!.attributesFromMap({
      40           45 :       'process.executable.name': io.Platform.executable,
      41           90 :       'process.command_line': io.Platform.executableArguments.join(' '),
      42              :       'process.runtime.name': 'dart',
      43           45 :       'process.runtime.version': io.Platform.version,
      44           90 :       'process.num_threads': io.Platform.numberOfProcessors.toString(),
      45              :     }));
      46              :   }
      47              : }
      48              : 
      49              : /// Detects host-related resource information.
      50              : ///
      51              : /// This detector populates resource attributes with information about the
      52              : /// host machine, such as hostname, architecture, and operating system details.
      53              : ///
      54              : /// Semantic conventions:
      55              : /// https://opentelemetry.io/docs/specs/semconv/resource/host/
      56              : class HostResourceDetector implements ResourceDetector {
      57           45 :   @override
      58              :   Future<Resource> detect() async {
      59              :     if (OTelFactory.otelFactory == null) {
      60              :       throw 'OTel initialize must be called first.';
      61              :     }
      62           45 :     final Map<String, Object> attributes = {
      63           45 :       'host.name': io.Platform.localHostname,
      64           45 :       'host.arch': io.Platform.localHostname,
      65           45 :       'host.processors': io.Platform.numberOfProcessors,
      66           45 :       'host.os.name': io.Platform.operatingSystem,
      67           45 :       'host.locale': io.Platform.localeName,
      68              :     };
      69              : 
      70              :     // Add OS-specific information
      71           45 :     if (io.Platform.isLinux) {
      72           45 :       attributes['os.type'] = 'linux';
      73            0 :     } else if (io.Platform.isWindows) {
      74            0 :       attributes['os.type'] = 'windows';
      75            0 :     } else if (io.Platform.isMacOS) {
      76            0 :       attributes['os.type'] = 'macos';
      77            0 :     } else if (io.Platform.isAndroid) {
      78            0 :       attributes['os.type'] = 'android';
      79            0 :     } else if (io.Platform.isIOS) {
      80            0 :       attributes['os.type'] = 'ios';
      81              :     }
      82              : 
      83           90 :     attributes['os.version'] = io.Platform.operatingSystemVersion;
      84              : 
      85           45 :     return ResourceCreate.create(
      86           45 :         OTelFactory.otelFactory!.attributesFromMap(attributes));
      87              :   }
      88              : }
      89              : 
      90              : /// Detects resource information from environment variables.
      91              : ///
      92              : /// This detector looks for the OTEL_RESOURCE_ATTRIBUTES environment variable
      93              : /// and parses its contents into resource attributes.
      94              : ///
      95              : /// More information:
      96              : /// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration
      97              : class EnvVarResourceDetector implements ResourceDetector {
      98              :   final EnvironmentService _environmentService;
      99              : 
     100              :   /// Creates a new EnvVarResourceDetector with the specified environment service.
     101              :   ///
     102              :   /// If no environment service is provided, the singleton instance will be used.
     103              :   ///
     104              :   /// @param environmentService Optional service for accessing environment variables
     105           45 :   EnvVarResourceDetector([EnvironmentService? environmentService])
     106           45 :       : _environmentService = environmentService ?? EnvironmentService.instance;
     107              : 
     108           45 :   @override
     109              :   Future<Resource> detect() async {
     110              :     if (OTelFactory.otelFactory == null) {
     111              :       throw 'OTel initialize must be called first.';
     112              :     }
     113              : 
     114              :     //TODO - OTEL_RESOURCE_ATTRIBUTES?
     115              :     final resourceAttrs =
     116           90 :         _environmentService.getValue('OTEL_RESOURCE_ATTRIBUTES');
     117            0 :     if (resourceAttrs == null || resourceAttrs.isEmpty) {
     118           45 :       return Resource.empty;
     119              :     }
     120              : 
     121            0 :     final attributes = _parseResourceAttributes(resourceAttrs);
     122            0 :     return ResourceCreate.create(attributes);
     123              :   }
     124              : 
     125              :   /// Parses the OTEL_RESOURCE_ATTRIBUTES environment variable.
     126              :   ///
     127              :   /// The format is a comma-separated list of key=value pairs.
     128              :   /// For example: key1=value1,key2=value2
     129              :   ///
     130              :   /// Commas can be escaped with a backslash, and the values can be
     131              :   /// percent-encoded.
     132              :   ///
     133              :   /// @param envValue The value of the OTEL_RESOURCE_ATTRIBUTES environment variable
     134              :   /// @return Attributes parsed from the environment variable
     135            0 :   Attributes _parseResourceAttributes(String envValue) {
     136            0 :     final Map<String, Object> attributes = {};
     137              : 
     138              :     // Split on commas, but handle escaped commas
     139            0 :     final parts = envValue.split(RegExp(r'(?<!\\),'));
     140              : 
     141            0 :     for (var part in parts) {
     142              :       // Remove any leading/trailing whitespace
     143            0 :       part = part.trim();
     144              : 
     145              :       // Split on first equals sign
     146            0 :       final keyValue = part.split('=');
     147            0 :       if (keyValue.length != 2) continue;
     148              : 
     149            0 :       final key = keyValue[0].trim();
     150            0 :       var value = keyValue[1].trim();
     151              : 
     152              :       // Handle percent-encoded characters
     153            0 :       value = Uri.decodeComponent(value);
     154              : 
     155              :       // Remove escape characters
     156            0 :       value = value.replaceAll(r'\,', ',');
     157              : 
     158            0 :       attributes[key] = value;
     159              :     }
     160              : 
     161            0 :     return OTelFactory.otelFactory!.attributesFromMap(attributes);
     162              :   }
     163              : }
     164              : 
     165              : /// Composite detector that combines multiple resource detectors.
     166              : ///
     167              : /// This detector runs multiple detectors and merges their results.
     168              : /// This is useful for combining resource information from different sources.
     169              : ///
     170              : /// More information:
     171              : /// https://opentelemetry.io/docs/specs/otel/resource/sdk/#resource-creation
     172              : class CompositeResourceDetector implements ResourceDetector {
     173              :   final List<ResourceDetector> _detectors;
     174              : 
     175              :   /// Creates a new CompositeResourceDetector with the specified detectors.
     176              :   ///
     177              :   /// @param detectors The list of detectors to run
     178           45 :   CompositeResourceDetector(this._detectors);
     179              : 
     180           45 :   @override
     181              :   Future<Resource> detect() async {
     182              :     if (OTelFactory.otelFactory == null) {
     183              :       throw 'OTel initialize must be called first.';
     184              :     }
     185           45 :     Resource result = Resource.empty;
     186              : 
     187           90 :     for (final detector in _detectors) {
     188              :       try {
     189           45 :         final resource = await detector.detect();
     190           45 :         result = result.merge(resource);
     191              :       } catch (e) {
     192              :         // Log error but continue with other detectors
     193            3 :         if (OTelLog.isError()) OTelLog.error('Error in resource detector: $e');
     194              :       }
     195              :     }
     196              : 
     197              :     return result;
     198              :   }
     199              : }
     200              : 
     201              : /// Factory for creating platform-appropriate resource detectors.
     202              : ///
     203              : /// This factory creates a composite detector with the appropriate
     204              : /// detectors for the current platform (web or native).
     205              : class PlatformResourceDetector {
     206              :   /// Creates a composite detector with platform-appropriate detectors.
     207              :   ///
     208              :   /// @return A ResourceDetector that combines all appropriate detectors
     209           45 :   static ResourceDetector create() {
     210           45 :     final detectors = <ResourceDetector>[
     211           45 :       EnvVarResourceDetector(),
     212              :     ];
     213              : 
     214              :     // For non-web platforms (native)
     215              :     if (!const bool.fromEnvironment('dart.library.js_interop')) {
     216              :       try {
     217           90 :         detectors.addAll([
     218           45 :           ProcessResourceDetector(),
     219           45 :           HostResourceDetector(),
     220              :         ]);
     221              :       } catch (e) {
     222            0 :         if (OTelLog.isError()) {
     223            0 :           OTelLog.error('Error adding native detectors: $e');
     224              :         }
     225              :       }
     226              :     }
     227              :     // For web platforms
     228              :     else {
     229              :       try {
     230            0 :         detectors.add(WebResourceDetector());
     231              :       } catch (e) {
     232            0 :         if (OTelLog.isError()) OTelLog.error('Error adding web detector: $e');
     233              :       }
     234              :     }
     235              : 
     236           45 :     return CompositeResourceDetector(detectors);
     237              :   }
     238              : }
        

Generated by: LCOV version 2.0-1