From f48756d532f596bac4d5bd267be79928a0e080a1 Mon Sep 17 00:00:00 2001 From: xuan-cao-swi Date: Fri, 22 Nov 2024 11:01:43 -0500 Subject: [PATCH 1/5] NH-91603: add otlp-metrics --- lib/solarwinds_apm/constants.rb | 6 ++++ lib/solarwinds_apm/oboe_init_options.rb | 5 +-- lib/solarwinds_apm/otel_config.rb | 46 +++++++++++++++++++++++++ solarwinds_apm.gemspec | 2 ++ 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/lib/solarwinds_apm/constants.rb b/lib/solarwinds_apm/constants.rb index c9ad2b7..d6bfdc1 100644 --- a/lib/solarwinds_apm/constants.rb +++ b/lib/solarwinds_apm/constants.rb @@ -32,5 +32,11 @@ module Constants INTL_SWO_OTEL_STATUS = 'otel.status_code' INTL_SWO_OTEL_STATUS_DESCRIPTION = 'otel.status_description' INTERNAL_TRIGGERED_TRACE = 'TriggeredTrace' + + APPOPTICS_ENDPOINT = ['collector.appoptics.com', 'collector-stg.appoptics.com', 'collector.appoptics.com:443', 'collector-stg.appoptics.com:443'] + + SW_OTEL_ENDPOINT = 'https://otel.collector.na-01.solarwinds.com:443' + SW_OTEL_ENDPOINT_STG = 'https://otel.collector.na-01.st-ssp.solarwinds.com:443' + SW_APM_ENDPOINT = 'apm.collector.cloud.solarwinds.com' end end diff --git a/lib/solarwinds_apm/oboe_init_options.rb b/lib/solarwinds_apm/oboe_init_options.rb index 47b7fc8..77a13d0 100644 --- a/lib/solarwinds_apm/oboe_init_options.rb +++ b/lib/solarwinds_apm/oboe_init_options.rb @@ -170,10 +170,7 @@ def determine_the_metric_model end def appoptics_collector? - allowed_uri = ['collector.appoptics.com', 'collector-stg.appoptics.com', - 'collector.appoptics.com:443', 'collector-stg.appoptics.com:443'] - - (allowed_uri.include? ENV.fetch('SW_APM_COLLECTOR', nil)) + (SolarWindsAPM::Constants::APPOPTICS_ENDPOINT.include? ENV.fetch('SW_APM_COLLECTOR', nil)) end def java_collector?(uri) diff --git a/lib/solarwinds_apm/otel_config.rb b/lib/solarwinds_apm/otel_config.rb index 511b557..f67aa44 100644 --- a/lib/solarwinds_apm/otel_config.rb +++ b/lib/solarwinds_apm/otel_config.rb @@ -91,6 +91,50 @@ def self.validate_propagator(propagators) disable_agent(reason: 'Missing tracecontext propagator.') unless ([::OpenTelemetry::Trace::Propagation::TraceContext::TextMapPropagator, ::OpenTelemetry::Baggage::Propagation::TextMapPropagator] - propagators.map(&:class)).empty? end + def self.setup_otlp_metrics + + # determine the correct endpoint + otlp_metrics_endpoint = ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'] || ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] || '' + + if otlp_metrics_endpoint.empty? + if ENV['SW_APM_COLLECTOR'].nil? + otlp_metrics_endpoint = SolarWindsAPM::Constants::SW_OTEL_ENDPOINT + else + if SolarWindsAPM::Constants::APPOPTICS_ENDPOINT.include?(ENV['SW_APM_COLLECTOR']) + SolarWindsAPM.logger.warn { 'Endpoint is appoptics. Appoptics does not support otlp metrics export. No custom metrics will be exported.' } + else + otlp_metrics_endpoint = ENV['SW_APM_COLLECTOR'] == SolarWindsAPM::Constants::SW_APM_ENDPOINT ? + SolarWindsAPM::Constants::SW_OTEL_ENDPOINT : SolarWindsAPM::Constants::SW_OTEL_ENDPOINT_STG + end + end + end + + ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'] = otlp_metrics_endpoint + + token, service_name = ENV['SW_APM_SERVICE_KEY'].split(':') + bearer_token = "authorization=Bearer #{token}" + + otlp_metrics_header = ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS'] || ENV['OTEL_EXPORTER_OTLP_HEADERS'] || '' + metrics_service_name = service_name || ENV['OTEL_SERVICE_NAME'] || '' + + ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS'] = bearer_token if otlp_metrics_header.empty? + + # mTLS? + SolarWindsAPM.logger.warn do { + "Final endpoint: #{otlp_metrics_endpoint}; final masked bearer_token: #{mask_token(token)}" + } + + # resource attributes for otlp metrics + ENV['OTEL_RESOURCE_ATTRIBUTES'] = "sw.data.module=apm,service.name=#{ENV['OTEL_SERVICE_NAME']}," + ENV['OTEL_RESOURCE_ATTRIBUTES'] + end + + def self.mask_token(token) + first_two = token[0, 23] + last_two = token[-2, 2] + masked = '*' * (token.length - 25) + "#{first_two}#{masked}#{last_two}" + end + def self.initialize unless defined?(::OpenTelemetry::SDK::Configurator) disable_agent(reason: 'missing OpenTelemetry::SDK::Configurator; opentelemetry seems not loaded.') @@ -114,6 +158,8 @@ def self.initialize # for dbo, traceparent injection as comments require_relative 'patch/tag_sql_patch' if SolarWindsAPM::Config[:tag_sql] + setup_otlp_metrics if ENV['SW_APM_EXPORT_METRICS_ENABLED'].to_s == 'true' + ::OpenTelemetry::SDK.configure { |c| c.use_all(@@config_map) } validate_propagator(::OpenTelemetry.propagation.instance_variable_get(:@propagators)) diff --git a/solarwinds_apm.gemspec b/solarwinds_apm.gemspec index ab79443..5374965 100644 --- a/solarwinds_apm.gemspec +++ b/solarwinds_apm.gemspec @@ -42,6 +42,8 @@ Gem::Specification.new do |s| s.add_dependency('opentelemetry-instrumentation-all', '>= 0.31.0') s.add_dependency('opentelemetry-sdk', '>= 1.2.0') + s.add_dependency('opentelemetry-metrics-sdk', '>= 0.1.0') + s.add_dependency('opentelemetry-exporter-otlp-metrics', '>= 0.1.0') s.required_ruby_version = '>= 2.7.0' s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } From e2fbae07e0b029ad05003134c462f6b714c09752 Mon Sep 17 00:00:00 2001 From: xuan-cao-swi Date: Mon, 25 Nov 2024 10:34:24 -0500 Subject: [PATCH 2/5] NH-91603: update --- lib/solarwinds_apm.rb | 2 +- lib/solarwinds_apm/constants.rb | 6 +- lib/solarwinds_apm/opentelemetry.rb | 1 + lib/solarwinds_apm/otel_config.rb | 60 ++++++++++--------- solarwinds_apm.gemspec | 4 +- test/api/set_transaction_name_test.rb | 1 - test/initest_helper.rb | 1 + test/minitest_helper.rb | 1 + test/opentelemetry/otlp_processor_test.rb | 1 - .../opentelemetry/solarwinds_exporter_test.rb | 1 - .../solarwinds_processor_test.rb | 1 - .../solarwinds_propagator_test.rb | 1 - test/opentelemetry/solarwinds_sampler_test.rb | 1 - test/patch/sw_mysql2_patch_integrate_test.rb | 1 - 14 files changed, 40 insertions(+), 42 deletions(-) diff --git a/lib/solarwinds_apm.rb b/lib/solarwinds_apm.rb index 393c1a4..c1a8dec 100644 --- a/lib/solarwinds_apm.rb +++ b/lib/solarwinds_apm.rb @@ -20,6 +20,7 @@ begin if RUBY_PLATFORM.include?('linux') + require 'solarwinds_apm/constants' require 'solarwinds_apm/config' require 'solarwinds_apm/oboe_init_options' # setup oboe reporter options if !SolarWindsAPM::OboeInitOptions.instance.service_key_ok? && !SolarWindsAPM::OboeInitOptions.instance.lambda_env @@ -56,7 +57,6 @@ SolarWindsAPM::Reporter.start # start the reporter, any issue will be logged if SolarWindsAPM.loaded - require 'solarwinds_apm/constants' require 'solarwinds_apm/api' require 'solarwinds_apm/support' require 'solarwinds_apm/opentelemetry' diff --git a/lib/solarwinds_apm/constants.rb b/lib/solarwinds_apm/constants.rb index d6bfdc1..f135396 100644 --- a/lib/solarwinds_apm/constants.rb +++ b/lib/solarwinds_apm/constants.rb @@ -33,10 +33,8 @@ module Constants INTL_SWO_OTEL_STATUS_DESCRIPTION = 'otel.status_description' INTERNAL_TRIGGERED_TRACE = 'TriggeredTrace' - APPOPTICS_ENDPOINT = ['collector.appoptics.com', 'collector-stg.appoptics.com', 'collector.appoptics.com:443', 'collector-stg.appoptics.com:443'] - - SW_OTEL_ENDPOINT = 'https://otel.collector.na-01.solarwinds.com:443' - SW_OTEL_ENDPOINT_STG = 'https://otel.collector.na-01.st-ssp.solarwinds.com:443' + APPOPTICS_ENDPOINT = ['collector.appoptics.com', 'collector-stg.appoptics.com', 'collector.appoptics.com:443', 'collector-stg.appoptics.com:443'].freeze + SW_OTEL_METRICS_ENDPOINT = 'https://otel.collector.na-01.solarwinds.com:443/v1/metrics' SW_APM_ENDPOINT = 'apm.collector.cloud.solarwinds.com' end end diff --git a/lib/solarwinds_apm/opentelemetry.rb b/lib/solarwinds_apm/opentelemetry.rb index 088d46f..0b3bc06 100644 --- a/lib/solarwinds_apm/opentelemetry.rb +++ b/lib/solarwinds_apm/opentelemetry.rb @@ -8,6 +8,7 @@ require 'opentelemetry/sdk' require 'opentelemetry/instrumentation/all' +require 'opentelemetry-metrics-sdk' # TODO: in future, it should add opentelemetry-metrics-sdk and require it here diff --git a/lib/solarwinds_apm/otel_config.rb b/lib/solarwinds_apm/otel_config.rb index f67aa44..aa9e67c 100644 --- a/lib/solarwinds_apm/otel_config.rb +++ b/lib/solarwinds_apm/otel_config.rb @@ -91,41 +91,45 @@ def self.validate_propagator(propagators) disable_agent(reason: 'Missing tracecontext propagator.') unless ([::OpenTelemetry::Trace::Propagation::TraceContext::TextMapPropagator, ::OpenTelemetry::Baggage::Propagation::TextMapPropagator] - propagators.map(&:class)).empty? end - def self.setup_otlp_metrics - - # determine the correct endpoint - otlp_metrics_endpoint = ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'] || ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] || '' - - if otlp_metrics_endpoint.empty? - if ENV['SW_APM_COLLECTOR'].nil? - otlp_metrics_endpoint = SolarWindsAPM::Constants::SW_OTEL_ENDPOINT - else - if SolarWindsAPM::Constants::APPOPTICS_ENDPOINT.include?(ENV['SW_APM_COLLECTOR']) - SolarWindsAPM.logger.warn { 'Endpoint is appoptics. Appoptics does not support otlp metrics export. No custom metrics will be exported.' } - else - otlp_metrics_endpoint = ENV['SW_APM_COLLECTOR'] == SolarWindsAPM::Constants::SW_APM_ENDPOINT ? - SolarWindsAPM::Constants::SW_OTEL_ENDPOINT : SolarWindsAPM::Constants::SW_OTEL_ENDPOINT_STG - end - end + # determine the correct endpoint + # if have either OTEL_EXPORTER_OTLP_METRICS_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT, use it + # else, determine if the collector is nil or appoptics + def self.determine_otlp_metrics_endpoint + return unless ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'].to_s.empty? && ENV['OTEL_EXPORTER_OTLP_ENDPOINT'].to_s.empty? + + if SolarWindsAPM::Constants::APPOPTICS_ENDPOINT.include?(ENV['SW_APM_COLLECTOR']) + SolarWindsAPM.logger.warn { 'Endpoint is AppOptics. AppOptics does not support OTLP metrics export. No custom metrics will be exported.' } + else + # SW_OTEL_METRICS_ENDPOINT is the default endpoint for production + # for staging, just use OTEL_EXPORTER_OTLP_METRICS_ENDPOINT + ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'] = SolarWindsAPM::Constants::SW_OTEL_METRICS_ENDPOINT end - ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'] = otlp_metrics_endpoint + SolarWindsAPM.logger.warn { "OTLP metrics endpoint: #{ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'] || ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] || 'No valid endpoint'}." } + end + + def self.setup_otlp_metrics + determine_otlp_metrics_endpoint - token, service_name = ENV['SW_APM_SERVICE_KEY'].split(':') - bearer_token = "authorization=Bearer #{token}" + # determine the headers + # if no explicit define the headers, use the composed headers from SW_APM_SERVICE_KEY + return unless ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS'].to_s.empty? && ENV['OTEL_EXPORTER_OTLP_HEADERS'].to_s.empty? - otlp_metrics_header = ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS'] || ENV['OTEL_EXPORTER_OTLP_HEADERS'] || '' - metrics_service_name = service_name || ENV['OTEL_SERVICE_NAME'] || '' + key_name = ENV['SW_APM_SERVICE_KEY'].to_s.split(':') - ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS'] = bearer_token if otlp_metrics_header.empty? + if key_name.length < 2 + SolarWindsAPM.logger.error { 'No valid SW_APM_SERVICE_KEY present for OTLP_METRICS_HEADERS.' } + else + token = key_name[0] + service_name = key_name[1] - # mTLS? - SolarWindsAPM.logger.warn do { - "Final endpoint: #{otlp_metrics_endpoint}; final masked bearer_token: #{mask_token(token)}" - } + ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS'] = "authorization=Bearer #{token}" + ENV['OTEL_RESOURCE_ATTRIBUTES'] = "sw.data.module=apm,service.name=#{service_name || ENV['OTEL_SERVICE_NAME'] || ''}" + ENV['OTEL_RESOURCE_ATTRIBUTES'].to_s - # resource attributes for otlp metrics - ENV['OTEL_RESOURCE_ATTRIBUTES'] = "sw.data.module=apm,service.name=#{ENV['OTEL_SERVICE_NAME']}," + ENV['OTEL_RESOURCE_ATTRIBUTES'] + SolarWindsAPM.logger.warn do + "OTLP metrics masked headers: #{mask_token(token)}; resource attributes: #{ENV.fetch('OTEL_RESOURCE_ATTRIBUTES', nil)}." + end + end end def self.mask_token(token) diff --git a/solarwinds_apm.gemspec b/solarwinds_apm.gemspec index 5374965..42c9db9 100644 --- a/solarwinds_apm.gemspec +++ b/solarwinds_apm.gemspec @@ -40,10 +40,10 @@ Gem::Specification.new do |s| s.extensions = ['ext/oboe_metal/extconf.rb'] + s.add_dependency('opentelemetry-exporter-otlp-metrics', '>= 0.1.0') s.add_dependency('opentelemetry-instrumentation-all', '>= 0.31.0') - s.add_dependency('opentelemetry-sdk', '>= 1.2.0') s.add_dependency('opentelemetry-metrics-sdk', '>= 0.1.0') - s.add_dependency('opentelemetry-exporter-otlp-metrics', '>= 0.1.0') + s.add_dependency('opentelemetry-sdk', '>= 1.2.0') s.required_ruby_version = '>= 2.7.0' s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) } diff --git a/test/api/set_transaction_name_test.rb b/test/api/set_transaction_name_test.rb index d95f1ed..6af6fc0 100644 --- a/test/api/set_transaction_name_test.rb +++ b/test/api/set_transaction_name_test.rb @@ -7,7 +7,6 @@ require './lib/solarwinds_apm/support/txn_name_manager' require './lib/solarwinds_apm/opentelemetry' require './lib/solarwinds_apm/otel_config' -require './lib/solarwinds_apm/constants' require './lib/solarwinds_apm/api' describe 'SolarWinds Set Transaction Name Test' do diff --git a/test/initest_helper.rb b/test/initest_helper.rb index d29ce16..6f2ddd9 100644 --- a/test/initest_helper.rb +++ b/test/initest_helper.rb @@ -8,6 +8,7 @@ require 'minitest/spec' require 'minitest/reporters' require './lib/solarwinds_apm/logger' +require './lib/solarwinds_apm/constants' ENV['SW_APM_SERVICE_KEY'] = 'this-is-a-dummy-api-token-for-testing-111111111111111111111111111111111:test-service' diff --git a/test/minitest_helper.rb b/test/minitest_helper.rb index 68df77f..e442dc2 100644 --- a/test/minitest_helper.rb +++ b/test/minitest_helper.rb @@ -21,6 +21,7 @@ require './lib/solarwinds_apm/version' require './lib/solarwinds_apm/logger' +require './lib/solarwinds_apm/constants' # simplecov coverage information require 'simplecov' diff --git a/test/opentelemetry/otlp_processor_test.rb b/test/opentelemetry/otlp_processor_test.rb index dd94bb5..d5c778e 100644 --- a/test/opentelemetry/otlp_processor_test.rb +++ b/test/opentelemetry/otlp_processor_test.rb @@ -5,7 +5,6 @@ require 'minitest_helper' require './lib/solarwinds_apm/opentelemetry' -require './lib/solarwinds_apm/constants' require './lib/solarwinds_apm/support/txn_name_manager' require './lib/solarwinds_apm/otel_config' require './lib/solarwinds_apm/api' diff --git a/test/opentelemetry/solarwinds_exporter_test.rb b/test/opentelemetry/solarwinds_exporter_test.rb index d89ee28..1003678 100644 --- a/test/opentelemetry/solarwinds_exporter_test.rb +++ b/test/opentelemetry/solarwinds_exporter_test.rb @@ -10,7 +10,6 @@ require './lib/solarwinds_apm/support/txn_name_manager' require './lib/solarwinds_apm/oboe_init_options' require './lib/solarwinds_apm/config' -require './lib/solarwinds_apm/constants' describe 'SolarWindsExporterTest' do before do diff --git a/test/opentelemetry/solarwinds_processor_test.rb b/test/opentelemetry/solarwinds_processor_test.rb index bafc314..3193337 100644 --- a/test/opentelemetry/solarwinds_processor_test.rb +++ b/test/opentelemetry/solarwinds_processor_test.rb @@ -5,7 +5,6 @@ require 'minitest_helper' require './lib/solarwinds_apm/opentelemetry' -require './lib/solarwinds_apm/constants' require './lib/solarwinds_apm/support/txn_name_manager' require './lib/solarwinds_apm/otel_config' require './lib/solarwinds_apm/api' diff --git a/test/opentelemetry/solarwinds_propagator_test.rb b/test/opentelemetry/solarwinds_propagator_test.rb index 8bb5c3f..1fbccef 100644 --- a/test/opentelemetry/solarwinds_propagator_test.rb +++ b/test/opentelemetry/solarwinds_propagator_test.rb @@ -7,7 +7,6 @@ require 'minitest/mock' require './lib/solarwinds_apm/opentelemetry' require './lib/solarwinds_apm/support/x_trace_options' -require './lib/solarwinds_apm/constants' require './lib/solarwinds_apm/support/utils' require './lib/solarwinds_apm/support/transaction_cache' require './lib/solarwinds_apm/support/transaction_settings' diff --git a/test/opentelemetry/solarwinds_sampler_test.rb b/test/opentelemetry/solarwinds_sampler_test.rb index cafebb7..d7deea0 100644 --- a/test/opentelemetry/solarwinds_sampler_test.rb +++ b/test/opentelemetry/solarwinds_sampler_test.rb @@ -6,7 +6,6 @@ require 'minitest_helper' require './lib/solarwinds_apm/opentelemetry' require './lib/solarwinds_apm/support/x_trace_options' -require './lib/solarwinds_apm/constants' require './lib/solarwinds_apm/support/utils' require './lib/solarwinds_apm/support/transaction_cache' require './lib/solarwinds_apm/support/transaction_settings' diff --git a/test/patch/sw_mysql2_patch_integrate_test.rb b/test/patch/sw_mysql2_patch_integrate_test.rb index 8fc76e8..426d7ad 100644 --- a/test/patch/sw_mysql2_patch_integrate_test.rb +++ b/test/patch/sw_mysql2_patch_integrate_test.rb @@ -10,7 +10,6 @@ require './lib/solarwinds_apm/otel_config' require './lib/solarwinds_apm/api' require './lib/solarwinds_apm/support' -require './lib/solarwinds_apm/constants' require './lib/solarwinds_apm/oboe_init_options' # rubocop:disable Naming/MethodName From 823299459da355eb31ae2b609664249eff4e065e Mon Sep 17 00:00:00 2001 From: xuan-cao-swi Date: Mon, 25 Nov 2024 11:39:37 -0500 Subject: [PATCH 3/5] NH-91603: add test --- lib/solarwinds_apm/otel_config.rb | 38 ++++----- test/solarwinds_apm/otel_config_test.rb | 106 ++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 21 deletions(-) diff --git a/lib/solarwinds_apm/otel_config.rb b/lib/solarwinds_apm/otel_config.rb index aa9e67c..ec98937 100644 --- a/lib/solarwinds_apm/otel_config.rb +++ b/lib/solarwinds_apm/otel_config.rb @@ -91,9 +91,8 @@ def self.validate_propagator(propagators) disable_agent(reason: 'Missing tracecontext propagator.') unless ([::OpenTelemetry::Trace::Propagation::TraceContext::TextMapPropagator, ::OpenTelemetry::Baggage::Propagation::TextMapPropagator] - propagators.map(&:class)).empty? end - # determine the correct endpoint - # if have either OTEL_EXPORTER_OTLP_METRICS_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT, use it - # else, determine if the collector is nil or appoptics + # use the default OTEL_EXPORTER_OTLP_METRICS_ENDPOINT or OTEL_EXPORTER_OTLP_ENDPOINT if exist + # otherwise, use default SW_OTEL_METRICS_ENDPOINT if the collector is not appoptics def self.determine_otlp_metrics_endpoint return unless ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'].to_s.empty? && ENV['OTEL_EXPORTER_OTLP_ENDPOINT'].to_s.empty? @@ -111,32 +110,29 @@ def self.determine_otlp_metrics_endpoint def self.setup_otlp_metrics determine_otlp_metrics_endpoint - # determine the headers - # if no explicit define the headers, use the composed headers from SW_APM_SERVICE_KEY - return unless ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS'].to_s.empty? && ENV['OTEL_EXPORTER_OTLP_HEADERS'].to_s.empty? + token, service_name = ENV['SW_APM_SERVICE_KEY'].to_s.split(':') - key_name = ENV['SW_APM_SERVICE_KEY'].to_s.split(':') + # if no explicit define the headers, use the composed headers from SW_APM_SERVICE_KEY + if ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS'].to_s.empty? && ENV['OTEL_EXPORTER_OTLP_HEADERS'].to_s.empty? - if key_name.length < 2 - SolarWindsAPM.logger.error { 'No valid SW_APM_SERVICE_KEY present for OTLP_METRICS_HEADERS.' } - else - token = key_name[0] - service_name = key_name[1] + if token.nil? + SolarWindsAPM.logger.error { 'No valid SW_APM_SERVICE_KEY present for OTLP_METRICS_HEADERS.' } + return + end ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS'] = "authorization=Bearer #{token}" - ENV['OTEL_RESOURCE_ATTRIBUTES'] = "sw.data.module=apm,service.name=#{service_name || ENV['OTEL_SERVICE_NAME'] || ''}" + ENV['OTEL_RESOURCE_ATTRIBUTES'].to_s - - SolarWindsAPM.logger.warn do - "OTLP metrics masked headers: #{mask_token(token)}; resource attributes: #{ENV.fetch('OTEL_RESOURCE_ATTRIBUTES', nil)}." - end + SolarWindsAPM.logger.warn { "OTLP metrics masked headers: authorization=Bearer #{mask_token(token)}" } end + + ENV['OTEL_RESOURCE_ATTRIBUTES'] = "sw.data.module=apm,service.name=#{service_name || ENV['OTEL_SERVICE_NAME'] || ''}" + ENV['OTEL_RESOURCE_ATTRIBUTES'].to_s + SolarWindsAPM.logger.warn { "OTLP metrics resource attributes: #{ENV.fetch('OTEL_RESOURCE_ATTRIBUTES', nil)}." } end def self.mask_token(token) - first_two = token[0, 23] - last_two = token[-2, 2] - masked = '*' * (token.length - 25) - "#{first_two}#{masked}#{last_two}" + token = token.to_s + return '*' * token.length if token.length <= 4 + + "#{token[0, 2]}#{'*' * (token.length - 4)}#{token[-2, 2]}" end def self.initialize diff --git a/test/solarwinds_apm/otel_config_test.rb b/test/solarwinds_apm/otel_config_test.rb index ffa5007..6cca678 100644 --- a/test/solarwinds_apm/otel_config_test.rb +++ b/test/solarwinds_apm/otel_config_test.rb @@ -98,4 +98,110 @@ _(OpenTelemetry.logger.level).must_equal 4 end end + + describe 'test_otlp_metrics_custom_metrics' do + it 'test_mask_token_with_71' do + token = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + _(SolarWindsAPM::OTelConfig.mask_token(token)).must_equal 'aa*******************************************************************aa' + end + + it 'test_mask_token_with_2' do + token = 'aa' + _(SolarWindsAPM::OTelConfig.mask_token(token)).must_equal '**' + end + + describe 'test_determine_setup_otlp_metrics' do + before do + ENV.delete('SW_APM_EXPORT_METRICS_ENABLED') + ENV.delete('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT') + ENV.delete('OTEL_EXPORTER_OTLP_ENDPOINT') + ENV.delete('SW_APM_COLLECTOR') + end + + it 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT_present_ignore_other_options' do + # ENV['SW_APM_EXPORT_METRICS_ENABLED'] = 'true' + ENV['OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'] = 'http://fake-uri:8181/v1/metrics' + ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] = 'http://fake-again-uri:8181' + ENV['SW_APM_COLLECTOR'] = 'collector.appoptics.com' + + SolarWindsAPM::OTelConfig.determine_otlp_metrics_endpoint + + _(ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT', nil)).must_equal 'http://fake-uri:8181/v1/metrics' + end + + it 'OTEL_EXPORTER_OTLP_ENDPOINT_present_ignore_other_options' do + ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] = 'http://fake-again-uri:8181' + ENV['SW_APM_COLLECTOR'] = 'collector.appoptics.com' + + SolarWindsAPM::OTelConfig.determine_otlp_metrics_endpoint + + _(ENV.fetch('OTEL_EXPORTER_OTLP_ENDPOINT', nil)).must_equal 'http://fake-again-uri:8181' + end + + it 'SW_APM_COLLECTOR_present_with_appoptics' do + ENV['SW_APM_COLLECTOR'] = 'collector.appoptics.com' + + SolarWindsAPM::OTelConfig.determine_otlp_metrics_endpoint + + _(ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT', nil)).must_equal nil + end + + it 'SW_APM_COLLECTOR_present_with_nil' do + ENV['SW_APM_COLLECTOR'] = nil + + SolarWindsAPM::OTelConfig.determine_otlp_metrics_endpoint + + _(ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT', nil)).must_equal 'https://otel.collector.na-01.solarwinds.com:443/v1/metrics' + end + + it 'SW_APM_COLLECTOR_present_with_any_kind_url_will_use_default_endpoint' do + ENV['SW_APM_COLLECTOR'] = 'apm.so-fake.cloud.solarwinds.com' + + SolarWindsAPM::OTelConfig.determine_otlp_metrics_endpoint + + _(ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT', nil)).must_equal 'https://otel.collector.na-01.solarwinds.com:443/v1/metrics' + end + end + + describe 'test_setup_otlp_metrics' do + before do + ENV.delete('OTEL_EXPORTER_OTLP_METRICS_ENDPOINT') + ENV.delete('OTEL_EXPORTER_OTLP_METRICS_HEADERS') + ENV.delete('OTEL_EXPORTER_OTLP_HEADERS') + ENV.delete('SW_APM_SERVICE_KEY') + ENV.delete('OTEL_RESOURCE_ATTRIBUTES') + ENV.delete('OTEL_SERVICE_NAME') + end + + it 'SW_APM_SERVICE_KEY_present_without_headers_defined' do + ENV['SW_APM_SERVICE_KEY'] = 'so_keyso_keyso_keyso_keyso_keyso_keyso_keyso_keyso_keyso_key:so_name' + + SolarWindsAPM::OTelConfig.setup_otlp_metrics + + _(ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_HEADERS', nil)).must_equal 'authorization=Bearer so_keyso_keyso_keyso_keyso_keyso_keyso_keyso_keyso_keyso_key' + _(ENV.fetch('OTEL_RESOURCE_ATTRIBUTES', nil)).must_equal 'sw.data.module=apm,service.name=so_name' + end + + it 'SW_APM_SERVICE_KEY_present_with_OTEL_EXPORTER_OTLP_METRICS_HEADERS_defined' do + ENV['OTEL_EXPORTER_OTLP_METRICS_HEADERS'] = 'sample_fake_headers' + ENV['SW_APM_SERVICE_KEY'] = 'so_keyso_keyso_keyso_keyso_keyso_keyso_keyso_keyso_keyso_key:so_name' + + SolarWindsAPM::OTelConfig.setup_otlp_metrics + + _(ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_HEADERS', nil)).must_equal 'sample_fake_headers' + _(ENV.fetch('OTEL_RESOURCE_ATTRIBUTES', nil)).must_equal 'sw.data.module=apm,service.name=so_name' + end + + it 'SW_APM_SERVICE_KEY_present_with_OTEL_EXPORTER_OTLP_HEADERS_defined' do + ENV['OTEL_EXPORTER_OTLP_HEADERS'] = 'sample_fake_again_headers' + ENV['SW_APM_SERVICE_KEY'] = 'so_keyso_keyso_keyso_keyso_keyso_keyso_keyso_keyso_keyso_key:so_name' + + SolarWindsAPM::OTelConfig.setup_otlp_metrics + + _(ENV.fetch('OTEL_EXPORTER_OTLP_HEADERS', nil)).must_equal 'sample_fake_again_headers' + _(ENV.fetch('OTEL_EXPORTER_OTLP_METRICS_HEADERS', nil)).must_equal nil + _(ENV.fetch('OTEL_RESOURCE_ATTRIBUTES', nil)).must_equal 'sw.data.module=apm,service.name=so_name' + end + end + end end From 84165f3ec6d56eebe01a583e0e9b5612794aace2 Mon Sep 17 00:00:00 2001 From: xuan-cao-swi Date: Mon, 25 Nov 2024 12:05:05 -0500 Subject: [PATCH 4/5] add doc --- CONFIGURATION.md | 83 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 908d2d5..c07de17 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -112,26 +112,27 @@ The configuration file should be Ruby code that sets key/values in the hash expo ## Reference -Environment Variable | Config File Key | Description | Default --------------------- | --------------- | ----------- | ------- -`SW_APM_AUTO_CONFIGURE` | N/A | By default the library is configured to work out-of-the-box with all automatic instrumentation libraries enabled. Set this to `false` to custom initialize the library with configuration options for instrumentation, see [Programmatic Configuration](#programmatic-configuration) for details. | `true` -`SW_APM_COLLECTOR` | N/A | Override the default collector endpoint to which the library connects and exports data. It should be defined using the format host:port. | `apm.collector.na-01.cloud.solarwinds.com:443` -`SW_APM_CONFIG_RUBY` | N/A | Override the default location for the configuration file. This can be an absolute or relative filename, or the directory under which the `solarwinds_apm_config.rb` file would be looked for. | None -`SW_APM_DEBUG_LEVEL` | `:debug_level` | Set the library's logging level, valid values are -1 through 6 (least to most verbose).
Setting -1 disables logging from the library. | 3 -`SW_APM_EC2_METADATA_TIMEOUT` | `:ec2_metadata_timeout` | Timeout for AWS IMDS metadata retrieval in milliseconds. | 1000 -`SW_APM_ENABLED` | N/A | Enable/disable the library, setting `false` is an alternative to uninstalling `solarwinds_apm` since it will prevent the library from loading. | `true` -`SW_APM_LOG_FILEPATH` | N/A | Configure the log file path for the C extension, e.g. `export SW_APM_LOG_FILEPATH=/path/file_path.log`. If set, messages from the C extension are written to the specified file instead of stderr. | None -`SW_APM_PROXY` | `:http_proxy` | Configure an HTTP proxy through which the library connects to the collector. | None -`SW_APM_SERVICE_KEY` | `:service_key` | API token and service name in the form of `token:service_name`, **required**. | None -`SW_APM_TAG_SQL` | `:tag_sql` | Enable/disable injecting trace context into supported SQL statements. Set to boolean true or (or string `true` in env var) to enable, see [Tag Query with Trace Context](#tag-query-with-trace-context) for details.| `false` -`SW_APM_TRIGGER_TRACING_MODE` | `:trigger_tracing_mode` | Enable/disable trigger tracing for the service. Setting to `disabled` may impact DEM visibility into the service. | `enabled` -`SW_APM_TRUSTEDPATH` | N/A | The library uses the host system's default trusted CA certificates to verify the TLS connection to the collector. To override the default, define the trusted certificate path configuration option with an absolute path to a specific trusted certificate file in PEM format. | None -`SW_APM_LAMBDA_PRELOAD_DEPS` | N/A | This option only takes effect in the AWS Lambda runtime. Set to `false` to disable the attempt to preload function dependencies and install instrumentations. | `true` -`SW_APM_TRANSACTION_NAME` | N/A | Customize the transaction name for all traces, typically used to target specific instrumented lambda functions. _Precedence order_: custom SDK > `SW_APM_TRANSACTION_NAME` > automatic naming | None -N/A | `:log_args` | Enable/disable the collection of URL query parameters, set to boolean false to disable. | true -N/A | `:log_traceId` | Configure the insertion of trace context into application logs, setting `:traced` would include the available context fields such as trace_id, span_id into log messages. | `:never` -N/A | `:tracing_mode` | Enable/disable the tracing mode for this service, setting `:disabled` would suppress all trace spans and metrics. | `:enabled` -N/A | `:transaction_settings` | Configure tracing mode per transaction, aka transaction filtering. See [Transaction Filtering](#transaction-filtering) for details.| None +Environment Variable | Option | Config File Key | Description | Default +-------------------- | ------ | --------------- | ----------- | ------- +`SW_APM_SERVICE_KEY` | required | `:service_key` | API token and service name in the form of `token:service_name`. | None +`SW_APM_AUTO_CONFIGURE` | optional | N/A | By default the library is configured to work out-of-the-box with all automatic instrumentation libraries enabled. Set this to `false` to custom initialize the library with configuration options for instrumentation, see [Programmatic Configuration](#programmatic-configuration) for details. | `true` +`SW_APM_COLLECTOR` | optional | N/A | Override the default collector endpoint to which the library connects and exports data. It should be defined using the format host:port. | `apm.collector.na-01.cloud.solarwinds.com:443` +`SW_APM_CONFIG_RUBY` | optional | N/A | Override the default location for the configuration file. This can be an absolute or relative filename, or the directory under which the `solarwinds_apm_config.rb` file would be looked for. | None +`SW_APM_DEBUG_LEVEL` | optional | `:debug_level` | Set the library's logging level, valid values are -1 through 6 (least to most verbose).
Setting -1 disables logging from the library. | 3 +`SW_APM_EC2_METADATA_TIMEOUT` | optional | `:ec2_metadata_timeout` | Timeout for AWS IMDS metadata retrieval in milliseconds. | 1000 +`SW_APM_ENABLED` | optional | N/A | Enable/disable the library, setting `false` is an alternative to uninstalling `solarwinds_apm` since it will prevent the library from loading. | `true` +`SW_APM_LOG_FILEPATH` | optional | N/A | Configure the log file path for the C extension, e.g. `export SW_APM_LOG_FILEPATH=/path/file_path.log`. If set, messages from the C extension are written to the specified file instead of stderr. | None +`SW_APM_PROXY` | optional | `:http_proxy` | Configure an HTTP proxy through which the library connects to the collector. | None +`SW_APM_TAG_SQL` | optional | `:tag_sql` | Enable/disable injecting trace context into supported SQL statements. Set to boolean true or (or string `true` in env var) to enable, see [Tag Query with Trace Context](#tag-query-with-trace-context) for details.| `false` +`SW_APM_TRIGGER_TRACING_MODE` | optional | `:trigger_tracing_mode` | Enable/disable trigger tracing for the service. Setting to `disabled` may impact DEM visibility into the service. | `enabled` +`SW_APM_TRUSTEDPATH` | optional | N/A | The library uses the host system's default trusted CA certificates to verify the TLS connection to the collector. To override the default, define the trusted certificate path configuration option with an absolute path to a specific trusted certificate file in PEM format. | None +`SW_APM_LAMBDA_PRELOAD_DEPS` | optional | N/A | This option only takes effect in the AWS Lambda runtime. Set to `false` to disable the attempt to preload function dependencies and install instrumentations. | `true` +`SW_APM_TRANSACTION_NAME` | optional | N/A | Customize the transaction name for all traces, typically used to target specific instrumented lambda functions. _Precedence order_: custom SDK > `SW_APM_TRANSACTION_NAME` > automatic naming | None +`SW_APM_EXPORT_METRICS_ENABLED` | optional | N/A | Enable the custom metrics to export to swo backend through otlp metrics protocol | `false` +N/A | optional | `:log_args` | Enable/disable the collection of URL query parameters, set to boolean false to disable. | true +N/A | optional | `:log_traceId` | Configure the insertion of trace context into application logs, setting `:traced` would include the available context fields such as trace_id, span_id into log messages. | `:never` +N/A | optional | `:tracing_mode` | Enable/disable the tracing mode for this service, setting `:disabled` would suppress all trace spans and metrics. | `:enabled` +N/A | optional | `:transaction_settings` | Configure tracing mode per transaction, aka transaction filtering. See [Transaction Filtering](#transaction-filtering) for details.| None ### Transaction Filtering @@ -151,6 +152,48 @@ SolarWindsAPM::Config[:transaction_settings] = [ ] ``` +### OpenTelemetry Metrics Export with Custom Metrics + +If user want to export the opentelemetry metrics through opentelemetry otlp protocol, user can do it directly after require `solarwinds_apm`. Since we don't provide the default metrics exporter, user needs to install `opentelemetry-exporter-otlp-metrics`. + +Required setting: + +```console +export SW_APM_EXPORT_METRICS_ENABLED='true' # enable the metrics +export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT='your desired endpoint' +export OTEL_EXPORTER_OTLP_METRICS_HEADERS='headers should include authentication e.g. authorization=Bearer ***' +``` + +Without setting `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` and `OTEL_EXPORTER_OTLP_METRICS_HEADERS`, `solarwinds_apm` will provide the default value. These value will export metrics to solarwinds apm production. + +```console +OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: https://otel.collector.na-01.solarwinds.com:443/v1/metrics +OTEL_EXPORTER_OTLP_METRICS_HEADERS: authorization=Bearer +``` + +Example: + +```ruby +require 'opentelemetry-exporter-otlp-metrics' +require 'solarwinds_apm' + +# initialize the metrics exporter +otlp_metric_exporter = OpenTelemetry::Exporter::OTLP::Metrics::MetricsExporter.new + +# add metrics exporter to meter_provider +OpenTelemetry.meter_provider.add_metric_reader(otlp_metric_exporter) + +# initialize meter +meter = OpenTelemetry.meter_provider.meter("SAMPLE_METER_NAME") + +# create a new metrics instrument +histogram = meter.create_histogram('sample_histogram', unit: 'smidgen', description: 'desscription') + +histogram.record(123, attributes: {'foo' => 'bar'}) + +# you should see the metrics in swo backend +``` + ### Tag Query with Trace Context You can set the environment variable `SW_APM_TAG_SQL` or configuration file option `:tag_sql` to true to enable appending the current trace context into a database query as a SQL comment. For example: From 4a5d612aa4622c6bb66a89f73109c08f951a61fd Mon Sep 17 00:00:00 2001 From: xuan-cao-swi Date: Mon, 25 Nov 2024 12:11:26 -0500 Subject: [PATCH 5/5] update doc --- CONFIGURATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index c07de17..daa99e4 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -154,7 +154,7 @@ SolarWindsAPM::Config[:transaction_settings] = [ ### OpenTelemetry Metrics Export with Custom Metrics -If user want to export the opentelemetry metrics through opentelemetry otlp protocol, user can do it directly after require `solarwinds_apm`. Since we don't provide the default metrics exporter, user needs to install `opentelemetry-exporter-otlp-metrics`. +If user want to export the opentelemetry metrics through opentelemetry otlp protocol to solarwinds observability, user can do it directly after require `solarwinds_apm`. Since we don't provide the default metrics exporter, user needs to install `opentelemetry-exporter-otlp-metrics`. However, `solarwinds_apm` agent is not responsible to decide when to export. User need to export by themselves (e.g. `OpenTelemetry.meter_provider.metric_readers.each { |reader| reader.pull if reader.respond_to?(:pull) }` or use [PeriodicMetricReader](https://github.com/open-telemetry/opentelemetry-ruby/blob/main/metrics_sdk/lib/opentelemetry/sdk/metrics/export/periodic_metric_reader.rb) to export with desired time interval. Required setting: