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