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 : }
|