LCOV - code coverage report
Current view: top level - lib/src/metrics/export - metric_config.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 95.6 % 68 65
Test Date: 2026-08-27 23:42:02 Functions: - 0 0

            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              :     show OTelLog;
       6              : import '../../environment/otel_env.dart';
       7              : import '../../otel.dart';
       8              : import '../../resource/resource.dart';
       9              : import '../exemplar_filter.dart';
      10              : import '../meter_provider.dart';
      11              : import '../metric_exporter.dart';
      12              : import '../metric_reader.dart';
      13              : import 'composite_metric_exporter.dart';
      14              : import 'metrics_sdk_config.dart';
      15              : import 'otlp/http/otlp_http_metric_exporter.dart';
      16              : import 'otlp/http/otlp_http_metric_exporter_config.dart';
      17              : import 'otlp/otlp_grpc_metric_exporter.dart';
      18              : import 'otlp/otlp_grpc_metric_exporter_config.dart';
      19              : 
      20              : /// Configuration for metrics exporters and readers.
      21              : class MetricsConfiguration {
      22              :   /// Configures a MeterProvider with given settings.
      23              :   ///
      24              :   /// This configures everything needed for metrics pipeline:
      25              :   /// - An exporter selected per the OTel spec:
      26              :   ///   `OTEL_METRICS_EXPORTER=otlp` (default) → OtlpHttp/Grpc exporter,
      27              :   ///   `=console` → ConsoleMetricExporter, `=none` → no reader is added.
      28              :   /// - A reader (defaults to PeriodicExportingMetricReader if none provided)
      29              :   /// - Sets up resources on the MeterProvider
      30              :   ///
      31              :   /// An explicit [metricExporter] or [metricReader] always wins over the
      32              :   /// env-var selection so programmatic configuration is unsurprising.
      33              :   ///
      34              :   /// When [endpoint] is null (or unset), the exporter default depends on the
      35              :   /// resolved protocol (issue #220): `OTel.defaultGrpcEndpoint` for gRPC,
      36              :   /// `OTel.defaultEndpoint` for the HTTP protocols.
      37          131 :   static MeterProvider configureMeterProvider({
      38              :     String? endpoint,
      39              :     bool? secure,
      40              :     MetricExporter? metricExporter,
      41              :     MetricReader? metricReader,
      42              :     Resource? resource,
      43              :     ExemplarFilter? exemplarFilter,
      44              :     OtlpEnvironmentValues? otlpConfig,
      45              :     List<String>? exporters,
      46              :   }) {
      47            2 :     otlpConfig ??= OTelEnv.getOtlpConfig(signal: 'metrics');
      48            4 :     exporters ??= OTelEnv.getExporters(signal: 'metrics') ?? ['otlp'];
      49              : 
      50          131 :     final meterProvider = OTel.meterProvider();
      51              :     if (resource != null) {
      52          129 :       meterProvider.resource = resource;
      53              :     }
      54              : 
      55          131 :     final metricsSdkConfig = MetricsSdkConfig.fromEnvironment();
      56              : 
      57              :     ExemplarFilter filter;
      58          131 :     switch (metricsSdkConfig.exemplarFilter) {
      59          131 :       case MetricsExemplarFilter.alwaysOn:
      60              :         filter = const AlwaysOnExemplarFilter();
      61          131 :       case MetricsExemplarFilter.alwaysOff:
      62              :         filter = const AlwaysOffExemplarFilter();
      63          131 :       case MetricsExemplarFilter.traceBased:
      64              :         filter = const TraceBasedExemplarFilter();
      65              :     }
      66          131 :     meterProvider.exemplarFilter = exemplarFilter ?? filter;
      67              : 
      68              :     // Honor OTEL_METRICS_EXPORTER, but only when the caller did not pass an
      69              :     // explicit exporter/reader — explicit args are an unambiguous opt-in and
      70              :     // should not be silently dropped by env config.
      71              :     if (metricExporter == null && metricReader == null) {
      72          117 :       if (exporters.contains('none')) {
      73            7 :         if (exporters.length > 1 && OTelLog.isWarn()) {
      74            1 :           OTelLog.warn("OTEL_METRICS_EXPORTER contains 'none' alongside "
      75              :               'other values; installing no reader.');
      76            3 :         } else if (OTelLog.isDebug()) {
      77            1 :           OTelLog.debug(
      78              :               'MetricsConfiguration: OTEL_METRICS_EXPORTER=none, skipping reader');
      79              :         }
      80              :         return meterProvider;
      81              :       }
      82          115 :       final createdExporters = <MetricExporter>[];
      83          230 :       for (final name in exporters) {
      84              :         switch (name) {
      85          115 :           case 'otlp':
      86            1 :           case 'console':
      87          115 :             final created = _createExporter(name, endpoint, secure, otlpConfig,
      88          115 :                 metricsSdkConfig.exemplarFilter);
      89              :             if (created != null) {
      90          115 :               createdExporters.add(created);
      91              :             }
      92            1 :           case 'prometheus':
      93              :             // Recognized spec value, but not auto-wirable yet: the SDK has
      94              :             // no scrape server, and an env-created PrometheusExporter would
      95              :             // be unreachable by the app — a silent no-op. Honest support
      96              :             // arrives with the scrape server (#82). Programmatic use of
      97              :             // PrometheusExporter (app serves prometheusData) works today.
      98            1 :             if (OTelLog.isWarn()) {
      99            1 :               OTelLog.warn("OTEL_METRICS_EXPORTER value 'prometheus' is not "
     100              :                   'supported yet (no scrape server; see issue #82). '
     101              :                   'Construct PrometheusExporter programmatically and serve '
     102              :                   'prometheusData, or route OTLP through the collector.');
     103              :             }
     104            1 :           case 'logging':
     105            1 :             if (OTelLog.isWarn()) {
     106            1 :               OTelLog.warn("OTEL_METRICS_EXPORTER value 'logging' is "
     107              :                   "deprecated in the spec and not supported; use 'console'.");
     108              :             }
     109              :           default:
     110            1 :             if (OTelLog.isWarn()) {
     111            2 :               OTelLog.warn("OTEL_METRICS_EXPORTER value '$name' is not "
     112              :                   'supported; ignoring. Supported: otlp, console, none.');
     113              :             }
     114              :         }
     115              :       }
     116          115 :       if (createdExporters.isEmpty) {
     117            1 :         if (OTelLog.isWarn()) {
     118            1 :           OTelLog.warn('OTEL_METRICS_EXPORTER produced no usable exporter; '
     119              :               'falling back to the default otlp exporter.');
     120              :         }
     121            2 :         createdExporters.add(_createExporter('otlp', endpoint, secure,
     122            1 :             otlpConfig, metricsSdkConfig.exemplarFilter)!);
     123              :       }
     124          230 :       metricExporter = createdExporters.length == 1
     125          115 :           ? createdExporters.single
     126            1 :           : CompositeMetricExporter(createdExporters);
     127              :     }
     128              : 
     129           15 :     metricExporter ??= _createExporter(
     130           15 :         'otlp', endpoint, secure, otlpConfig, metricsSdkConfig.exemplarFilter);
     131              :     if (metricExporter == null) {
     132              :       return meterProvider;
     133              :     }
     134              : 
     135          115 :     metricReader ??= PeriodicExportingMetricReader(
     136              :       metricExporter,
     137          115 :       interval: metricsSdkConfig.exportInterval,
     138          115 :       timeout: metricsSdkConfig.exportTimeout,
     139              :     );
     140              : 
     141          129 :     meterProvider.addMetricReader(metricReader);
     142              :     return meterProvider;
     143              :   }
     144              : 
     145              :   /// Creates a metric exporter for [exporterType] (`otlp` or `console`).
     146              :   /// Returns null for unknown values.
     147          129 :   static MetricExporter? _createExporter(
     148              :     String exporterType,
     149              :     String? endpoint,
     150              :     bool? secure,
     151              :     OtlpEnvironmentValues otlpConfig,
     152              :     MetricsExemplarFilter exemplarFilter,
     153              :   ) {
     154          129 :     if (exporterType == 'console') {
     155            1 :       if (OTelLog.isDebug()) {
     156            0 :         OTelLog.debug('MetricsConfiguration: Creating ConsoleMetricExporter');
     157              :       }
     158            1 :       return ConsoleMetricExporter();
     159              :     }
     160          129 :     if (exporterType != 'otlp') {
     161            0 :       if (OTelLog.isDebug()) {
     162            0 :         OTelLog.debug(
     163              :             'MetricsConfiguration: Unknown OTEL_METRICS_EXPORTER value '
     164              :             '"$exporterType", falling back to otlp');
     165              :       }
     166              :     }
     167              : 
     168              :     final protocol = otlpConfig.protocol ?? 'http/protobuf';
     169              :     // The default endpoint depends on the protocol (issue #220): OTLP/gRPC
     170              :     // uses port 4317, the HTTP protocols use port 4318.
     171              :     final effectiveEndpoint = otlpConfig.endpoint ??
     172              :         endpoint ??
     173          105 :         (protocol == 'grpc' ? OTel.defaultGrpcEndpoint : OTel.defaultEndpoint);
     174              :     // Parity with logs_config: honor OTEL_EXPORTER_OTLP_METRICS_INSECURE
     175              :     // (previously parsed and dropped) and the endpoint scheme per the
     176              :     // OTLP spec, falling back to the resolved global setting.
     177          129 :     final effectiveSecure = OTelEnv.resolveOtlpSecure(
     178              :       envInsecure: otlpConfig.insecure,
     179              :       endpoint: effectiveEndpoint,
     180              :       explicitSecure: secure,
     181              :     );
     182              :     final headers = otlpConfig.headers ?? const {};
     183              :     final timeout = otlpConfig.timeout ?? const Duration(seconds: 10);
     184          129 :     final compression = otlpConfig.compression == 'gzip';
     185              :     final certificate = otlpConfig.certificate;
     186              :     final clientKey = otlpConfig.clientKey;
     187              :     final clientCertificate = otlpConfig.clientCertificate;
     188              : 
     189          129 :     if (protocol == 'grpc') {
     190            4 :       if (OTelLog.isDebug()) {
     191            3 :         OTelLog.debug(
     192            3 :             'MetricsConfiguration: Creating OtlpGrpcMetricExporter for $effectiveEndpoint');
     193              :       }
     194            4 :       return OtlpGrpcMetricExporter(
     195            4 :         OtlpGrpcMetricExporterConfig(
     196              :           endpoint: effectiveEndpoint,
     197              :           insecure: !effectiveSecure,
     198              :           headers: headers,
     199            4 :           timeoutMillis: timeout.inMilliseconds,
     200              :           certificate: certificate,
     201              :           clientKey: clientKey,
     202              :           clientCertificate: clientCertificate,
     203              :           compression: compression,
     204              :           exemplarFilter: exemplarFilter,
     205              :         ),
     206              :       );
     207              :     }
     208              : 
     209          127 :     if (OTelLog.isDebug()) {
     210          119 :       OTelLog.debug(
     211          119 :           'MetricsConfiguration: Creating OtlpHttpMetricExporter for $effectiveEndpoint');
     212              :     }
     213          127 :     return OtlpHttpMetricExporter(
     214          127 :       OtlpHttpMetricExporterConfig(
     215              :         endpoint: effectiveEndpoint,
     216              :         headers: headers,
     217              :         timeout: timeout,
     218              :         compression: compression,
     219              :         certificate: certificate,
     220              :         clientKey: clientKey,
     221              :         clientCertificate: clientCertificate,
     222              :         exemplarFilter: exemplarFilter,
     223              :       ),
     224              :     );
     225              :   }
     226              : }
        

Generated by: LCOV version 2.0-1