-
Notifications
You must be signed in to change notification settings - Fork 273
Description
We've recently looked into adding a custom SpanProcessor to most of our applications. We need integrate with a particular vendor that doesn't fully/truly support OpenTelemetry specifications and relies on setting custom attributes (e.g. operation.name
, resource.name
and even adjusting the Span Kind (to avoid "internal")).
The SpanProcessor itself is easily written, but adding it to an otherwise default setup of OpenTelemetry in Ruby is not:
require 'rake'
require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
require 'opentelemetry/instrumentation/all'
class RakeTaskSpanProcessor < ::OpenTelemetry::SDK::Trace::SpanProcessor
def on_start(span, _parent_context)
return unless span.name =~ /^rake\.(invoke|execute)/
task = span.attributes['rake.task']
span.set_attribute('operation.name', span.name)
span.set_attribute('resource.name', task || 'unknown')
end
end
::OpenTelemetry::SDK.configure do |c|
c.add_span_processor(RakeTaskSpanProcessor.new)
c.use_all
end
desc 'Dummy task'
task :dummy do
puts 'foo'
end
# write to a file: Rakefile.custom
# run: env OTEL_RESOURCE_ATTRIBUTES="service.namespace=rake-tester,service.name=test" rake -f Rakefile.custom dummy
Adding a SpanProcessor in this way will result in no exporting SpanProcessors being added. This seems to be due to https://github.com/open-telemetry/opentelemetry-ruby/blob/opentelemetry-sdk/v1.9.0/sdk/lib/opentelemetry/sdk/configurator.rb#L180-L183 which only adds the default config from the environment if no other SpanProcessors are configured. That seems to be due to Exporters being implemented as SpanProcessors themselves with no other clearly identifiable markers (e.g. a shared parent class or a specific attribute). So while it is unfortunate that just "adding" a SpanProcessor breaks default behaviour it does seem consistent.
However, there is no built in way for somebody who adds a SpanProcessor to opt in to also getting the default SpanProcessors (exporters), because wrapped_exporters_from_env
is a private method.
In our testing the following setup works as intended:
require 'rake'
require 'opentelemetry/sdk'
require 'opentelemetry/exporter/otlp'
require 'opentelemetry/instrumentation/all'
class RakeTaskSpanProcessor < ::OpenTelemetry::SDK::Trace::SpanProcessor
def on_start(span, _parent_context)
return unless span.name =~ /^rake\.(invoke|execute)/
task = span.attributes['rake.task']
span.set_attribute('operation.name', span.name)
span.set_attribute('resource.name', task || 'unknown')
end
end
::OpenTelemetry::SDK.configure do |c|
c.add_span_processor(RakeTaskSpanProcessor.new)
c.send(:wrapped_exporters_from_env).compact.each { |p| c.add_span_processor(p) }
c.use_all
end
desc 'Dummy task'
task :dummy do
puts 'foo'
end
But it requires calling a private method (c.send(:wrapped_exporters_from_env).compact.each { |p| c.add_span_processor(p) }
), which is inherently undesirable.
Instead it would be preferable if obtaining the environment based default exporters would be a documented method on the public interface or if there would be a method to add span processors without deactivating the default behaviour.