From f0c7663bbdde48aec7a5c021a7f7104f592f65d4 Mon Sep 17 00:00:00 2001 From: Kayla Reopelle Date: Thu, 30 Nov 2023 16:44:01 -0800 Subject: [PATCH] feat: Add configuration patch to set metrics exporter by environment variable The environment variable has options for: * console (default) * otlp * in-memory * none Like the Traces exporter, more than one exporter can be configured --- .../sdk/metrics/configuration_patch.rb | 41 +++++++ .../sdk/metrics/configuration_patch_test.rb | 108 ++++++++++++++++++ .../sdk/metrics/meter_provider_test.rb | 6 +- metrics_sdk/test/test_helper.rb | 3 + 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 metrics_sdk/test/opentelemetry/sdk/metrics/configuration_patch_test.rb diff --git a/metrics_sdk/lib/opentelemetry/sdk/metrics/configuration_patch.rb b/metrics_sdk/lib/opentelemetry/sdk/metrics/configuration_patch.rb index ac529e67f4..24c95877cc 100644 --- a/metrics_sdk/lib/opentelemetry/sdk/metrics/configuration_patch.rb +++ b/metrics_sdk/lib/opentelemetry/sdk/metrics/configuration_patch.rb @@ -10,12 +10,53 @@ module Metrics # The ConfiguratorPatch implements a hook to configure the metrics # portion of the SDK. module ConfiguratorPatch + def add_metric_reader(metric_reader) + @metric_readers << metric_reader + end + private + def initialize + super + @metric_readers = [] + end + # The metrics_configuration_hook method is where we define the setup process # for metrics SDK. def metrics_configuration_hook OpenTelemetry.meter_provider = Metrics::MeterProvider.new(resource: @resource) + configure_metric_readers + end + + def configure_metric_readers + readers = @metric_readers.empty? ? wrapped_metric_exporters_from_env.compact : @metric_readers + readers.each { |r| OpenTelemetry.meter_provider.add_metric_reader(r) } + end + + def wrapped_metric_exporters_from_env + exporters = ENV.fetch('OTEL_METRICS_EXPORTER', 'console') + + exporters.split(',').map do |exporter| + case exporter.strip + when 'none' then nil + when 'console' + OpenTelemetry.meter_provider.add_metric_reader(Metrics::Export::ConsoleMetricPullExporter.new) + when 'in-memory' + OpenTelemetry.meter_provider.add_metric_reader(Metrics::Export::InMemoryMetricPullExporter.new) + when 'otlp' + begin + OpenTelemetry.meter_provider.add_metric_reader(OpenTelemetry::Exporter::OTLP::MetricsExporter.new) + rescue NameError + OpenTelemetry.logger.warn 'The otlp metrics exporter cannot be configured - please add ' \ + 'opentelemetry-exporter-otlp-metrics to your Gemfile, metrics will not be exported' + nil + end + else + OpenTelemetry.logger.warn "The #{exporter} exporter is unknown and cannot be configured, " \ + 'metrics will not be exported' + nil + end + end end end end diff --git a/metrics_sdk/test/opentelemetry/sdk/metrics/configuration_patch_test.rb b/metrics_sdk/test/opentelemetry/sdk/metrics/configuration_patch_test.rb new file mode 100644 index 0000000000..aa6a30dd0b --- /dev/null +++ b/metrics_sdk/test/opentelemetry/sdk/metrics/configuration_patch_test.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +# Copyright The OpenTelemetry Authors +# +# SPDX-License-Identifier: Apache-2.0 + +require 'test_helper' + +describe OpenTelemetry::SDK::Metrics::ConfiguratorPatch do + let(:configurator) { OpenTelemetry::SDK::Configurator.new } + let(:default_resource_attributes) do + { + 'telemetry.sdk.name' => 'opentelemetry', + 'telemetry.sdk.language' => 'ruby', + 'telemetry.sdk.version' => OpenTelemetry::SDK::VERSION, + 'process.pid' => Process.pid, + 'process.command' => $PROGRAM_NAME, + 'process.runtime.name' => RUBY_ENGINE, + 'process.runtime.version' => RUBY_VERSION, + 'process.runtime.description' => RUBY_DESCRIPTION, + 'service.name' => 'unknown_service' + } + end + + def setup + # Suppress log warnings about trace exporter absence + ENV['OTEL_TRACES_EXPORTER'] = 'none' + end + + def teardown + # Let the settings go back to their default + ENV['OTEL_TRACES_EXPORTER'] = nil + end + + describe '#configure' do + describe 'meter_provider' do + it 'is an instance of SDK::Metrics::MeterProvider' do + configurator.configure + + _(OpenTelemetry.meter_provider).must_be_instance_of( + OpenTelemetry::SDK::Metrics::MeterProvider + ) + end + end + + describe 'metric readers' do + it 'defaults to the console reader' do + configurator.configure + + assert_equal 1, OpenTelemetry.meter_provider.metric_readers.size + assert_instance_of OpenTelemetry::SDK::Metrics::Export::ConsoleMetricPullExporter, OpenTelemetry.meter_provider.metric_readers[0] + end + + it 'can be set by environment variable' do + OpenTelemetry::TestHelpers.with_env('OTEL_METRICS_EXPORTER' => 'in-memory') do + configurator.configure + end + + assert_equal 1, OpenTelemetry.meter_provider.metric_readers.size + assert_instance_of OpenTelemetry::SDK::Metrics::Export::InMemoryMetricPullExporter, OpenTelemetry.meter_provider.metric_readers[0] + end + + it 'supports "none" as an environment variable' do + OpenTelemetry::TestHelpers.with_test_logger do |log_stream| + OpenTelemetry::TestHelpers.with_env('OTEL_METRICS_EXPORTER' => 'none') do + configurator.configure + end + + assert_empty OpenTelemetry.meter_provider.metric_readers + + refute_match(/The none exporter is unknown and cannot be configured/, log_stream.string) + end + end + + it 'supports multiple exporters passed by environment variable' do + OpenTelemetry::TestHelpers.with_env('OTEL_METRICS_EXPORTER' => 'in-memory,console') do + configurator.configure + end + + assert_equal 2, OpenTelemetry.meter_provider.metric_readers.size + assert_instance_of OpenTelemetry::SDK::Metrics::Export::InMemoryMetricPullExporter, OpenTelemetry.meter_provider.metric_readers[0] + assert_instance_of OpenTelemetry::SDK::Metrics::Export::ConsoleMetricPullExporter, OpenTelemetry.meter_provider.metric_readers[1] + end + + it 'defaults to noop with invalid env var' do + OpenTelemetry::TestHelpers.with_test_logger do |log_stream| + OpenTelemetry::TestHelpers.with_env('OTEL_METRICS_EXPORTER' => 'unladen_swallow') do + configurator.configure + end + + assert_empty OpenTelemetry.meter_provider.metric_readers + assert_match(/The unladen_swallow exporter is unknown and cannot be configured/, log_stream.string) + end + end + + it 'rescues NameErrors when otlp set to env var and the library is not installed' do + OpenTelemetry::TestHelpers.with_test_logger do |log_stream| + OpenTelemetry::TestHelpers.with_env('OTEL_METRICS_EXPORTER' => 'otlp') do + configurator.configure + end + + assert_empty OpenTelemetry.meter_provider.metric_readers + assert_match(/The otlp exporter cannot be configured - please add opentelemetry-exporter-otlp-metrics to your Gemfile, metrics will not be exported/, log_stream.string) + end + end + end + end +end diff --git a/metrics_sdk/test/opentelemetry/sdk/metrics/meter_provider_test.rb b/metrics_sdk/test/opentelemetry/sdk/metrics/meter_provider_test.rb index f781d613b3..2881fd8ae2 100644 --- a/metrics_sdk/test/opentelemetry/sdk/metrics/meter_provider_test.rb +++ b/metrics_sdk/test/opentelemetry/sdk/metrics/meter_provider_test.rb @@ -9,7 +9,11 @@ describe OpenTelemetry::SDK::Metrics::MeterProvider do before do reset_metrics_sdk - OpenTelemetry::SDK.configure + # The MeterProvider tests deal with mock MetricReaders that are manually added + # Run the tests without the configuration patch interfering with the setup + OpenTelemetry::TestHelpers.with_env('OTEL_METRICS_EXPORTER' => 'none') do + OpenTelemetry::SDK.configure + end end describe '#meter' do diff --git a/metrics_sdk/test/test_helper.rb b/metrics_sdk/test/test_helper.rb index 99aa4deee0..11076bde18 100644 --- a/metrics_sdk/test/test_helper.rb +++ b/metrics_sdk/test/test_helper.rb @@ -33,3 +33,6 @@ def with_test_logger ensure OpenTelemetry.logger = original_logger end + +# Suppress warn-level logs about a missing OTLP exporter for traces +ENV['OTEL_TRACES_EXPORTER'] = 'none'