From fe2ef476753dc6d3ee34edbf6cc2d5d7bc5e681e Mon Sep 17 00:00:00 2001 From: cn Date: Fri, 28 May 2021 11:15:14 +0200 Subject: [PATCH] tracing: migrate from OpenTracing to OpenTelemetry - still uses Jaeger client (`AgentExporter`) and supports similar configuration except host + port which can be done via https://github.com/open-telemetry/opentelemetry-ruby/tree/main/exporter/jaeger#how-can-i-configure-the-jaeger-exporter --- README.md | 12 ++--- dyndnsd.gemspec | 6 +-- lib/dyndnsd.rb | 46 +++++++++++++------ lib/dyndnsd/helper.rb | 23 ++++------ lib/dyndnsd/updater/command_with_bind_zone.rb | 2 +- lib/dyndnsd/updater/zone_transfer_server.rb | 2 +- spec/dyndnsd/daemon_spec.rb | 4 +- 7 files changed, 50 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index ae9799d..62f9a19 100644 --- a/README.md +++ b/README.md @@ -271,9 +271,9 @@ users: ### Tracing (experimental) -For tracing, dyndnsd.rb is instrumented using the [OpenTracing](http://opentracing.io/) framework and will emit span tracing data for the most important operations happening during the request/response cycle. Using a middleware for Rack allows handling incoming OpenTracing span information properly. +For tracing, dyndnsd.rb is instrumented using the [OpenTelemetry](https://opentelemetry.io/docs/ruby/) framework and will emit span tracing data for the most important operations happening during the request/response cycle. Using an [instrumentation for Rack](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/instrumentation/rack) allows handling incoming OpenTelemetry parent span information properly (when desired, turned off by default to reduce attack surface). -Currently, only one OpenTracing-compatible tracer implementation named [CNCF Jaeger](https://github.com/jaegertracing/jaeger) can be configured to use with dyndnsd.rb. +Currently, the [OpenTelemetry trace exporter](https://github.com/open-telemetry/opentelemetry-ruby/tree/main/exporter/jaeger) for [CNCF Jaeger](https://github.com/jaegertracing/jaeger) can be enabled via dyndnsd.rb configuration. Alternatively, you can also enable other exporters via the environment variable `OTEL_TRACES_EXPORTER`, e.g. `OTEL_TRACES_EXPORTER=console`. ```yaml host: "0.0.0.0" @@ -282,11 +282,9 @@ db: "/opt/dyndnsd/db.json" domain: "dyn.example.org" # enable and configure tracing using the (currently only) tracer jaeger tracing: - trust_incoming_span: false # default value, change to accept incoming OpenTracing spans as parents - jaeger: - host: 127.0.0.1 # defaults for host and port of local jaeger-agent - port: 6831 - service_name: "my.dyndnsd.identifier" + trust_incoming_span: false # default value, change to accept incoming OpenTelemetry spans as parents + service_name: "my.dyndnsd.identifier" # default unset, will be populated by OpenTelemetry + jaeger: true # enables the Jaeger AgentExporter # configure the updater, here we use command_with_bind_zone, params are updater-specific updater: name: "command_with_bind_zone" diff --git a/dyndnsd.gemspec b/dyndnsd.gemspec index 4a0c501..34daaa8 100644 --- a/dyndnsd.gemspec +++ b/dyndnsd.gemspec @@ -28,11 +28,11 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.5' s.add_runtime_dependency 'async-dns', '~> 1.2.0' - s.add_runtime_dependency 'jaeger-client', '~> 1.1.0' s.add_runtime_dependency 'metriks' - s.add_runtime_dependency 'opentracing', '~> 0.5.0' + s.add_runtime_dependency 'opentelemetry-exporter-jaeger', '~> 0.18.0' + s.add_runtime_dependency 'opentelemetry-instrumentation-rack', '~> 0.18.0' + s.add_runtime_dependency 'opentelemetry-sdk', '~> 1.0.0.rc1' s.add_runtime_dependency 'rack', '~> 2.0' - s.add_runtime_dependency 'rack-tracer', '~> 0.9.0' s.add_runtime_dependency 'webrick', '>= 1.6.1' s.add_development_dependency 'bundler' diff --git a/lib/dyndnsd.rb b/lib/dyndnsd.rb index 2134745..7821044 100644 --- a/lib/dyndnsd.rb +++ b/lib/dyndnsd.rb @@ -8,9 +8,9 @@ require 'json' require 'yaml' require 'rack' require 'metriks' +require 'opentelemetry/instrumentation/rack' +require 'opentelemetry/sdk' require 'metriks/reporter/graphite' -require 'opentracing' -require 'rack/tracer' require 'dyndnsd/generator/bind' require 'dyndnsd/updater/command_with_bind_zone' @@ -69,7 +69,7 @@ module Dyndnsd # @return [Boolean] def authorized?(username, password) Helper.span('check_authorized') do |span| - span.set_tag('dyndnsd.user', username) + span.set_attribute('enduser.id', username) allow = Helper.user_allowed?(username, password, @users) if !allow @@ -170,7 +170,7 @@ module Dyndnsd def process_changes(hostnames, myips) changes = [] Helper.span('process_changes') do |span| - span.set_tag('dyndnsd.hostnames', hostnames.join(',')) + span.set_attribute('dyndnsd.hostnames', hostnames.join(',')) hostnames.each do |hostname| # myips order is always deterministic @@ -252,6 +252,8 @@ module Dyndnsd Dyndnsd.logger.progname = 'dyndnsd' Dyndnsd.logger.formatter = LogFormatter.new Dyndnsd.logger.level = config['debug'] ? Logger::DEBUG : Logger::INFO + + OpenTelemetry.logger = Dyndnsd.logger end # @return [void] @@ -296,16 +298,31 @@ module Dyndnsd # @param config [Hash{String => Object}] # @return [void] private_class_method def self.setup_tracing(config) - # configure OpenTracing - if config.dig('tracing', 'jaeger') - require 'jaeger/client' + # by default do not try to emit any traces until the user opts in + ENV['OTEL_TRACES_EXPORTER'] ||= 'none' - host = config['tracing']['jaeger']['host'] || '127.0.0.1' - port = config['tracing']['jaeger']['port'] || 6831 - service_name = config['tracing']['jaeger']['service_name'] || 'dyndnsd' - OpenTracing.global_tracer = Jaeger::Client.build( - host: host, port: port, service_name: service_name, flush_interval: 1 - ) + # configure OpenTelemetry + OpenTelemetry::SDK.configure do |c| + if config.dig('tracing', 'jaeger') + require 'opentelemetry/exporter/jaeger' + + c.add_span_processor( + OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new( + OpenTelemetry::Exporter::Jaeger::AgentExporter.new + ) + ) + end + + if config.dig('tracing', 'service_name') + c.service_name = config['tracing']['service_name'] + end + + c.service_version = Dyndnsd::VERSION + c.use('OpenTelemetry::Instrumentation::Rack') + end + + if !config.dig('tracing', 'trust_incoming_span') + OpenTelemetry.propagation = OpenTelemetry::Context::Propagation::NoopTextMapPropagator.new end end @@ -331,8 +348,7 @@ module Dyndnsd app = Responder::DynDNSStyle.new(app) end - trust_incoming_span = config.dig('tracing', 'trust_incoming_span') || false - app = Rack::Tracer.new(app, trust_incoming_span: trust_incoming_span) + app = OpenTelemetry::Instrumentation::Rack::Middlewares::TracerMiddleware.new(app) Rack::Handler::WEBrick.run app, Host: config['host'], Port: config['port'] end diff --git a/lib/dyndnsd/helper.rb b/lib/dyndnsd/helper.rb index 88bae76..ef6fce3 100644 --- a/lib/dyndnsd/helper.rb +++ b/lib/dyndnsd/helper.rb @@ -45,24 +45,17 @@ module Dyndnsd # @param block [Proc] # @return [void] def self.span(operation, &block) - scope = OpenTracing.start_active_span(operation) - span = scope.span - span.set_tag('component', 'dyndnsd') - span.set_tag('span.kind', 'server') - begin + tracer = OpenTelemetry.tracer_provider.tracer(Dyndnsd.name, Dyndnsd::VERSION) + tracer.in_span( + operation, + attributes: {'component' => 'dyndnsd'}, + kind: :server + ) do |span| + Dyndnsd.logger.debug "Creating span ID #{span.context.hex_span_id} for trace ID #{span.context.hex_trace_id}" block.call(span) rescue StandardError => e - span.set_tag('error', true) - span.log_kv( - event: 'error', - 'error.kind': e.class.to_s, - 'error.object': e, - message: e.message, - stack: e.backtrace&.join("\n") || '' - ) + span.record_exception(e) raise e - ensure - scope.close end end end diff --git a/lib/dyndnsd/updater/command_with_bind_zone.rb b/lib/dyndnsd/updater/command_with_bind_zone.rb index 0419216..ad5e00c 100644 --- a/lib/dyndnsd/updater/command_with_bind_zone.rb +++ b/lib/dyndnsd/updater/command_with_bind_zone.rb @@ -18,7 +18,7 @@ module Dyndnsd return if !db.changed? Helper.span('updater_update') do |span| - span.set_tag('dyndnsd.updater.name', self.class.name&.split('::')&.last || 'None') + span.set_attribute('dyndnsd.updater.name', self.class.name&.split('::')&.last || 'None') # write zone file in bind syntax File.open(@zone_file, 'w') { |f| f.write(@generator.generate(db)) } diff --git a/lib/dyndnsd/updater/zone_transfer_server.rb b/lib/dyndnsd/updater/zone_transfer_server.rb index 541d82a..e04ba22 100644 --- a/lib/dyndnsd/updater/zone_transfer_server.rb +++ b/lib/dyndnsd/updater/zone_transfer_server.rb @@ -35,7 +35,7 @@ module Dyndnsd # @return [void] def update(db) Helper.span('updater_update') do |span| - span.set_tag('dyndnsd.updater.name', self.class.name&.split('::')&.last || 'None') + span.set_attribute('dyndnsd.updater.name', self.class.name&.split('::')&.last || 'None') soa_rr = Resolv::DNS::Resource::IN::SOA.new( @zone_nameservers[0], @zone_email_address, diff --git a/spec/dyndnsd/daemon_spec.rb b/spec/dyndnsd/daemon_spec.rb index 1936805..6164c09 100644 --- a/spec/dyndnsd/daemon_spec.rb +++ b/spec/dyndnsd/daemon_spec.rb @@ -24,9 +24,7 @@ describe Dyndnsd::Daemon do app = Rack::Auth::Basic.new(daemon, 'DynDNS', &daemon.method(:authorized?)) - app = Dyndnsd::Responder::DynDNSStyle.new(app) - - Rack::Tracer.new(app, trust_incoming_span: false) + Dyndnsd::Responder::DynDNSStyle.new(app) end it 'requires authentication' do