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 :
6 : import '../environment/environment_service.dart';
7 : import 'native_detectors.dart';
8 : import 'resource.dart';
9 : import 'web_detector.dart';
10 :
11 : // Re-export the platform-conditional native detectors so existing
12 : // imports of `package:dartastic_opentelemetry/src/resource/resource_detector.dart`
13 : // continue to find `ProcessResourceDetector` and `HostResourceDetector`.
14 : export 'native_detectors.dart'
15 : show HostResourceDetector, ProcessResourceDetector;
16 :
17 : /// Interface for resource detectors that automatically discover resource information.
18 : ///
19 : /// Resource detectors are used to automatically populate resource attributes
20 : /// based on the environment (operating system, platform, etc.).
21 : ///
22 : /// More information:
23 : /// https://opentelemetry.io/docs/specs/otel/resource/sdk/#detecting-resource-information-from-the-environment
24 : abstract class ResourceDetector {
25 : /// Detects resource information from the environment.
26 : ///
27 : /// @return A resource containing the detected attributes
28 : Future<Resource> detect();
29 : }
30 :
31 : /// Detects resource information from environment variables.
32 : ///
33 : /// This detector looks for the OTEL_RESOURCE_ATTRIBUTES environment variable
34 : /// and parses its contents into resource attributes.
35 : ///
36 : /// More information:
37 : /// https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/#general-sdk-configuration
38 : class EnvVarResourceDetector implements ResourceDetector {
39 : final EnvironmentService _environmentService;
40 :
41 : /// Creates a new EnvVarResourceDetector with the specified environment service.
42 : ///
43 : /// If no environment service is provided, the singleton instance will be used.
44 : ///
45 : /// @param environmentService Optional service for accessing environment variables
46 53 : EnvVarResourceDetector([EnvironmentService? environmentService])
47 52 : : _environmentService = environmentService ?? EnvironmentService.instance;
48 :
49 53 : @override
50 : Future<Resource> detect() async {
51 : if (OTelFactory.otelFactory == null) {
52 1 : throw StateError('OTel initialize must be called first.');
53 : }
54 :
55 : //TODO - OTEL_RESOURCE_ATTRIBUTES?
56 106 : final resourceAttrs = _environmentService.getValue(
57 : 'OTEL_RESOURCE_ATTRIBUTES',
58 : );
59 1 : if (resourceAttrs == null || resourceAttrs.isEmpty) {
60 53 : return Resource.empty;
61 : }
62 :
63 1 : final attributes = _parseResourceAttributes(resourceAttrs);
64 1 : return ResourceCreate.create(attributes);
65 : }
66 :
67 : /// Parses the OTEL_RESOURCE_ATTRIBUTES environment variable.
68 : ///
69 : /// The format is a comma-separated list of key=value pairs.
70 : /// For example: key1=value1,key2=value2
71 : ///
72 : /// Commas can be escaped with a backslash, and the values can be
73 : /// percent-encoded.
74 : ///
75 : /// @param envValue The value of the OTEL_RESOURCE_ATTRIBUTES environment variable
76 : /// @return Attributes parsed from the environment variable
77 1 : Attributes _parseResourceAttributes(String envValue) {
78 1 : final attributes = <String, Object>{};
79 :
80 : // Split on commas, but handle escaped commas
81 2 : final parts = envValue.split(RegExp(r'(?<!\\),'));
82 :
83 2 : for (var part in parts) {
84 : // Remove any leading/trailing whitespace
85 1 : part = part.trim();
86 :
87 : // Split on first equals sign
88 1 : final keyValue = part.split('=');
89 2 : if (keyValue.length != 2) continue;
90 :
91 2 : final key = keyValue[0].trim();
92 2 : var value = keyValue[1].trim();
93 :
94 : // Handle percent-encoded characters
95 1 : value = Uri.decodeComponent(value);
96 :
97 : // Remove escape characters
98 1 : value = value.replaceAll(r'\,', ',');
99 :
100 1 : attributes[key] = value;
101 : }
102 :
103 1 : return OTelFactory.otelFactory!.attributesFromMap(attributes);
104 : }
105 : }
106 :
107 : /// Composite detector that combines multiple resource detectors.
108 : ///
109 : /// This detector runs multiple detectors and merges their results.
110 : /// This is useful for combining resource information from different sources.
111 : ///
112 : /// More information:
113 : /// https://opentelemetry.io/docs/specs/otel/resource/sdk/#resource-creation
114 : class CompositeResourceDetector implements ResourceDetector {
115 : final List<ResourceDetector> _detectors;
116 :
117 : /// Creates a new CompositeResourceDetector with the specified detectors.
118 : ///
119 : /// @param detectors The list of detectors to run
120 53 : CompositeResourceDetector(this._detectors);
121 :
122 53 : @override
123 : Future<Resource> detect() async {
124 : if (OTelFactory.otelFactory == null) {
125 1 : throw StateError('OTel initialize must be called first.');
126 : }
127 53 : var result = Resource.empty;
128 :
129 106 : for (final detector in _detectors) {
130 : try {
131 53 : final resource = await detector.detect();
132 53 : result = result.merge(resource);
133 : } catch (e) {
134 : // Log error but continue with other detectors
135 9 : if (OTelLog.isError()) OTelLog.error('Error in resource detector: $e');
136 : }
137 : }
138 :
139 : return result;
140 : }
141 : }
142 :
143 : /// Factory for creating platform-appropriate resource detectors.
144 : ///
145 : /// This factory creates a composite detector with the appropriate
146 : /// detectors for the current platform (web or native).
147 : class PlatformResourceDetector {
148 : /// Creates a composite detector with platform-appropriate detectors.
149 : ///
150 : /// @return A ResourceDetector that combines all appropriate detectors
151 52 : static ResourceDetector create() {
152 104 : final detectors = <ResourceDetector>[EnvVarResourceDetector()];
153 :
154 : // For non-web platforms (native)
155 : if (!const bool.fromEnvironment('dart.library.js_interop')) {
156 : try {
157 208 : detectors.addAll([ProcessResourceDetector(), HostResourceDetector()]);
158 : } catch (e) {
159 0 : if (OTelLog.isError()) {
160 0 : OTelLog.error('Error adding native detectors: $e');
161 : }
162 : }
163 : }
164 : // For web platforms
165 : else {
166 : try {
167 0 : detectors.add(WebResourceDetector());
168 : } catch (e) {
169 0 : if (OTelLog.isError()) OTelLog.error('Error adding web detector: $e');
170 : }
171 : }
172 :
173 52 : return CompositeResourceDetector(detectors);
174 : }
175 : }
|