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