From 29418cf145e199e49f1969c024a0524f58ca3c1e Mon Sep 17 00:00:00 2001 From: Jakub Malinowski Date: Fri, 11 Mar 2022 19:11:33 +0100 Subject: [PATCH] feat: Add preconfigured support for Smart Agent --- README.md | 10 +++- examples/smart-agent/.gitignore | 1 + examples/smart-agent/Gemfile | 6 +++ examples/smart-agent/docker-compose.yml | 8 +++ examples/smart-agent/runner.rb | 22 ++++++++ examples/smart-agent/smart-agent-config.yaml | 5 ++ lib/splunk/otel.rb | 36 ++++++++----- lib/splunk/otel/proprietary_exporters.rb | 54 +++++++++++++++++++ .../splunk/otel/proprietary_exporters_test.rb | 25 +++++++++ test/test_helper.rb | 3 +- 10 files changed, 153 insertions(+), 17 deletions(-) create mode 100644 examples/smart-agent/.gitignore create mode 100644 examples/smart-agent/Gemfile create mode 100644 examples/smart-agent/docker-compose.yml create mode 100644 examples/smart-agent/runner.rb create mode 100644 examples/smart-agent/smart-agent-config.yaml create mode 100644 lib/splunk/otel/proprietary_exporters.rb create mode 100644 test/splunk/otel/proprietary_exporters_test.rb diff --git a/README.md b/README.md index b1b479d..27ed7c6 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ end ``` And add the middleware in `Rack::Builder`: - + ``` ruby Rack::Builder.app do use OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware @@ -227,6 +227,14 @@ To disable the response headers being added the environment variable `SPLUNK_TRACE_RESPONSE_HEADER_ENABLED` can be set to `false`, or pass `trace_response_header_enabled: false` to `Splunk::Otel.configure`. +## Configure for use with Smart Agent + +This distribution includes the `jaeger-thrift-splunk` exporter, which is preconfigured to send data to local instance of the [SignalFx Smart Agent](https://github.com/signalfx/signalfx-agent). + +To use the `jaeger-thrift-splunk` exporter, set the`OTEL_TRACES_EXPORTER` environment variable to `jaeger-thrift-splunk`, or append the exporter to the existing values. For example, `OTEL_TRACES_EXPORTER=otlp,jaeger-thrift-splunk`. + +If the `SPLUNK_REALM` or the `OTEL_EXPORTER_JAEGER_ENDPOINT` environmental variables are set, the default endpoint is overwritten. + ## Troubleshooting For troubleshooting information, see the [Troubleshooting](docs/troubleshooting.md) documentation. diff --git a/examples/smart-agent/.gitignore b/examples/smart-agent/.gitignore new file mode 100644 index 0000000..68feb7d --- /dev/null +++ b/examples/smart-agent/.gitignore @@ -0,0 +1 @@ +Gemfile.lock \ No newline at end of file diff --git a/examples/smart-agent/Gemfile b/examples/smart-agent/Gemfile new file mode 100644 index 0000000..3727071 --- /dev/null +++ b/examples/smart-agent/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "opentelemetry-exporter-jaeger" +gem "splunk-otel", path: "../../" diff --git a/examples/smart-agent/docker-compose.yml b/examples/smart-agent/docker-compose.yml new file mode 100644 index 0000000..7324a27 --- /dev/null +++ b/examples/smart-agent/docker-compose.yml @@ -0,0 +1,8 @@ +version: "3" +services: + otel: + image: quay.io/signalfx/signalfx-agent:5 + ports: + - 9080:9080 + volumes: + - ./smart-agent-config.yaml:/etc/signalfx/agent.yaml diff --git a/examples/smart-agent/runner.rb b/examples/smart-agent/runner.rb new file mode 100644 index 0000000..5fecb9d --- /dev/null +++ b/examples/smart-agent/runner.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require "rubygems" +require "bundler/setup" +Bundler.require(:default) + +Splunk::Otel.configure do |configurator| + $configurator = configurator # rubocop:disable Style/GlobalVars + + class << configurator + def test_shutdown + @span_processors.each(&:shutdown) + end + end +end + +tracer = OpenTelemetry.tracer_provider.tracer("test", "1.0") +tracer.in_span("test-span") do + puts "test-span execution" +end + +$configurator.test_shutdown # rubocop:disable Style/GlobalVars diff --git a/examples/smart-agent/smart-agent-config.yaml b/examples/smart-agent/smart-agent-config.yaml new file mode 100644 index 0000000..b20b633 --- /dev/null +++ b/examples/smart-agent/smart-agent-config.yaml @@ -0,0 +1,5 @@ +signalFxAccessToken: "" +ingestUrl: "https://ingest.lab0.signalfx.com" +signalFxRealm: "lab0" +monitors: + - type: trace-forwarder diff --git a/lib/splunk/otel.rb b/lib/splunk/otel.rb index 8e787bd..0242a77 100644 --- a/lib/splunk/otel.rb +++ b/lib/splunk/otel.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -require_relative "otel/version" require "opentelemetry/sdk" - -# FIXME: without this, otlp doesn't work out-of-the-box require "opentelemetry-exporter-otlp" +require_relative "otel/version" +require_relative "otel/proprietary_exporters" + module Splunk # main module for application startup configuration - module Otel + module Otel # rubocop:disable Metrics/ModuleLength # custom exception types in this gem must inherit from Splunk::Otel::Error # this allows the user to rescue a generic exception type to catch all exceptions class Error < StandardError; end @@ -57,20 +57,21 @@ def configure(service_name: ENV.fetch("OTEL_SERVICE_NAME", "unnamed-ruby-service trace_response_header_enabled: ENV.fetch("SPLUNK_TRACE_RESPONSE_HEADER_ENABLED", "true")) @trace_response_header_enabled = to_boolean(trace_response_header_enabled) - set_default_propagators - set_access_token_header - set_default_exporter - set_default_span_limits + set_defaults # run SDK's setup function - OpenTelemetry::SDK.configure do |c| - c.service_name = service_name - c.resource = OpenTelemetry::SDK::Resources::Resource.create( + OpenTelemetry::SDK.configure do |configurator| + class << configurator + include Splunk::Otel::ExporterExtensions + end + + configurator.service_name = service_name + configurator.resource = OpenTelemetry::SDK::Resources::Resource.create( "splunk.distro.version" => Splunk::Otel::VERSION ) - c.use_all if auto_instrument - yield c if block_given? + configurator.use_all if auto_instrument + yield configurator if block_given? end # set span limits to GDI defaults if not set by the user @@ -79,6 +80,13 @@ def configure(service_name: ENV.fetch("OTEL_SERVICE_NAME", "unnamed-ruby-service verify_service_name end + def set_defaults + set_default_propagators + set_access_token_header + set_default_exporter + set_default_span_limits + end + # verify `service.name` is set and print a warning if it is still the default def verify_service_name provider_resource = OpenTelemetry.tracer_provider.instance_variable_get(:@resource) @@ -183,7 +191,7 @@ def service_name_warning module_function :configure, :gdi_span_limits, :set_default_propagators, :set_default_exporter, :verify_service_name, :service_name_warning, :default_env_vars, :set_default_span_limits, :set_access_token_header, :set_endpoint, - :to_boolean, :trace_response_header_enabled + :to_boolean, :trace_response_header_enabled, :set_defaults end end diff --git a/lib/splunk/otel/proprietary_exporters.rb b/lib/splunk/otel/proprietary_exporters.rb new file mode 100644 index 0000000..4c834b5 --- /dev/null +++ b/lib/splunk/otel/proprietary_exporters.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Splunk + module Otel + OUR_EXPORTERS_KEYS = ["jaeger-thrift-splunk"].freeze + private_constant :OUR_EXPORTERS_KEYS + + # internal module for allowing our exporters to be set via ENV + module ExporterExtensions + def effective_splunk_jaeger_endpoint + return "https://ingest.#{ENV["SPLUNK_REALM"]}.signalfx.com/v1/trace" if ENV["SPLUNK_REALM"] + return ENV["OTEL_EXPORTER_JAEGER_ENDPOINT"] if ENV["OTEL_EXPORTER_JAEGER_ENDPOINT"] + + "http://127.0.0.1:9080/v1/trace" + end + + def wrapped_exporters_from_env + original_env_value = ENV.fetch("OTEL_TRACES_EXPORTER", "otlp") + exporters_keys = original_env_value.split(",").map(&:strip) + non_proprietary_exporters_keys = exporters_keys - OUR_EXPORTERS_KEYS + + ENV["OTEL_TRACES_EXPORTER"] = non_proprietary_exporters_keys.join(",") + original_exporters = super + ENV["OTEL_TRACES_EXPORTER"] = original_env_value + + original_exporters + proprietary_exporters(exporters_keys) + end + + def proprietary_exporters(exporters_keys) + (exporters_keys & OUR_EXPORTERS_KEYS).map do |exporter_key| + case exporter_key + when "jaeger-thrift-splunk" + args = { + endpoint: effective_splunk_jaeger_endpoint + } + fetch_exporter_with_args("jaeger-thrift-splunk", "OpenTelemetry::Exporter::Jaeger::CollectorExporter", + **args) + end + end.compact + end + + def fetch_exporter_with_args(name, class_name, **args) + # TODO: warn if jaeger exporter gem is not present + exporter = Kernel.const_get(class_name).new(**args) + OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new exporter + rescue NameError + OpenTelemetry.logger.warn "The #{name} exporter cannot be configured" \ + "- please add opentelemetry-exporter-#{name} to your Gemfile" \ + ", spans will not be exported.." + nil + end + end + end +end diff --git a/test/splunk/otel/proprietary_exporters_test.rb b/test/splunk/otel/proprietary_exporters_test.rb new file mode 100644 index 0000000..966b7e2 --- /dev/null +++ b/test/splunk/otel/proprietary_exporters_test.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "test_helper" +require "opentelemetry/sdk" +require "opentelemetry-exporter-jaeger" + +module Splunk + class OtelProprietaryExportersTest < Test::Unit::TestCase + def setup + with_env("OTEL_TRACES_EXPORTER" => "jaeger-thrift-splunk") do + Splunk::Otel.configure + end + end + + def teardown + OpenTelemetry.tracer_provider.shutdown + end + + test "check that jaeger exporter is present" do + processors = OpenTelemetry.tracer_provider.instance_variable_get("@span_processors") + exporter = processors[0].instance_variable_get("@exporter") + assert_instance_of OpenTelemetry::Exporter::Jaeger::CollectorExporter, exporter + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 0109852..b57e2f1 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -6,8 +6,7 @@ require "simplecov-cobertura" SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter -$LOAD_PATH.unshift File.expand_path("../lib", __dir__) -require "splunk/otel" +require_relative "../lib/splunk/otel" require "test-unit"