LCOV - code coverage report
Current view: top level - src/metrics - meter_provider.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 96.9 % 96 93
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: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              : }
        

Generated by: LCOV version 2.0-1