Line data Source code
1 : // Copyright The OpenTelemetry Authors
2 : // SPDX-License-Identifier: Apache-2.0
3 :
4 : import 'dart:async';
5 :
6 : import 'package:meta/meta.dart';
7 :
8 : import '../../dartastic_opentelemetry.dart';
9 : import 'meter.dart';
10 :
11 : part 'meter_provider_create.dart';
12 :
13 : /// SDK implementation of the APIMeterProvider interface.
14 : ///
15 : /// The MeterProvider is the entry point to the metrics API. It is responsible
16 : /// for creating and managing Meters, as well as configuring the metric pipeline
17 : /// via MetricReaders and Views.
18 : ///
19 : /// This implementation delegates some functionality to the API MeterProvider
20 : /// implementation while adding SDK-specific behaviors.
21 : ///
22 : /// More information:
23 : /// https://opentelemetry.io/docs/specs/otel/metrics/sdk/
24 : class MeterProvider implements APIMeterProvider {
25 : /// The underlying API MeterProvider implementation.
26 : final APIMeterProvider delegate;
27 :
28 : /// The resource associated with this MeterProvider.
29 : Resource? resource;
30 :
31 : /// List of metric readers associated with this MeterProvider.
32 : final List<MetricReader> _metricReaders = [];
33 :
34 : /// List of views for configuring metric collection.
35 : final List<View> _views = [];
36 :
37 : /// Private constructor for creating MeterProvider instances.
38 : ///
39 : /// @param delegate The API MeterProvider implementation to delegate to
40 : /// @param resource Optional Resource describing the entity producing telemetry
41 121 : MeterProvider._({required this.delegate, this.resource}) {
42 121 : if (OTelLog.isDebug()) {
43 351 : OTelLog.debug('MeterProvider: Created with resource: $resource');
44 : }
45 : }
46 :
47 2 : @override
48 4 : String get endpoint => delegate.endpoint;
49 :
50 2 : @override
51 4 : set endpoint(String value) => delegate.endpoint = value;
52 :
53 2 : @override
54 4 : String get serviceName => delegate.serviceName;
55 :
56 2 : @override
57 4 : set serviceName(String value) => delegate.serviceName = value;
58 :
59 2 : @override
60 4 : String? get serviceVersion => delegate.serviceVersion;
61 :
62 2 : @override
63 4 : set serviceVersion(String? value) => delegate.serviceVersion = value;
64 :
65 26 : @override
66 : bool get enabled {
67 26 : return _enabledOverride ?? true;
68 : }
69 :
70 : // Track explicit enablement settings
71 : bool? _enabledOverride;
72 :
73 10 : @override
74 : set enabled(bool value) {
75 10 : _enabledOverride = value;
76 20 : delegate.enabled = value;
77 : }
78 :
79 120 : @override
80 240 : bool get isShutdown => delegate.isShutdown;
81 :
82 119 : @override
83 238 : set isShutdown(bool value) => delegate.isShutdown = value;
84 :
85 28 : @override
86 : APIMeter getMeter({
87 : required String name,
88 : String? version,
89 : String? schemaUrl,
90 : Attributes? attributes,
91 : }) {
92 : // Check if provider is shutdown
93 28 : if (isShutdown) {
94 : // Return a no-op meter instead of throwing
95 1 : if (OTelLog.isDebug()) {
96 1 : OTelLog.debug(
97 1 : 'MeterProvider: Attempting to get meter "$name" after shutdown. Returning a no-op meter.',
98 : );
99 : }
100 1 : return NoopMeter(name: name, version: version, schemaUrl: schemaUrl);
101 : }
102 :
103 : // Create a unique key for this meter
104 28 : final meterKey = '$name:${version ?? ''}:${schemaUrl ?? ''}';
105 :
106 : // Return an existing meter if we already have one with this configuration
107 56 : if (_meters.containsKey(meterKey)) {
108 6 : return _meters[meterKey]!;
109 : }
110 :
111 : // Call the API implementation first
112 56 : final apiMeter = delegate.getMeter(
113 : name: name,
114 : version: version,
115 : schemaUrl: schemaUrl,
116 : attributes: attributes,
117 : );
118 :
119 : // Wrap it with our SDK implementation
120 28 : final meter = MeterCreate.create(delegate: apiMeter, provider: this);
121 :
122 : // Store the meter in the registry
123 56 : _meters[meterKey] = meter;
124 :
125 : // Initialize the instruments set for this meter
126 56 : _instruments[meterKey] = {};
127 :
128 28 : if (OTelLog.isLogMetrics()) {
129 28 : OTelLog.logMetric(
130 28 : 'MeterProvider: Created meter "$name" (version: $version)',
131 : );
132 : }
133 :
134 : return meter;
135 : }
136 :
137 : /// Adds a MetricReader to this MeterProvider.
138 : ///
139 : /// MetricReaders are responsible for collecting and exporting metrics.
140 : /// They can be configured to collect metrics at different intervals and
141 : /// export them to different backends.
142 : ///
143 : /// @param reader The MetricReader to add
144 120 : void addMetricReader(MetricReader reader) {
145 240 : if (!_metricReaders.contains(reader)) {
146 240 : _metricReaders.add(reader);
147 120 : reader.registerMeterProvider(this);
148 : }
149 : }
150 :
151 : /// Adds a View to this MeterProvider.
152 : ///
153 : /// Views allow for customizing how metrics are collected and aggregated.
154 : /// They can be used to filter, transform, and aggregate metrics before
155 : /// they are exported.
156 : ///
157 : /// @param view The View to add
158 2 : void addView(View view) {
159 4 : _views.add(view);
160 : }
161 :
162 : /// Gets all views configured for this MeterProvider.
163 : ///
164 : /// @return An unmodifiable list of all views
165 6 : List<View> get views => List.unmodifiable(_views);
166 :
167 : /// Gets all metric readers associated with this MeterProvider.
168 : ///
169 : /// @return An unmodifiable list of all metric readers
170 9 : List<MetricReader> get metricReaders => List.unmodifiable(_metricReaders);
171 :
172 : /// Registry of all meters created by this provider
173 : final Map<String, Meter> _meters = {};
174 :
175 : /// Registry of active instruments across all meters
176 : final Map<String, Set<SDKInstrument>> _instruments = {};
177 :
178 : /// Registers an instrument with this provider.
179 : ///
180 : /// This allows the provider to track all active instruments for metrics collection.
181 : ///
182 : /// @param instrumentName The name of the instrument
183 : /// @param instrument The instrument to register
184 26 : void registerInstrument(String instrumentName, SDKInstrument instrument) {
185 52 : final meterKey = instrument.meter.name;
186 52 : if (!_instruments.containsKey(meterKey)) {
187 52 : _instruments[meterKey] = {};
188 : }
189 :
190 78 : _instruments[meterKey]!.add(instrument);
191 :
192 26 : if (OTelLog.isLogMetrics()) {
193 26 : OTelLog.logMetric(
194 104 : 'MeterProvider: Registered instrument "${instrument.name}" for meter "${instrument.meter.name}"',
195 : );
196 : }
197 : }
198 :
199 : /// Collects all metrics from all instruments across all meters.
200 : ///
201 : /// This is called by metric readers to gather the current metrics.
202 : ///
203 : /// @return A list of all collected metrics
204 116 : Future<List<Metric>> collectAllMetrics() async {
205 116 : if (isShutdown) {
206 106 : return [];
207 : }
208 :
209 14 : final allMetrics = <Metric>[];
210 :
211 : // Collect from each meter's instruments
212 39 : for (final entry in _instruments.entries) {
213 11 : final meterName = entry.key;
214 11 : final instruments = entry.value;
215 :
216 11 : if (OTelLog.isLogMetrics()) {
217 11 : OTelLog.logMetric(
218 22 : 'MeterProvider: Collecting metrics from ${instruments.length} instruments in meter "$meterName"',
219 : );
220 : }
221 :
222 : // Collect metrics from each instrument
223 22 : for (final instrument in instruments) {
224 : try {
225 11 : final metrics = instrument.collectMetrics();
226 11 : if (metrics.isNotEmpty) {
227 11 : allMetrics.addAll(metrics);
228 :
229 11 : if (OTelLog.isLogMetrics()) {
230 11 : OTelLog.logMetric(
231 33 : 'MeterProvider: Collected ${metrics.length} metrics from instrument "${instrument.name}"',
232 : );
233 : }
234 : }
235 : } catch (e) {
236 0 : if (OTelLog.isLogMetrics()) {
237 0 : OTelLog.logMetric(
238 0 : 'MeterProvider: Error collecting metrics from instrument "${instrument.name}": $e',
239 : );
240 : }
241 : }
242 : }
243 : }
244 :
245 14 : if (OTelLog.isLogMetrics()) {
246 14 : OTelLog.logMetric(
247 28 : 'MeterProvider: Collected ${allMetrics.length} total metrics',
248 : );
249 : }
250 :
251 : return allMetrics;
252 : }
253 :
254 : /// Force flushes metrics through all associated MetricReaders.
255 : ///
256 : /// This method forces an immediate collection and export of metrics
257 : /// through all registered metric readers.
258 : ///
259 : /// @return true if all flushes were successful, false otherwise
260 3 : @override
261 : Future<bool> forceFlush() async {
262 3 : if (isShutdown) {
263 2 : if (OTelLog.isLogExport()) {
264 2 : OTelLog.logExport('MeterProvider: Cannot flush after shutdown');
265 : }
266 : return false;
267 : }
268 :
269 3 : if (OTelLog.isLogExport()) {
270 3 : OTelLog.logExport(
271 9 : 'MeterProvider: Force flushing metrics through ${_metricReaders.length} readers',
272 : );
273 : }
274 :
275 : var success = true;
276 6 : for (final reader in _metricReaders) {
277 3 : final result = await reader.forceFlush();
278 : success = success && result;
279 : }
280 : return success;
281 : }
282 :
283 : /// Shuts down this MeterProvider and all associated resources.
284 : ///
285 : /// This method shuts down all metric readers and prevents the creation
286 : /// of new meters. Any subsequent calls to getMeter() will return a no-op
287 : /// meter.
288 : ///
289 : /// @return true if shutdown was successful, false otherwise
290 119 : @override
291 : Future<bool> shutdown() async {
292 119 : if (isShutdown) {
293 : return true; // Already shut down
294 : }
295 :
296 : // Mark as shut down immediately to prevent new interactions
297 119 : isShutdown = true;
298 :
299 : var success = true;
300 :
301 : // Shutdown all metric readers
302 237 : for (final reader in _metricReaders) {
303 118 : final result = await reader.shutdown();
304 : success = success && result;
305 : }
306 :
307 : // Clear collections
308 238 : _metricReaders.clear();
309 238 : _views.clear();
310 :
311 : // Finally call the underlying API implementation
312 238 : await delegate.shutdown();
313 :
314 : return success;
315 : }
316 : }
|