diff --git a/Gemfile b/Gemfile index 8da3e4fb..51ac033b 100644 --- a/Gemfile +++ b/Gemfile @@ -15,3 +15,8 @@ gem 'simplecov', '~> 0.21.2' # instana.gemspec gemspec + +gem "opentelemetry-api", "~> 1.4" + +gem "rubocop", "~> 1.71" +gem "opentelemetry-common", "~> 0.22.0" diff --git a/Rakefile b/Rakefile index 67449339..cabd8d01 100644 --- a/Rakefile +++ b/Rakefile @@ -22,7 +22,7 @@ Rake::TestTask.new(:test) do |t| else t.test_files = Dir[ 'test/*_test.rb', - 'test/{agent,tracing,backend,snapshot}/*_test.rb' + 'test/{agent,trace,backend,snapshot}/*_test.rb' ] end end diff --git a/examples/opentracing.rb b/examples/otel.rb similarity index 92% rename from examples/opentracing.rb rename to examples/otel.rb index ee8b5138..96d674bb 100644 --- a/examples/opentracing.rb +++ b/examples/otel.rb @@ -17,7 +17,7 @@ entry_span.set_tag(:'span.kind', "entry") intermediate_span = OpenTracing.start_span("myintermediate", :child_of => entry_span) -intermediate_span.finish() +intermediate_span.finish db_span = OpenTracing.start_span('mydbspan', :child_of => entry_span) db_span.set_tag(:'db.instance', "users") @@ -25,11 +25,11 @@ db_span.set_tag(:'db.type', "mysql") db_span.set_tag(:'db.user', "mysql_login") db_span.set_tag(:'span.kind', "exit") -db_span.finish() +db_span.finish intermediate_span = OpenTracing.start_span("myintermediate", :child_of => entry_span) intermediate_span.log("ALLOK", :message => "All seems ok") -intermediate_span.finish() +intermediate_span.finish entry_span.set_tag(:'http.status_code', 200) -entry_span.finish() +entry_span.finish diff --git a/examples/tracing.rb b/examples/tracing.rb index 22b61d12..cbf6f3c8 100644 --- a/examples/tracing.rb +++ b/examples/tracing.rb @@ -1,5 +1,6 @@ # This file outlines the Instana Ruby Tracing API. # +# Replace the below line once otel based standards are implemended # This same tracer also supports OpenTracing. See `opentracing.rb` for # separate documentation. diff --git a/instana.gemspec b/instana.gemspec index 482ffd1e..315eafe9 100644 --- a/instana.gemspec +++ b/instana.gemspec @@ -47,5 +47,7 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency('concurrent-ruby', '>= 1.1') spec.add_runtime_dependency('csv', '>= 0.1') spec.add_runtime_dependency('sys-proctable', '>= 1.2.2') + spec.add_runtime_dependency('opentelemetry-api', '~> 1.4') + spec.add_runtime_dependency('opentelemetry-common') spec.add_runtime_dependency('oj', '>=3.0.11') unless RUBY_PLATFORM =~ /java/i end diff --git a/lib/instana/base.rb b/lib/instana/base.rb index 82b4b66a..983ed97b 100644 --- a/lib/instana/base.rb +++ b/lib/instana/base.rb @@ -14,6 +14,7 @@ class << self attr_accessor :pid attr_reader :secrets attr_reader :serverless + attr_accessor :tracer_provider ## # setup @@ -22,8 +23,9 @@ class << self # to run" state. # def setup - @agent = ::Instana::Backend::Agent.new - @tracer = ::Instana::Tracer.new + @agent = ::Instana::Backend::Agent.new + @tracer_provider = ::Instana::Trace::TracerProvider.new + @tracer = @tracer_provider.tracer('instana_tracer') @processor = ::Instana::Processor.new @secrets = ::Instana::Secrets.new @serverless = ::Instana::Serverless.new diff --git a/lib/instana/instrumentation/action_cable.rb b/lib/instana/instrumentation/action_cable.rb index e307abd5..2cd04d98 100644 --- a/lib/instana/instrumentation/action_cable.rb +++ b/lib/instana/instrumentation/action_cable.rb @@ -27,8 +27,10 @@ def transmit(data, via: nil) } context = connection.instana_trace_context - ::Instana.tracer.start_or_continue_trace(:'rpc-server', rpc_tags, context) do - super(data, via: via) + Trace.with_span(OpenTelemetry::Trace.non_recording_span(context)) do + ::Instana.tracer.in_span(:'rpc-server', attributes: rpc_tags) do + super(data, via: via) + end end end @@ -44,8 +46,10 @@ def dispatch_action(action, data) } context = connection.instana_trace_context - ::Instana.tracer.start_or_continue_trace(:'rpc-server', rpc_tags, context) do - super(action, data) + Trace.with_span(OpenTelemetry::Trace.non_recording_span(context)) do + ::Instana.tracer.in_span(:'rpc-server', attributes: rpc_tags) do + super(action, data) + end end end end diff --git a/lib/instana/instrumentation/action_controller.rb b/lib/instana/instrumentation/action_controller.rb index 477f3667..082433b8 100644 --- a/lib/instana/instrumentation/action_controller.rb +++ b/lib/instana/instrumentation/action_controller.rb @@ -11,9 +11,8 @@ def process_action(*args) action: action_name } } - request.env['INSTANA_HTTP_PATH_TEMPLATE'] = matched_path_template - ::Instana::Tracer.trace(:actioncontroller, call_payload) { super(*args) } + ::Instana.tracer.in_span(:actioncontroller, attributes: call_payload) { super(*args) } end def render(*args, &block) @@ -22,8 +21,7 @@ def render(*args, &block) name: describe_render_options(args.first) || 'Default' } } - - ::Instana::Tracer.trace(:actionview, call_payload) { super(*args, &block) } + ::Instana.tracer.in_span(:actionview, attributes: call_payload) { super(*args, &block) } end private diff --git a/lib/instana/instrumentation/action_mailer.rb b/lib/instana/instrumentation/action_mailer.rb index dddab685..f8a67197 100644 --- a/lib/instana/instrumentation/action_mailer.rb +++ b/lib/instana/instrumentation/action_mailer.rb @@ -12,7 +12,7 @@ def method_missing(method_name, *args) # rubocop:disable Style/MissingRespondToM method: method_name.to_s } } - Instana::Tracer.trace(:'mail.actionmailer', tags) { super } + Instana.tracer.in_span(:'mail.actionmailer', attributes: tags) { super } else super end diff --git a/lib/instana/instrumentation/action_view.rb b/lib/instana/instrumentation/action_view.rb index 9f9b889a..ba5822a3 100644 --- a/lib/instana/instrumentation/action_view.rb +++ b/lib/instana/instrumentation/action_view.rb @@ -13,7 +13,7 @@ def render_partial(*args) } } - ::Instana::Tracer.trace(:render, call_payload) { super(*args) } + ::Instana.tracer.in_span(:render, attributes: call_payload) { super(*args) } end def render_collection(*args) @@ -24,7 +24,7 @@ def render_collection(*args) } } - ::Instana::Tracer.trace(:render, call_payload) { super(*args) } + ::Instana.tracer.in_span(:render, attributes: call_payload) { super(*args) } end def render_partial_template(*args) @@ -35,7 +35,7 @@ def render_partial_template(*args) } } - ::Instana::Tracer.trace(:render, call_payload) { super(*args) } + ::Instana.tracer.in_span(:render, attributes: call_payload) { super(*args) } end end @@ -48,7 +48,7 @@ def render_collection(*args) } } - ::Instana::Tracer.trace(:render, call_payload) { super(*args) } + ::Instana.tracer.in_span(:render, attributes: call_payload) { super(*args) } end end end diff --git a/lib/instana/instrumentation/active_job.rb b/lib/instana/instrumentation/active_job.rb index 059805e9..40f6bc70 100644 --- a/lib/instana/instrumentation/active_job.rb +++ b/lib/instana/instrumentation/active_job.rb @@ -1,5 +1,19 @@ # (c) Copyright IBM Corp. 2021 # (c) Copyright Instana Inc. 2021 +module OpenTelemetry + module Trace + module Propagation + module TraceContext + # A TraceParent is an implementation of the W3C trace context specification + # https://www.w3.org/TR/trace-context/ + # {Trace::SpanContext} + class TraceParent + REGEXP = /^(?[A-Fa-f0-9]{2})-(?[A-Fa-f0-9]{32})-(?[A-Fa-f0-9]{32})-(?[A-Fa-f0-9]{2})(?-.*)?$/ + end + end + end + end +end module Instana module Instrumentation @@ -15,7 +29,7 @@ def self.prepended(target) } } - ::Instana::Tracer.trace(:activejob, tags) do + ::Instana.tracer.in_span(:activejob, attributes: tags) do context = ::Instana.tracer.context job.arguments.append({ instana_context: context ? context.to_hash : nil @@ -37,11 +51,12 @@ def self.prepended(target) incoming_context = if job.arguments.is_a?(Array) && job.arguments.last.is_a?(Hash) && job.arguments.last.key?(:instana_context) instana_context = job.arguments.last[:instana_context] job.arguments.pop - instana_context ? ::Instana::SpanContext.new(instana_context[:trace_id], instana_context[:span_id]) : nil + instana_context ? ::Instana::SpanContext.new(trace_id: instana_context[:trace_id], span_id: instana_context[:span_id]) : nil end - - ::Instana::Tracer.start_or_continue_trace(:activejob, tags, incoming_context) do - block.call + Trace.with_span(OpenTelemetry::Trace.non_recording_span(incoming_context)) do + ::Instana.tracer.in_span(:activejob, attributes: tags) do + block.call + end end end end diff --git a/lib/instana/instrumentation/active_record.rb b/lib/instana/instrumentation/active_record.rb index 77fb6428..b15de232 100644 --- a/lib/instana/instrumentation/active_record.rb +++ b/lib/instana/instrumentation/active_record.rb @@ -35,7 +35,7 @@ def maybe_sanitize(sql) def maybe_trace(call_payload, name, &blk) if ::Instana.tracer.tracing? && !ignored?(call_payload, name) - ::Instana.tracer.trace(:activerecord, call_payload, &blk) + ::Instana.tracer.in_span(:activerecord, attributes: call_payload, &blk) else yield end diff --git a/lib/instana/instrumentation/aws_sdk_dynamodb.rb b/lib/instana/instrumentation/aws_sdk_dynamodb.rb index 7ac23750..2654dd25 100644 --- a/lib/instana/instrumentation/aws_sdk_dynamodb.rb +++ b/lib/instana/instrumentation/aws_sdk_dynamodb.rb @@ -11,7 +11,7 @@ def call(context) table: table_name_from(context) } - ::Instana.tracer.trace(:dynamodb, {dynamodb: dynamo_tags}) { @handler.call(context) } + ::Instana.tracer.in_span(:dynamodb, attributes: {dynamodb: dynamo_tags}) { @handler.call(context) } end private diff --git a/lib/instana/instrumentation/aws_sdk_lambda.rb b/lib/instana/instrumentation/aws_sdk_lambda.rb index 21b681d8..63e165a9 100644 --- a/lib/instana/instrumentation/aws_sdk_lambda.rb +++ b/lib/instana/instrumentation/aws_sdk_lambda.rb @@ -25,7 +25,7 @@ def call(context) type: context.params[:invocation_type] }.reject { |_, v| v.nil? } - ::Instana.tracer.start_or_continue_trace(:"aws.lambda.invoke", {aws: {lambda: {invoke: tags}}}) do + ::Instana.tracer.in_span(:"aws.lambda.invoke", attributes: {aws: {lambda: {invoke: tags}}}) do response = @handler.call(context) if response.respond_to? :status_code ::Instana.tracer.log_info(:http => {:status => response.status_code }) diff --git a/lib/instana/instrumentation/aws_sdk_s3.rb b/lib/instana/instrumentation/aws_sdk_s3.rb index b2a7d82a..d6b3329e 100644 --- a/lib/instana/instrumentation/aws_sdk_s3.rb +++ b/lib/instana/instrumentation/aws_sdk_s3.rb @@ -12,7 +12,7 @@ def call(context) key: key_from_context(context) }.reject { |_, v| v.nil? } - ::Instana.tracer.trace(:s3, {s3: s3_tags}) { @handler.call(context) } + ::Instana.tracer.in_span(:s3, attributes: {s3: s3_tags}) { @handler.call(context) } end private diff --git a/lib/instana/instrumentation/aws_sdk_sns.rb b/lib/instana/instrumentation/aws_sdk_sns.rb index dcd89e83..369210d7 100644 --- a/lib/instana/instrumentation/aws_sdk_sns.rb +++ b/lib/instana/instrumentation/aws_sdk_sns.rb @@ -14,7 +14,7 @@ def call(context) }.reject { |_, v| v.nil? } if context.operation_name == :publish - ::Instana.tracer.trace(:sns, {sns: sns_tags}) { @handler.call(context) } + ::Instana.tracer.in_span(:sns, attributes: {sns: sns_tags}) { @handler.call(context) } else @handler.call(context) end diff --git a/lib/instana/instrumentation/aws_sdk_sqs.rb b/lib/instana/instrumentation/aws_sdk_sqs.rb index ced89f9f..855d9071 100644 --- a/lib/instana/instrumentation/aws_sdk_sqs.rb +++ b/lib/instana/instrumentation/aws_sdk_sqs.rb @@ -15,7 +15,7 @@ def call(context) span_tags = tags_for(context.operation_name, context.params).reject { |_, v| v.nil? } - ::Instana.tracer.trace(:sqs, {sqs: span_tags}) do |span| + ::Instana.tracer.in_span(:sqs, attributes: {sqs: span_tags}) do |span| case context.operation_name when :send_message inject_instana_headers(span, context.params) diff --git a/lib/instana/instrumentation/dalli.rb b/lib/instana/instrumentation/dalli.rb index b7275ebd..78bc07f7 100644 --- a/lib/instana/instrumentation/dalli.rb +++ b/lib/instana/instrumentation/dalli.rb @@ -17,7 +17,7 @@ def perform(*args, &blk) entry_payload[:memcache][:command] = op entry_payload[:memcache][:key] = key - ::Instana.tracer.log_entry(:memcache, entry_payload) + ::Instana.tracer.start_span(:memcache, attributes: entry_payload) exit_payload = { :memcache => {} } result = super(*args, &blk) diff --git a/lib/instana/instrumentation/excon.rb b/lib/instana/instrumentation/excon.rb index 2c76dc7b..fbe1c108 100644 --- a/lib/instana/instrumentation/excon.rb +++ b/lib/instana/instrumentation/excon.rb @@ -16,10 +16,10 @@ def request_call(datum) if datum[:pipeline] == true # Pass the context along in the datum so we get back on response # and can close out the async span - datum[:instana_span] = ::Instana.tracer.log_async_entry(:excon, payload) + datum[:instana_span] = ::Instana.tracer.start_span(:excon, attributes: payload) t_context = datum[:instana_span].context else - ::Instana.tracer.log_entry(:excon, payload) + ::Instana.tracer.start_span(:excon, attributes: payload) t_context = ::Instana.tracer.context end diff --git a/lib/instana/instrumentation/graphql.rb b/lib/instana/instrumentation/graphql.rb index 55e5b961..f875eace 100644 --- a/lib/instana/instrumentation/graphql.rb +++ b/lib/instana/instrumentation/graphql.rb @@ -35,13 +35,13 @@ def platform_trace(platform_key, key, data) } begin - ::Instana.tracer.log_entry(:'graphql.server') + span = ::Instana.tracer.start_span(:'graphql.server', attributes: {graphql: payload}) yield rescue Exception => e - ::Instana.tracer.log_error(e) + span.record_exception(e) raise e ensure - ::Instana.tracer.log_exit(:'graphql.server', {graphql: payload}) + span.finish end end diff --git a/lib/instana/instrumentation/grpc.rb b/lib/instana/instrumentation/grpc.rb index c153c6b1..647ab823 100644 --- a/lib/instana/instrumentation/grpc.rb +++ b/lib/instana/instrumentation/grpc.rb @@ -22,7 +22,7 @@ module GRPCCientInstrumentation kvs[:rpc][:call] = method kvs[:rpc][:call_type] = call_type - ::Instana.tracer.log_entry(:'rpc-client', kvs) + current_span = ::Instana.tracer.start_span(:'rpc-client', attributes: kvs) context = ::Instana.tracer.context if context @@ -35,11 +35,11 @@ module GRPCCientInstrumentation super(method, *others, **options) rescue => e kvs[:rpc][:error] = true - ::Instana.tracer.log_info(kvs) - ::Instana.tracer.log_error(e) + current_span.set_tags(kvs) + current_span.record_exception(e) raise ensure - ::Instana.tracer.log_exit(:'rpc-client', {}) + current_span.finish end end end @@ -60,9 +60,9 @@ module GRPCServerInstrumentation incoming_context = {} if metadata.key?('x-instana-t') - incoming_context[:trace_id] = ::Instana::Util.header_to_id(metadata['x-instana-t']) - incoming_context[:span_id] = ::Instana::Util.header_to_id(metadata['x-instana-s']) if metadata.key?('x-instana-s') - incoming_context[:level] = metadata['x-instana-l'] if metadata.key?('x-instana-l') + incoming_context = SpanContext.new(trace_id: ::Instana::Util.header_to_id(metadata['x-instana-t']), + span_id: metadata.key?('x-instana-s') ? ::Instana::Util.header_to_id(metadata['x-instana-s']) : nil, + level: metadata.key?('x-instana-l') ? metadata['x-instana-l'] : nil) end kvs[:rpc][:flavor] = :grpc @@ -71,18 +71,19 @@ module GRPCServerInstrumentation kvs[:rpc][:call_type] = call_type kvs[:rpc][:peer] = { address: active_call.peer } - ::Instana.tracer.log_start_or_continue( - :'rpc-server', kvs, incoming_context - ) + span = OpenTelemetry::Trace.non_recording_span(incoming_context) if incoming_context + parent_context = Trace.context_with_span(span) if incoming_context + + current_span = ::Instana.tracer.start_span(:'rpc-server', attributes: kvs, with_parent: parent_context) super(active_call, mth, *others) rescue => e kvs[:rpc][:error] = true - ::Instana.tracer.log_info(kvs) - ::Instana.tracer.log_error(e) + current_span.set_tags(kvs) + current_span.record_exception(e) raise ensure - ::Instana.tracer.log_end(:'rpc-server', {}) if ::Instana.tracer.tracing? + current_span.finish if ::Instana.tracer.tracing? end end end diff --git a/lib/instana/instrumentation/mongo.rb b/lib/instana/instrumentation/mongo.rb index d04daa5d..39644e1e 100644 --- a/lib/instana/instrumentation/mongo.rb +++ b/lib/instana/instrumentation/mongo.rb @@ -20,19 +20,19 @@ def started(event) json: filter_statement(event.command) } - @requests[event.request_id] = ::Instana.tracer.log_async_entry(:mongo, {mongo: tags}) + @requests[event.request_id] = ::Instana.tracer.start_span(:mongo, attributes: {mongo: tags}) end def failed(event) span = @requests.delete(event.request_id) span.add_error(Exception.new(event.message)) - ::Instana.tracer.log_async_exit(:mongo, {}, span) + span.finish end def succeeded(event) span = @requests.delete(event.request_id) - ::Instana.tracer.log_async_exit(:mongo, {}, span) + span.finish end private diff --git a/lib/instana/instrumentation/net-http.rb b/lib/instana/instrumentation/net-http.rb index 74de113d..4a07b252 100644 --- a/lib/instana/instrumentation/net-http.rb +++ b/lib/instana/instrumentation/net-http.rb @@ -12,7 +12,7 @@ def request(*args, &block) return super(*args, &block) end - ::Instana.tracer.log_entry(:'net-http') + current_span = ::Instana.tracer.start_span(:'net-http') # Send out the tracing context with the request request = args[0] @@ -54,15 +54,16 @@ def request(*args, &block) if response.code.to_i >= 500 # Because of the 5xx response, we flag this span as errored but # without a backtrace (no exception) - ::Instana.tracer.log_error(nil) + current_span.record_exception(nil) end response rescue => e - ::Instana.tracer.log_error(e) + current_span&.record_exception(e) raise ensure - ::Instana.tracer.log_exit(:'net-http', kv_payload) unless do_skip + current_span&.set_tags(kv_payload) + current_span&.finish unless do_skip end def skip_instrumentation? diff --git a/lib/instana/instrumentation/rack.rb b/lib/instana/instrumentation/rack.rb index 00ad2a91..40c8c3eb 100644 --- a/lib/instana/instrumentation/rack.rb +++ b/lib/instana/instrumentation/rack.rb @@ -10,14 +10,20 @@ def initialize(app) @app = app end - def call(env) + def call(env) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength req = InstrumentedRequest.new(env) kvs = { http: req.request_tags }.reject { |_, v| v.nil? } - current_span = ::Instana.tracer.log_start_or_continue(:rack, {}, req.incoming_context) + parent_context = extract_trace_context(req.incoming_context) + span = OpenTelemetry::Trace.non_recording_span(parent_context) if parent_context + parent_context = Trace.context_with_span(span) if parent_context + + current_span = ::Instana.tracer.start_span(:rack, attributes: {}, with_parent: parent_context) + trace_ctx = OpenTelemetry::Trace.context_with_span(current_span) + @trace_token = OpenTelemetry::Context.attach(trace_ctx) status, headers, response = @app.call(env) if ::Instana.tracer.tracing? @@ -66,7 +72,7 @@ def call(env) [status, headers, response] rescue Exception => e - ::Instana.tracer.log_error(e) if ::Instana.tracer.tracing? + current_span.record_exception(e) if ::Instana.tracer.tracing? raise ensure if ::Instana.tracer.tracing? @@ -85,9 +91,35 @@ def call(env) headers['Traceparent'] = trace_context.trace_parent_header headers['Server-Timing'] = "intid;desc=#{trace_context.trace_id_header}" end + current_span.set_tags(kvs) + OpenTelemetry::Context.detach(@trace_token) if @trace_token + current_span.finish + end + end + + private - ::Instana.tracer.log_end(:rack, kvs) + def extract_trace_context(incoming_context) + return nil unless incoming_context + + parent_context = nil + + if incoming_context.is_a?(Hash) + unless incoming_context.empty? + parent_context = SpanContext.new( + trace_id: incoming_context[:trace_id], + span_id: incoming_context[:span_id], + level: incoming_context[:level], + baggage: { + external_trace_id: incoming_context[:external_trace_id], + external_state: incoming_context[:external_state] + } + ) + end + elsif incoming_context.is_a?(SpanContext) + parent_context = incoming_context end + parent_context end end end diff --git a/lib/instana/instrumentation/redis.rb b/lib/instana/instrumentation/redis.rb index c8f24741..426b82ab 100644 --- a/lib/instana/instrumentation/redis.rb +++ b/lib/instana/instrumentation/redis.rb @@ -66,7 +66,7 @@ def call_with_instana(command, original_super, args, kwargs, &block) kv_payload = { redis: {} } begin - ::Instana.tracer.log_entry(:redis) + ::Instana.tracer.start_span(:redis) begin kv_payload[:redis][:connection] = "#{self.host}:#{self.port}" diff --git a/lib/instana/instrumentation/resque.rb b/lib/instana/instrumentation/resque.rb index 0015dc86..68a7e0ec 100644 --- a/lib/instana/instrumentation/resque.rb +++ b/lib/instana/instrumentation/resque.rb @@ -27,7 +27,7 @@ def enqueue(klass, *args) if Instana.tracer.tracing? kvs = collect_kvs(:enqueue, klass, args) - Instana.tracer.trace(:'resque-client', kvs) do + Instana.tracer.in_span(:'resque-client', attributes: kvs) do args.push(::Instana.tracer.context.to_hash) if ::Instana.config[:'resque-client'][:propagate] super(klass, *args) end @@ -41,7 +41,7 @@ def enqueue_to(queue, klass, *args) kvs = collect_kvs(:enqueue_to, klass, args) kvs[:Queue] = queue.to_s if queue - Instana.tracer.trace(:'resque-client', kvs) do + Instana.tracer.in_span(:'resque-client', attributes: kvs) do args.push(::Instana.tracer.context.to_hash) if ::Instana.config[:'resque-client'][:propagate] super(queue, klass, *args) end @@ -54,7 +54,7 @@ def dequeue(klass, *args) if Instana.tracer.tracing? kvs = collect_kvs(:dequeue, klass, args) - Instana.tracer.trace(:'resque-client', kvs) do + Instana.tracer.in_span(:'resque-client', attributes: kvs) do super(klass, *args) end else @@ -78,13 +78,15 @@ def perform(job) trace_context = if ::Instana.config[:'resque-client'][:propagate] && job.payload['args'][-1].is_a?(Hash) && job.payload['args'][-1].keys.include?('trace_id') context_from_wire = job.payload['args'].pop ::Instana::SpanContext.new( - context_from_wire['trace_id'], - context_from_wire['span_id'] + trace_id: context_from_wire['trace_id'], + span_id: context_from_wire['span_id'] ) end - - Instana.tracer.start_or_continue_trace(:'resque-worker', kvs, trace_context) do - super(job) + span = OpenTelemetry::Trace.non_recording_span(trace_context) if trace_context + Trace.with_span(span) do + Instana.tracer.in_span(:'resque-worker', attributes: kvs) do |span| + super(job) + end end end end diff --git a/lib/instana/instrumentation/rest-client.rb b/lib/instana/instrumentation/rest-client.rb index 7e726b08..545b4c7c 100644 --- a/lib/instana/instrumentation/rest-client.rb +++ b/lib/instana/instrumentation/rest-client.rb @@ -8,14 +8,14 @@ def execute(&block) # Since RestClient uses net/http under the covers, we just # provide span visibility here. HTTP related KVs are reported # in the Net::HTTP instrumentation - ::Instana.tracer.log_entry(:'rest-client') + span = ::Instana.tracer.start_span(:'rest-client', with_parent: OpenTelemetry::Context.current) - super(&block) + Trace.with_span(span) { super(&block) } rescue => e - ::Instana.tracer.log_error(e) + span.record_exception(e) raise ensure - ::Instana.tracer.log_exit(:'rest-client') + span.finish end end end diff --git a/lib/instana/instrumentation/sequel.rb b/lib/instana/instrumentation/sequel.rb index 03273024..668fd75c 100644 --- a/lib/instana/instrumentation/sequel.rb +++ b/lib/instana/instrumentation/sequel.rb @@ -3,7 +3,7 @@ module Instana module Instrumentation module Sequel - IGNORED_SQL = %w[BEGIN COMMIT SET].freeze + IGNORED_SQL = %w[BEGIN COMMIT SET PRAGMA].freeze VERSION_SELECT_STATEMENT = "SELECT VERSION()".freeze SANITIZE_REGEXP = /('[\s\S][^']*'|\d*\.\d+|\d+|NULL)/i @@ -26,9 +26,9 @@ def maybe_sanitize(sql) ::Instana.config[:sanitize_sql] ? sql.gsub(SANITIZE_REGEXP, '?') : sql end - def maybe_trace(call_payload, &blk) + def maybe_trace(call_payload, &block) if ::Instana.tracer.tracing? && !ignored?(call_payload) - ::Instana.tracer.trace(:sequel, call_payload, &blk) + ::Instana.tracer.in_span(:sequel, attributes: call_payload, &block) else yield end diff --git a/lib/instana/instrumentation/shoryuken.rb b/lib/instana/instrumentation/shoryuken.rb index 067603d9..99598bf4 100644 --- a/lib/instana/instrumentation/shoryuken.rb +++ b/lib/instana/instrumentation/shoryuken.rb @@ -15,7 +15,10 @@ def call(_worker_instance, _queue, sqs_message, _body, &block) } context = incomming_context_from(sqs_message.message_attributes) - ::Instana.tracer.start_or_continue_trace(:sqs, {sqs: sqs_tags}, context, &block) + instana_context = Instana::SpanContext.new(trace_id: context[:trace_id], span_id: context[:span_id], level: context[:level]) + Trace.with_span(OpenTelemetry::Trace.non_recording_span(instana_context)) do + ::Instana.tracer.in_span(:sqs, attributes: {sqs: sqs_tags}, &block) + end end private diff --git a/lib/instana/instrumentation/sidekiq-client.rb b/lib/instana/instrumentation/sidekiq-client.rb index 8b86ce0c..e59c063a 100644 --- a/lib/instana/instrumentation/sidekiq-client.rb +++ b/lib/instana/instrumentation/sidekiq-client.rb @@ -5,11 +5,10 @@ module Instana module Instrumentation class SidekiqClient def call(worker_class, msg, queue, _redis_pool) - kv_payload = { :'sidekiq-client' => {} } - kv_payload[:'sidekiq-client'][:queue] = queue - kv_payload[:'sidekiq-client'][:job] = worker_class.to_s - kv_payload[:'sidekiq-client'][:retry] = msg['retry'].to_s - ::Instana.tracer.log_entry(:'sidekiq-client', kv_payload) + kvs = { :'sidekiq-client' => {} } + kvs[:'sidekiq-client'][:queue] = queue + kvs[:'sidekiq-client'][:job] = worker_class.to_s + kvs[:'sidekiq-client'][:retry] = msg['retry'].to_s # Temporary until we move connection collection to redis # instrumentation @@ -24,24 +23,27 @@ def call(worker_class, msg, queue, _redis_pool) else # Unexpected version, continue without recording any redis-url break end - kv_payload[:'sidekiq-client'][:'redis-url'] = "#{host}:#{port}" + kvs[:'sidekiq-client'][:'redis-url'] = "#{host}:#{port}" end - context = ::Instana.tracer.context - if context - msg['X-Instana-T'] = context.trace_id_header - msg['X-Instana-S'] = context.span_id_header - end + Instana.tracer.in_span(:'sidekiq-client', attributes: kvs) do |span| + context = ::Instana.tracer.context + if context + msg['X-Instana-T'] = context.trace_id_header + msg['X-Instana-S'] = context.span_id_header + end + + result = yield - result = yield + if result && result['jid'] + span.set_tag(:'sidekiq-client', { job_id: result['jid'] }) + end - kv_payload[:'sidekiq-client'][:job_id] = result['jid'] - result - rescue => e - ::Instana.tracer.log_error(e) - raise - ensure - ::Instana.tracer.log_exit(:'sidekiq-client', kv_payload) + result + rescue => e + span.record_exception(e) + raise + end end end end diff --git a/lib/instana/instrumentation/sidekiq-worker.rb b/lib/instana/instrumentation/sidekiq-worker.rb index 721ed4a6..19ad68ac 100644 --- a/lib/instana/instrumentation/sidekiq-worker.rb +++ b/lib/instana/instrumentation/sidekiq-worker.rb @@ -5,11 +5,11 @@ module Instana module Instrumentation class SidekiqWorker def call(_worker, msg, _queue) - kv_payload = { :'sidekiq-worker' => {} } - kv_payload[:'sidekiq-worker'][:job_id] = msg['jid'] - kv_payload[:'sidekiq-worker'][:queue] = msg['queue'] - kv_payload[:'sidekiq-worker'][:job] = msg['class'].to_s - kv_payload[:'sidekiq-worker'][:retry] = msg['retry'].to_s + kvs = { :'sidekiq-worker' => {} } + kvs[:'sidekiq-worker'][:job_id] = msg['jid'] + kvs[:'sidekiq-worker'][:queue] = msg['queue'] + kvs[:'sidekiq-worker'][:job] = msg['class'].to_s + kvs[:'sidekiq-worker'][:retry] = msg['retry'].to_s # Temporary until we move connection collection to redis # instrumentation @@ -24,29 +24,30 @@ def call(_worker, msg, _queue) else # Unexpected version, continue without recording any redis-url break end - kv_payload[:'sidekiq-worker'][:'redis-url'] = "#{host}:#{port}" + kvs[:'sidekiq-worker'][:'redis-url'] = "#{host}:#{port}" end - context = {} + trace_context = nil if msg.key?('X-Instana-T') trace_id = msg.delete('X-Instana-T') span_id = msg.delete('X-Instana-S') - context[:trace_id] = ::Instana::Util.header_to_id(trace_id) - context[:span_id] = ::Instana::Util.header_to_id(span_id) if span_id + trace_context = ::Instana::SpanContext.new( + trace_id: ::Instana::Util.header_to_id(trace_id), + span_id: span_id ? ::Instana::Util.header_to_id(span_id) : nil + ) end - ::Instana.tracer.log_start_or_continue( - :'sidekiq-worker', kv_payload, context - ) - - yield - rescue => e - kv_payload[:'sidekiq-worker'][:error] = true - ::Instana.tracer.log_info(kv_payload) - ::Instana.tracer.log_error(e) - raise - ensure - ::Instana.tracer.log_end(:'sidekiq-worker', {}) if ::Instana.tracer.tracing? + parent_non_recording_span = OpenTelemetry::Trace.non_recording_span(trace_context) if trace_context + Trace.with_span(parent_non_recording_span) do + Instana.tracer.in_span(:'sidekiq-worker', attributes: kvs) do |span| + yield + rescue => e + kvs[:'sidekiq-worker'][:error] = true + span.set_tags(kvs) + span.record_exception(e) + raise + end + end end end end diff --git a/lib/instana/instrumented_logger.rb b/lib/instana/instrumented_logger.rb index e41b8d3f..e312c076 100644 --- a/lib/instana/instrumented_logger.rb +++ b/lib/instana/instrumented_logger.rb @@ -17,7 +17,7 @@ def add(severity, message = nil, progname = nil) level: LEVEL_LABELS[severity], message: "#{message} #{progname}".strip } - Instana::Tracer.trace(:log, {log: tags}) {} + Instana.tracer.in_span(:log, attributes: {log: tags}) {} end super(severity, message, progname) diff --git a/lib/instana/open_tracing/carrier.rb b/lib/instana/open_tracing/carrier.rb deleted file mode 100644 index 0a181962..00000000 --- a/lib/instana/open_tracing/carrier.rb +++ /dev/null @@ -1,7 +0,0 @@ -# (c) Copyright IBM Corp. 2021 -# (c) Copyright Instana Inc. 2017 - -module OpenTracing - class Carrier < Hash - end -end diff --git a/lib/instana/open_tracing/instana_tracer.rb b/lib/instana/open_tracing/instana_tracer.rb deleted file mode 100644 index adad80d2..00000000 --- a/lib/instana/open_tracing/instana_tracer.rb +++ /dev/null @@ -1,99 +0,0 @@ -# (c) Copyright IBM Corp. 2021 -# (c) Copyright Instana Inc. 2021 - -require 'delegate' -# :nocov: -module OpenTracing - class InstanaTracer < SimpleDelegator - Span = ::Instana::Span - - # Start a new span - # - # @param operation_name [String] The name of the operation represented by the span - # @param child_of [Span] A span to be used as the ChildOf reference - # @param start_time [Time] the start time of the span - # @param tags [Hash] Starting tags for the span - # - # @return [Span] - # - def start_span(operation_name, child_of: nil, start_time: ::Instana::Util.now_in_ms, tags: nil) - new_span = if child_of && (child_of.is_a?(::Instana::Span) || child_of.is_a?(::Instana::SpanContext)) - Span.new(operation_name, parent_ctx: child_of, start_time: start_time) - else - Span.new(operation_name, start_time: start_time) - end - new_span.set_tags(tags) if tags - new_span - end - - # Start a new span which is the child of the current span - # - # @param operation_name [String] The name of the operation represented by the span - # @param child_of [Span] A span to be used as the ChildOf reference - # @param start_time [Time] the start time of the span - # @param tags [Hash] Starting tags for the span - # - # @return [Span] - # - def start_active_span(operation_name, child_of: active_span, start_time: ::Instana::Util.now_in_ms, tags: nil) - ::Instana.tracer.current_span = start_span(operation_name, child_of: child_of, start_time: start_time, tags: tags) - block_given? ? yield(::Instana.tracer.current_span) : ::Instana.tracer.current_span - end - - # Returns the currently active span - # - # @return [Span] - # - def active_span - ::Instana.tracer.current_span - end - - # Inject a span into the given carrier - # - # @param span_context [SpanContext] - # @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK] - # @param carrier [Carrier] - # - def inject(span_context, format, carrier) - case format - when OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY - ::Instana.logger.debug 'Unsupported inject format' - when OpenTracing::FORMAT_RACK - carrier['X-Instana-T'] = ::Instana::Util.id_to_header(span_context.trace_id) - carrier['X-Instana-S'] = ::Instana::Util.id_to_header(span_context.span_id) - else - ::Instana.logger.debug 'Unknown inject format' - end - end - - # Extract a span from a carrier - # - # @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK] - # @param carrier [Carrier] - # - # @return [SpanContext] - # - def extract(format, carrier) - case format - when OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY - ::Instana.logger.debug 'Unsupported extract format' - when OpenTracing::FORMAT_RACK - ::Instana::SpanContext.new(::Instana::Util.header_to_id(carrier['HTTP_X_INSTANA_T']), - ::Instana::Util.header_to_id(carrier['HTTP_X_INSTANA_S'])) - else - ::Instana.logger.debug 'Unknown inject format' - nil - end - end - - def method_missing(method, *args, &block) - ::Instana.logger.warn { "You are invoking `#{m}` on Instana::Tracer via OpenTracing." } - super(method, *args, &block) - end - - def respond_to_missing?(*) - super(method) - end - end -end -# :nocov: diff --git a/lib/instana/samplers/result.rb b/lib/instana/samplers/result.rb new file mode 100644 index 00000000..f753d0d6 --- /dev/null +++ b/lib/instana/samplers/result.rb @@ -0,0 +1,32 @@ +# (c) Copyright IBM Corp. 2025 + +module Instana + module Trace + module Samplers + class Result + EMPTY_HASH = {}.freeze + attr_reader :tracestate, :attributes + + def initialize(decision:, tracestate:, attributes: nil) + @decision = decision + @attributes = attributes.freeze || EMPTY_HASH + @tracestate = tracestate + end + + # Returns true if this span should be sampled. + # + # @return FALSE always + def sampled? + false + end + + # Returns true if this span should record events, attributes, status, etc. + # + # returns TRUE always + def recording? + true + end + end + end + end +end diff --git a/lib/instana/samplers/samplers.rb b/lib/instana/samplers/samplers.rb new file mode 100644 index 00000000..1642e7b9 --- /dev/null +++ b/lib/instana/samplers/samplers.rb @@ -0,0 +1,76 @@ +# (c) Copyright IBM Corp. 2025 +require 'instana/samplers/result' +module Instana + module Trace + # The Samplers module contains the sampling logic for OpenTelemetry. The + # reference implementation provides a {TraceIdRatioBased}, {ALWAYS_ON}, + # {ALWAYS_OFF}, and {ParentBased}. + # + # Custom samplers can be provided by SDK users. The required interface is: + # + # should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:) -> Result + # description -> String + # + # Where: + # + # @param [String] trace_id The trace_id of the {Span} to be created. + # @param [OpenTelemetry::Context] parent_context The + # {OpenTelemetry::Context} with a parent {Span}. The {Span}'s + # {OpenTelemetry::Trace::SpanContext} may be invalid to indicate a + # root span. + # @param [Enumerable] links A collection of links to be associated + # with the {Span} to be created. Can be nil. + # @param [String] name Name of the {Span} to be created. + # @param [Symbol] kind The {OpenTelemetry::Trace::SpanKind} of the {Span} + # to be created. Can be nil. + # @param [Hash] attributes Attributes to be attached + # to the {Span} to be created. Can be nil. + # @return [Result] The sampling result. + module Samplers + # Returns a {Result} with {Decision::RECORD_AND_SAMPLE}. + ALWAYS_ON = false + # # Returns a {Result} with {Decision::DROP}. + ALWAYS_OFF = true + + # Returns a new sampler. It delegates to samplers according to the following rules: + # + # | Parent | parent.remote? | parent.trace_flags.sampled? | Invoke sampler | + # |--|--|--|--| + # | absent | n/a | n/a | root | + # | present | true | true | remote_parent_sampled | + # | present | true | false | remote_parent_not_sampled | + # | present | false | true | local_parent_sampled | + # | present | false | false | local_parent_not_sampled | + # + # @param [Sampler] root The sampler to which the sampling + # decision is delegated for spans with no parent (root spans). + # @param [optional Sampler] remote_parent_sampled The sampler to which the sampling + # decision is delegated for remote parent sampled spans. Defaults to ALWAYS_ON. + # @param [optional Sampler] remote_parent_not_sampled The sampler to which the sampling + # decision is delegated for remote parent not sampled spans. Defaults to ALWAYS_OFF. + # @param [optional Sampler] local_parent_sampled The sampler to which the sampling + # decision is delegated for local parent sampled spans. Defaults to ALWAYS_ON. + # @param [optional Sampler] local_parent_not_sampled The sampler to which the sampling + # decision is delegated for local parent not sampld spans. Defaults to ALWAYS_OFF. + def self.parent_based(_) + self + end + + # Returns a new sampler. The ratio describes the proportion of the trace ID + # space that is sampled. + # + # @param [Numeric] ratio The desired sampling ratio. + # Must be within [0.0, 1.0]. + # @raise [ArgumentError] if ratio is out of range + def self.trace_id_ratio_based(_) + self + end + + def self.should_sample?(trace_id:, parent_context:, links:, name:, kind:, attributes:) # rubocop:disable Metrics/ParameterLists, Lint/UnusedMethodArgument: + parent_span_context = OpenTelemetry::Trace.current_span(parent_context).context + tracestate = parent_span_context&.tracestate + Result.new(decision: :__record_only__, tracestate: tracestate) + end + end + end +end diff --git a/lib/instana/serverless.rb b/lib/instana/serverless.rb index a7a47954..db26b31c 100644 --- a/lib/instana/serverless.rb +++ b/lib/instana/serverless.rb @@ -42,8 +42,10 @@ def wrap_aws(event, context, &block) else tags[:lambda] = tags[:lambda].merge(event_tags) end - - @tracer.start_or_continue_trace(:'aws.lambda.entry', tags, span_context, &block) + Trace.with_span(OpenTelemetry::Trace.non_recording_span(::Instana::SpanContext.new(trace_id: span_context[:trace_id], span_id: span_context[:span_id], + level: span_context[:level]))) do + @tracer.in_span(:'aws.lambda.entry', attributes: tags, &block) + end ensure begin @agent.send_bundle diff --git a/lib/instana/setup.rb b/lib/instana/setup.rb index ed89607d..94a827ff 100644 --- a/lib/instana/setup.rb +++ b/lib/instana/setup.rb @@ -11,8 +11,8 @@ require "instana/base" require "instana/config" require "instana/secrets" -require "instana/tracer" -require "instana/tracing/processor" +require "instana/trace/tracer" +require "instana/trace/processor" require 'instana/serverless' @@ -39,13 +39,12 @@ require 'instana/backend/host_agent' require 'instana/backend/serverless_agent' require 'instana/backend/agent' +require 'instana/trace' +require 'instana/trace/tracer_provider' ::Instana.setup ::Instana.agent.setup -# Require supported OpenTracing interfaces -require "opentracing" - # The Instana agent is now setup. The only remaining # task for a complete boot is to call # `Instana.agent.start` in the thread of your choice. diff --git a/lib/instana/trace.rb b/lib/instana/trace.rb new file mode 100644 index 00000000..1efc6df3 --- /dev/null +++ b/lib/instana/trace.rb @@ -0,0 +1,74 @@ +# (c) Copyright IBM Corp. 2025 +require 'opentelemetry/context' +module Instana + # The Trace API allows recording a set of events, triggered as a result of a + # single logical operation, consolidated across various components of an + # application. + module Trace + include OpenTelemetry::Trace + + module_function + + ID_RANGE = -2**63..2**63 - 1 + + # Generates a valid trace identifier + + def generate_trace_id(size = 1) + Array.new(size) { rand(ID_RANGE) } + .pack('q>*') + .unpack1('H*') + end + + # Generates a valid span identifier + # + def generate_span_id(size = 1) + Array.new(size) { rand(ID_RANGE) } + .pack('q>*') + .unpack1('H*') + end + + # Returns the current span from the current or provided context + # + # @param [optional Context] context The context to lookup the current + # {Span} from. Defaults to Context.current + def current_span(context = nil) + context ||= OpenTelemetry::Context.current + context.value(CURRENT_SPAN_KEY) || nil + end + + # Returns a context containing the span, derived from the optional parent + # context, or the current context if one was not provided. + # + # @param [optional Context] context The context to use as the parent for + # the returned context + def context_with_span(span, parent_context: OpenTelemetry::Context.current) + parent_context.set_value(CURRENT_SPAN_KEY, span) + end + + # Activates/deactivates the Span within the current Context, which makes the "current span" + # available implicitly. + # + # On exit, the Span that was active before calling this method will be reactivated. + # + # @param [Span] span the span to activate + # @yield [span, context] yields span and a context containing the span to the block. + def with_span(span) + OpenTelemetry::Context.with_value(CURRENT_SPAN_KEY, span) { |c, s| yield s, c } + end + + # Wraps a SpanContext with an object implementing the Span interface. This is done in order + # to expose a SpanContext as a Span in operations such as in-process Span propagation. + # + # @param [SpanContext] span_context SpanContext to be wrapped + # + # @return [Span] + def non_recording_span(span_context) + Span.new(span_context: span_context) + end + end +end + +require 'instana/trace/span_context' +require 'instana/trace/span_kind' +require 'instana/trace/span' +require 'instana/trace/tracer' diff --git a/lib/instana/trace/export.rb b/lib/instana/trace/export.rb new file mode 100644 index 00000000..00ca70d3 --- /dev/null +++ b/lib/instana/trace/export.rb @@ -0,0 +1,36 @@ +# (c) Copyright IBM Corp. 2025 + +module Instana + module Trace + # The Export module contains the built-in exporters and span processors for the OpenTelemetry + # reference implementation. + module Export + # Raised when an export fails; spans are available via :spans accessor + class ExportError < OpenTelemetry::Error + # Returns the {Span} array for this exception + # + # @return [Array] + attr_reader :spans + + # @param [Array] spans the array of spans that failed to export + def initialize(spans) + super("Unable to export #{spans.size} spans") + @spans = spans + end + end + + # Result codes for the SpanExporter#export method and the SpanProcessor#force_flush and SpanProcessor#shutdown methods. + + # The operation finished successfully. + SUCCESS = 0 + + # The operation finished with an error. + FAILURE = 1 + + # Additional result code for the SpanProcessor#force_flush and SpanProcessor#shutdown methods. + + # The operation timed out. + TIMEOUT = 2 + end + end +end diff --git a/lib/instana/tracing/processor.rb b/lib/instana/trace/processor.rb similarity index 90% rename from lib/instana/tracing/processor.rb rename to lib/instana/trace/processor.rb index 4df47c52..582a39ed 100644 --- a/lib/instana/tracing/processor.rb +++ b/lib/instana/trace/processor.rb @@ -1,7 +1,6 @@ # (c) Copyright IBM Corp. 2021 # (c) Copyright Instana Inc. 2016 -require 'thread' require 'forwardable' module Instana @@ -25,10 +24,13 @@ def initialize(logger: ::Instana.logger) @spans_closed = Concurrent::AtomicFixnum.new(0) end - # Adds a span to the span queue - # - # @param [Trace] - the trace to be added to the queue - def add_span(span) + # Note that we've started a new span. Used to + # generate monitoring metrics. + def on_start(_) + @spans_opened.increment + end + + def on_finish(span) # :nocov: if @pid != Process.pid @logger.info("Proces `#{@pid}` has forked into #{Process.pid}. Running post fork hook.") @@ -41,12 +43,6 @@ def add_span(span) @queue.push(span) end - # Note that we've started a new span. Used to - # generate monitoring metrics. - def start_span(_) - @spans_opened.increment - end - # Clears and retrieves metrics associated with span creation and submission def span_metrics response = { @@ -94,9 +90,13 @@ def queued_spans return [] if @queue.empty? spans = [] - until @queue.empty? do + until @queue.empty? # Non-blocking pop; ignore exception - span = @queue.pop(true) rescue nil + span = begin + @queue.pop(true) + rescue + nil + end spans << span.raw if span.is_a?(Span) && span.context.level == 1 end @@ -110,9 +110,13 @@ def clear! @spans_opened.value = 0 @spans_closed.value = 0 - until @queue.empty? do + until @queue.empty? # Non-blocking pop; ignore exception - @queue.pop(true) rescue nil + begin + @queue.pop(true) + rescue + nil + end end end end diff --git a/lib/instana/trace/span.rb b/lib/instana/trace/span.rb new file mode 100644 index 00000000..88f18de6 --- /dev/null +++ b/lib/instana/trace/span.rb @@ -0,0 +1,532 @@ +# (c) Copyright IBM Corp. 2025 + +require 'opentelemetry' +require 'instana/trace/span_kind' + +module Instana + class Span < OpenTelemetry::Trace::Span + include SpanKind + + attr_accessor :parent, :baggage, :is_root, :context + + def initialize(name, parent_ctx = nil, _context = nil, parent_span = nil, _kind = nil, parent_span_id = nil, _span_limits = nil, _span_processors = nil, attributes = nil, _links = nil, start_timestamp = ::Instana::Util.now_in_ms, _resource = nil, _instrumentation_scope = nil) # rubocop:disable Lint/MissingSuper, Metrics/ParameterLists, Layout/LineLength + @attributes = {} + + @ended = false + if parent_span.is_a?(::Instana::Span) + @parent = parent_span + end + if parent_ctx.is_a?(::Instana::Span) + @parent = parent_ctx + parent_ctx = parent_ctx.context + end + + if parent_ctx.is_a?(::Instana::SpanContext) + @is_root = false + + # If we have a parent trace, link to it + if parent_ctx.trace_id + @attributes[:t] = parent_ctx.trace_id # Trace ID + @attributes[:p] = parent_span_id || parent_ctx.span_id # Parent ID + else + @attributes[:t] = ::Instana::Trace.generate_trace_id + end + + @attributes[:s] = ::Instana::Trace.generate_span_id # Span ID + + @baggage = parent_ctx.baggage.dup + @level = parent_ctx.level + else + # No parent specified so we're starting a new Trace - this will be the root span + @is_root = true + @level = 1 + + id = ::Instana::Trace.generate_span_id + @attributes[:t] = id # Trace ID + @attributes[:s] = id # Span ID + end + + @attributes[:data] = {} + + if ENV.key?('INSTANA_SERVICE_NAME') + @attributes[:data][:service] = ENV['INSTANA_SERVICE_NAME'] + end + + # Entity Source + @attributes[:f] = ::Instana.agent.source + # Start time + @attributes[:ts] = if start_timestamp.is_a?(Time) + ::Instana::Util.time_to_ms(start_timestamp) + else + start_timestamp + end + + # Check for custom tracing + if REGISTERED_SPANS.include?(name&.to_sym) # Todo remove the safe & operator once all the tests are adapted to new init structure + @attributes[:n] = name.to_sym + else + configure_custom(name) + end + set_tags(attributes) + ::Instana.processor.on_start(self) + # Attach a backtrace to all exit spans + add_stack if ::Instana.config[:collect_backtraces] && exit_span? + end + + # Adds a backtrace to this span + # + # @param limit [Integer] Limit the backtrace to the top frames + # + def add_stack(limit: 30, stack: Kernel.caller) + cleaner = ::Instana.config[:backtrace_cleaner] + stack = cleaner.call(stack) if cleaner + + @attributes[:stack] = stack + .map do |call| + file, line, *method = call.split(':') + + { + c: file, + n: line, + m: method.join(' ') + } + end.take(limit > 40 ? 40 : limit) + end + + # Log an error into the span + # + # @param e [Exception] The exception to be logged + # + def record_exception(error) + @attributes[:error] = true + + @attributes[:ec] = if @attributes.key?(:ec) + @attributes[:ec] + 1 + else + 1 + end + + # If a valid exception has been passed in, log the information about it + # In case of just logging an error for things such as HTTP client 5xx + # responses, an exception/backtrace may not exist. + if error + if error.backtrace.is_a?(Array) + add_stack(stack: error.backtrace) + end + + if HTTP_SPANS.include?(@attributes[:n]) + set_tags(:http => { :error => "#{error.class}: #{error.message}" }) + elsif @attributes[:n] == :activerecord + @attributes[:data][:activerecord][:error] = error.message + else + log(:error, Time.now, message: error.message, parameters: error.class.to_s) + end + error.instance_variable_set(:@instana_logged, true) + end + self + end + + # Configure this span to be a custom span per the + # SDK generic span type. + # + # Default to an intermediate kind span. Can be overridden by + # setting a span.kind tag. + # + # @param name [String] name of the span + # @param kvs [Hash] list of key values to be reported in the span + # + def configure_custom(name) + @attributes[:n] = :sdk + @attributes[:data] = { :sdk => { :name => name&.to_sym } } # Todo remove safe operator once other tests adapt to new init structure + @attributes[:data][:sdk][:custom] = { :tags => {}, :logs => {} } + + if @is_root + # For custom root spans (via SDK or opentracing), default to entry type + @attributes[:k] = 1 + @attributes[:data][:sdk][:type] = :entry + else + @attributes[:k] = 3 + @attributes[:data][:sdk][:type] = :intermediate + end + self + end + + # Closes out the span. This difference between this and + # the finish method tells us how the tracing is being + # performed (with OpenTracing or Instana default) + # + # @param end_time [Time] custom end time, if not now + # @return [Span] + # + def close(end_time = ::Instana::Util.now_in_ms) + if end_time.is_a?(Time) + end_time = ::Instana::Util.time_to_ms(end_time) + end + + @attributes[:d] = end_time - @attributes[:ts] + @ended = true + # Add this span to the queue for reporting + ::Instana.processor.on_finish(self) + + self + end + + ############################################################# + # Accessors + ############################################################# + + # Retrieve the context of this span. + # + # @return [Instana::SpanContext] + # + def context # rubocop:disable Lint/DuplicateMethods + @context ||= ::Instana::SpanContext.new(trace_id: @attributes[:t], span_id: @attributes[:s], level: @level, baggage: @baggage) + end + + # Retrieve the ID for this span + # + # @return [Integer] the span ID + def id + @attributes[:s] + end + + # Retrieve the Trace ID for this span + # + # @return [Integer] the Trace ID + def trace_id + @attributes[:t] + end + + # Retrieve the parent ID of this span + # + # @return [Integer] parent span ID + def parent_id + @attributes[:p] + end + + # Set the parent ID of this span + # + # @return [Integer] parent span ID + def parent_id=(id) + @attributes[:p] = id + end + + # Get the name (operation) of this Span + # + # @return [String] or [Symbol] representing the span name + def name + if custom? + @attributes[:data][:sdk][:name] + else + @attributes[:n] + end + end + + # Set the name (operation) for this Span + # + # @params name [String] or [Symbol] + # + def name=(name) + if custom? + @attributes[:data][:sdk][:name] = name + else + @attributes[:n] = name + end + end + + # Get the duration value for this Span + # + # @return [Integer] the duration in milliseconds + def duration + @attributes[:d] + end + + # Hash accessor to the internal @attributes hash + # + def [](key) + @attributes[key.to_sym] + end + + # Hash setter to the internal @attributes hash + # + def []=(key, value) + @attributes[key.to_sym] = value + end + + # Hash key query to the internal @attributes hash + # + def key?(key) + @attributes.key?(key.to_sym) + end + + # Get the raw @attributes hash that summarizes this span + # + def raw + @attributes + end + + # Indicates whether this span is a custom or registered Span + def custom? + @attributes[:n] == :sdk + end + + def inspect + @attributes.inspect + end + + # Check to see if the current span indicates an exit from application + # code and into an external service + def exit_span? + EXIT_SPANS.include?(@attributes[:n]) + end + + ############################################################# + # OpenTracing Compatibility Methods + ############################################################# + + # Set the name of the operation + # Spec: OpenTracing API + # + # @params name [String] or [Symbol] + # + def operation_name=(name) + @attributes[:n] = name + end + + # Set a tag value on this span + # Spec: OpenTracing API + # + # @param key [String] the key of the tag + # @param value [String, Numeric, Boolean] the value of the tag. If it's not + # a String, Numeric, or Boolean it will be encoded with to_s + # + def set_tag(key, value) + unless [Symbol, String].include?(key.class) + key = key.to_s + end + + # If is not a Symbol, String, Array, Hash or Numeric - convert to string + if ![Symbol, String, Array, TrueClass, FalseClass, Hash].include?(value.class) && !value.is_a?(Numeric) + value = value.to_s + end + + if custom? + @attributes[:data][:sdk][:custom] ||= {} + @attributes[:data][:sdk][:custom][:tags] ||= {} + @attributes[:data][:sdk][:custom][:tags][key] = value + + if key.to_sym == :'span.kind' + case value.to_sym + when ENTRY, SERVER, CONSUMER + @attributes[:data][:sdk][:type] = ENTRY + @attributes[:k] = 1 + when EXIT, CLIENT, PRODUCER + @attributes[:data][:sdk][:type] = EXIT + @attributes[:k] = 2 + else + @attributes[:data][:sdk][:type] = INTERMEDIATE + @attributes[:k] = 3 + end + end + elsif value.is_a?(Hash) && @attributes[:data][key].is_a?(Hash) + @attributes[:data][key].merge!(value) + else + @attributes[:data][key] = value + end + self + end + + # Helper method to add multiple tags to this span + # + # @params tags [Hash] + # @return [Span] + # + def set_tags(tags) # rubocop:disable Naming + return unless tags.is_a?(Hash) + + tags.each do |k, v| + set_tag(k, v) + end + self + end + + # Set a baggage item on the span + # Spec: OpenTracing API + # + # @param key [String] the key of the baggage item + # @param value [String] the value of the baggage item + # Todo Evalute if baggage is used anywhere in instana + def set_baggage_item(key, value) + @baggage ||= {} + @baggage[key] = value + + # Init/Update the SpanContext item + if @context + @context.baggage = @baggage + else + @context ||= ::Instana::SpanContext.new(trace_id: @attributes[:t], span_id: @attributes[:s], level: @level, baggage: @baggage) + end + self + end + + # Get a baggage item + # Spec: OpenTracing API + # + # @param key [String] the key of the baggage item + # @return Value of the baggage item + # + def get_baggage_item(key) + @baggage[key] + end + + # Retrieve the hash of tags for this span + # + def tags(key = nil) + tags = if custom? + @attributes[:data][:sdk][:custom][:tags] + else + @attributes[:data] + end + key ? tags[key] : tags + end + + # Add a log entry to this span + # Spec: OpenTracing API + # + # @param event [String] event name for the log + # @param timestamp [Time] time of the log + # @param fields [Hash] Additional information to log + # + def log(event = nil, timestamp = Time.now, **fields) + ts = ::Instana::Util.time_to_ms(timestamp).to_s + if custom? + @attributes[:data][:sdk][:custom][:logs][ts] = fields + @attributes[:data][:sdk][:custom][:logs][ts][:event] = event + else + set_tags(:log => fields) + end + rescue StandardError => e + Instana.logger.debug { "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" } + end + + # Finish the {Span} + # Spec: OpenTracing API + # + # @param end_time [Time] custom end time, if not now + # + def finish(end_time = ::Instana::Util.now_in_ms) + close(end_time) + ::Instana.tracer.current_span = ::Instana.tracer.current_span&.parent || nil + self + end + + # Return the flag whether this span is recording events + # + # @return [Boolean] true if this Span is active and recording information + # like events with the #add_event operation and attributes using + # #set_attribute. + def recording? + !@ended + end + + # Set attribute + # + # Note that the OpenTelemetry project + # {https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md + # documents} certain "standard attributes" that have prescribed semantic + # meanings. + # + # @param [String] key + # @param [String, Boolean, Numeric, Array] value + # Values must be non-nil and (array of) string, boolean or numeric type. + # Array values must not contain nil elements and all elements must be of + # the same basic type (string, numeric, boolean). + # + # @return [self] returns itself + def set_attribute(key, value) + @attributes ||= {} + @attributes[key] = value + self + end + # alias []= set_attribute + + # Add attributes + # + # Note that the OpenTelemetry project + # {https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md + # documents} certain "standard attributes" that have prescribed semantic + # meanings. + # + # @param [Hash{String => String, Numeric, Boolean, Array}] attributes + # Values must be non-nil and (array of) string, boolean or numeric type. + # Array values must not contain nil elements and all elements must be of + # the same basic type (string, numeric, boolean). + # + # @return [self] returns itself + def add_attributes(attributes) + @attributes ||= {} + @attributes.merge!(attributes) + self + end + + # Add a link to a {Span}. + # + # Adding links at span creation using the `links` option is preferred + # to calling add_link later, because head sampling decisions can only + # consider information present during span creation. + # + # Example: + # + # span.add_link(OpenTelemetry::Trace::Link.new(span_to_link_from.context)) + # + # Note that the OpenTelemetry project + # {https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md + # documents} certain "standard attributes" that have prescribed semantic + # meanings. + # + # @param [OpenTelemetry::Trace::Link] the link object to add on the {Span}. + # + # @return [self] returns itself + # Todo add link logic later + def add_link(_link) + self + end + + # Add an event to a {Span}. + # + # Example: + # + # span.add_event('event', attributes: {'eager' => true}) + # + # Note that the OpenTelemetry project + # {https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md + # documents} certain "standard event names and keys" which have + # prescribed semantic meanings. + # + # @param [String] name Name of the event. + # @param [optional Hash{String => String, Numeric, Boolean, Array}] + # attributes One or more key:value pairs, where the keys must be + # strings and the values may be (array of) string, boolean or numeric + # type. + # @param [optional Time] timestamp Optional timestamp for the event. + # + # @return [self] returns itself + # Todo Add the vent logic later + def add_event(_name, attributes: nil, timestamp: nil) # rubocop:disable Lint/UnusedMethodArgument + self + end + + # Sets the Status to the Span + # + # If used, this will override the default Span status. Default status is unset. + # + # Only the value of the last call will be recorded, and implementations + # are free to ignore previous calls. + # + # @param [Status] status The new status, which overrides the default Span + # status, which is OK. + # + # @return [void] + def status=(status); end + end +end diff --git a/lib/instana/tracing/span_context.rb b/lib/instana/trace/span_context.rb similarity index 64% rename from lib/instana/tracing/span_context.rb rename to lib/instana/trace/span_context.rb index 567c606a..b1032f60 100644 --- a/lib/instana/tracing/span_context.rb +++ b/lib/instana/trace/span_context.rb @@ -2,10 +2,8 @@ # (c) Copyright Instana Inc. 2017 module Instana - class SpanContext - attr_accessor :trace_id - attr_accessor :span_id - attr_accessor :baggage + class SpanContext < OpenTelemetry::Trace::SpanContext + attr_accessor :trace_id, :span_id, :baggage attr_reader :level # Create a new SpanContext @@ -15,9 +13,20 @@ class SpanContext # @param level [Integer] default 1 # @param baggage [Hash] baggage applied to this trace # - def initialize(tid, sid, level = 1, baggage = {}) - @trace_id = tid - @span_id = sid + def initialize( # rubocop:disable Lint/MissingSuper, Metrics/ParameterLists + trace_id: Trace.generate_trace_id, + span_id: Trace.generate_span_id, + trace_flags: OpenTelemetry::Trace::TraceFlags::DEFAULT, # Todo - implement traceflags + tracestate: OpenTelemetry::Trace::Tracestate::DEFAULT, # Todo - implement tracestates + remote: false, + level: 1, + baggage: {} + ) + @trace_id = trace_id + @span_id = span_id + @trace_flags = trace_flags + @tracestate = tracestate + @remote = remote @level = Integer(level || 1) @baggage = baggage || {} end @@ -55,7 +64,7 @@ def to_hash end def valid? - @baggage && @trace_id && !@trace_id.emtpy? + @baggage && @trace_id && !@trace_id.empty? end def active? diff --git a/lib/instana/trace/span_kind.rb b/lib/instana/trace/span_kind.rb new file mode 100644 index 00000000..2f56c64a --- /dev/null +++ b/lib/instana/trace/span_kind.rb @@ -0,0 +1,51 @@ +# (c) Copyright IBM Corp. 2025 + +module Instana + # Type of span. Can be used to specify additional relationships between spans in addition to a + # parent/child relationship. For API ergonomics, use of the symbols rather than the constants + # may be preferred. For example: + # + # span = tracer.on_start('op', kind: :client) + module SpanKind + # Instana specific spans + REGISTERED_SPANS = [:actioncontroller, :actionview, :activerecord, :excon, + :memcache, :'net-http', :rack, :render, :'rpc-client', + :'rpc-server', :'sidekiq-client', :'sidekiq-worker', + :redis, :'resque-client', :'resque-worker', :'graphql.server', :dynamodb, :s3, :sns, :sqs, :'aws.lambda.entry', :activejob, :log, :"mail.actionmailer", + :"aws.lambda.invoke", :mongo, :sequel].freeze + ENTRY_SPANS = [:rack, :'resque-worker', :'rpc-server', :'sidekiq-worker', :'graphql.server', :sqs, + :'aws.lambda.entry'].freeze + EXIT_SPANS = [:activerecord, :excon, :'net-http', :'resque-client', + :'rpc-client', :'sidekiq-client', :redis, :dynamodb, :s3, :sns, :sqs, :log, :"mail.actionmailer", + :"aws.lambda.invoke", :mongo, :sequel].freeze + HTTP_SPANS = [:rack, :excon, :'net-http'].freeze + + # Default value. Indicates that the span is used internally. + INTERNAL = :internal + + # Indicates that the span covers server-side handling of an RPC or other remote request. + SERVER = :server + + # Indicates that the span covers the client-side wrapper around an RPC or other remote request. + CLIENT = :client + + # Indicates that the span describes producer sending a message to a broker. Unlike client and + # server, there is no direct critical path latency relationship between producer and consumer + # spans. + PRODUCER = :producer + + # Indicates that the span describes consumer receiving a message from a broker. Unlike client + # and server, there is no direct critical path latency relationship between producer and + # consumer spans. + CONSUMER = :consumer + + # Indicates an entry span. Equivalant to Server or Consumer + ENTRY = :entry + + # Indicates an exit span. Equivalant to Client or Producer + EXIT = :exit + + # Indicates an intermediate span. This used when sdk is used to produce intermediate traces + INTERMEDIATE = :intermediate + end +end diff --git a/lib/instana/trace/span_limits.rb b/lib/instana/trace/span_limits.rb new file mode 100644 index 00000000..003df808 --- /dev/null +++ b/lib/instana/trace/span_limits.rb @@ -0,0 +1,63 @@ +# (c) Copyright IBM Corp. 2025 + +require 'opentelemetry-common' + +module Instana + module Trace + # Class that holds global trace parameters. + class SpanLimits + # The global default max number of attributes per {Span}. + attr_reader :attribute_count_limit + + # The global default max length of attribute value per {Span}. + attr_reader :attribute_length_limit + + # The global default max number of {OpenTelemetry::SDK::Trace::Event}s per {Span}. + attr_reader :event_count_limit + + # The global default max number of {OpenTelemetry::Trace::Link} entries per {Span}. + attr_reader :link_count_limit + + # The global default max number of attributes per {OpenTelemetry::SDK::Trace::Event}. + attr_reader :event_attribute_count_limit + + # The global default max length of attribute value per {OpenTelemetry::SDK::Trace::Event}. + attr_reader :event_attribute_length_limit + + # The global default max number of attributes per {OpenTelemetry::Trace::Link}. + attr_reader :link_attribute_count_limit + + # Returns a {SpanLimits} with the desired values. + # + # @return [SpanLimits] with the desired values. + # @raise [ArgumentError] if any of the max numbers are not positive. + def initialize(attribute_count_limit: Integer(OpenTelemetry::Common::Utilities.config_opt('OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT', 'OTEL_ATTRIBUTE_COUNT_LIMIT', default: 128)), # rubocop:disable Metrics/ParameterLists + attribute_length_limit: OpenTelemetry::Common::Utilities.config_opt('OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT', 'OTEL_RUBY_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT', + 'OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT'), + event_count_limit: Integer(OpenTelemetry::Common::Utilities.config_opt('OTEL_SPAN_EVENT_COUNT_LIMIT', default: 128)), + link_count_limit: Integer(OpenTelemetry::Common::Utilities.config_opt('OTEL_SPAN_LINK_COUNT_LIMIT', default: 128)), + event_attribute_count_limit: Integer(OpenTelemetry::Common::Utilities.config_opt('OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT', default: 128)), + event_attribute_length_limit: OpenTelemetry::Common::Utilities.config_opt('OTEL_EVENT_ATTRIBUTE_VALUE_LENGTH_LIMIT', 'OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT'), + link_attribute_count_limit: Integer(OpenTelemetry::Common::Utilities.config_opt('OTEL_LINK_ATTRIBUTE_COUNT_LIMIT', default: 128))) + raise ArgumentError, 'attribute_count_limit must be positive' unless attribute_count_limit.positive? + raise ArgumentError, 'attribute_length_limit must not be less than 32' unless attribute_length_limit.nil? || Integer(attribute_length_limit) >= 32 + raise ArgumentError, 'event_count_limit must be positive' unless event_count_limit.positive? + raise ArgumentError, 'link_count_limit must be positive' unless link_count_limit.positive? + raise ArgumentError, 'event_attribute_count_limit must be positive' unless event_attribute_count_limit.positive? + raise ArgumentError, 'event_attribute_length_limit must not be less than 32' unless event_attribute_length_limit.nil? || Integer(event_attribute_length_limit) >= 32 + raise ArgumentError, 'link_attribute_count_limit must be positive' unless link_attribute_count_limit.positive? + + @attribute_count_limit = attribute_count_limit + @attribute_length_limit = attribute_length_limit.nil? ? nil : Integer(attribute_length_limit) + @event_count_limit = event_count_limit + @link_count_limit = link_count_limit + @event_attribute_count_limit = event_attribute_count_limit + @event_attribute_length_limit = event_attribute_length_limit.nil? ? nil : Integer(event_attribute_length_limit) + @link_attribute_count_limit = link_attribute_count_limit + end + + # The default {SpanLimits}. + DEFAULT = new + end + end +end diff --git a/lib/instana/tracer.rb b/lib/instana/trace/tracer.rb similarity index 72% rename from lib/instana/tracer.rb rename to lib/instana/trace/tracer.rb index 9ef3e35d..4c8cf607 100644 --- a/lib/instana/tracer.rb +++ b/lib/instana/trace/tracer.rb @@ -1,14 +1,14 @@ -# (c) Copyright IBM Corp. 2021 -# (c) Copyright Instana Inc. 2016 +# (c) Copyright IBM Corp. 2025 -require "instana/tracing/span" -require "instana/tracing/span_context" +require 'opentelemetry/trace/tracer' +require 'instana/trace/span' +require "instana/trace/span_context" +require 'opentelemetry/context' module Instana - class Tracer - # Support ::Instana::Tracer.xxx call style for the instantiated tracer + class Tracer < OpenTelemetry::Trace::Tracer class << self - def method_missing(method, *args, &block) + def method_missing(method, *args, &block) # rubocop:disable Style/MissingRespondToMissing if ::Instana.tracer.respond_to?(method) ::Instana.tracer.send(method, *args, &block) else @@ -17,7 +17,9 @@ def method_missing(method, *args, &block) end end - def initialize(logger: Instana.logger) + def initialize(_name, _version, tracer_provider, logger = Instana.logger) + super() + @tracer_provider = tracer_provider @current_span = Concurrent::ThreadLocalVar.new @logger = logger end @@ -29,8 +31,8 @@ def current_span # @param [Instana::Span, NilClas] v the new current span # Set the value of the current span - def current_span=(v) - @current_span.value = v + def current_span=(value) + @current_span.value = value end ####################################### @@ -48,10 +50,10 @@ def current_span=(v) # :span_id the ID of the parent span (must be an unsigned hex-string) # :level specifies data collection level (optional) # - def start_or_continue_trace(name, kvs = {}, incoming_context = nil, &block) + def start_or_continue_trace(name, kvs = {}, incoming_context = nil) span = log_start_or_continue(name, kvs, incoming_context) yield(span) - rescue Exception => e + rescue Exception => e # rubocop:disable Lint/RescueException log_error(e) raise ensure @@ -69,10 +71,10 @@ def start_or_continue_trace(name, kvs = {}, incoming_context = nil, &block) # @param name [String, Symbol] the name of the span to start # @param kvs [Hash] list of key values to be reported in this new span # - def trace(name, kvs = {}, &block) + def trace(name, kvs = {}) span = log_entry(name, kvs) yield(span) - rescue Exception => e + rescue Exception => e # rubocop:disable Lint/RescueException log_error(e) raise ensure @@ -100,12 +102,12 @@ def log_start_or_continue(name, kvs = {}, incoming_context = nil) # Handle the potential variations on `incoming_context` if incoming_context if incoming_context.is_a?(Hash) - if !incoming_context.empty? + unless incoming_context.empty? parent_context = SpanContext.new( - incoming_context[:trace_id], - incoming_context[:span_id], - incoming_context[:level], - { + trace_id: incoming_context[:trace_id], + span_id: incoming_context[:span_id], + level: incoming_context[:level], + baggage: { external_trace_id: incoming_context[:external_trace_id], external_state: incoming_context[:external_state] } @@ -116,14 +118,14 @@ def log_start_or_continue(name, kvs = {}, incoming_context = nil) end end - if parent_context - self.current_span = Span.new(name, parent_ctx: parent_context) - else - self.current_span = Span.new(name) - end + self.current_span = if parent_context + Span.new(name, parent_context) + else + Span.new(name) + end - self.current_span.set_tags(kvs) unless kvs.empty? - self.current_span + current_span.set_tags(kvs) unless kvs.empty? + current_span end # Will establish a new span as a child of the current span @@ -132,13 +134,13 @@ def log_start_or_continue(name, kvs = {}, incoming_context = nil) # @param name [String, Symbol] the name of the span to create # @param kvs [Hash] list of key values to be reported in the span # - def log_entry(name, kvs = nil, start_time = ::Instana::Util.now_in_ms, child_of = nil) + def log_entry(name, kvs = nil, _start_time = ::Instana::Util.now_in_ms, child_of = nil) return unless tracing? || child_of - new_span = if child_of.nil? && !self.current_span.nil? - Span.new(name, parent_ctx: self.current_span, start_time: start_time) + new_span = if child_of.nil? && !current_span.nil? + Span.new(name, current_span) else - Span.new(name, parent_ctx: child_of, start_time: start_time) + Span.new(name, child_of) end new_span.set_tags(kvs) if kvs self.current_span = new_span @@ -149,17 +151,19 @@ def log_entry(name, kvs = nil, start_time = ::Instana::Util.now_in_ms, child_of # @param kvs [Hash] list of key values to be reported in the span # def log_info(kvs) - return unless self.current_span - self.current_span.set_tags(kvs) + return unless current_span + + current_span.set_tags(kvs) end # Add an error to the current span # # @param e [Exception] Add exception to the current span # - def log_error(e) - return unless self.current_span - self.current_span.add_error(e) + def log_error(error) + return unless current_span + + current_span.record_exception(error) end # Closes out the current span @@ -171,16 +175,16 @@ def log_error(e) # @param kvs [Hash] list of key values to be reported in the span # def log_exit(name, kvs = {}) - return unless self.current_span + return unless current_span - if self.current_span.name != name - @logger.warn "Span mismatch: Attempt to end #{name} span but #{self.current_span.name} is active." + if current_span.name != name + @logger.warn "Span mismatch: Attempt to end #{name} span but #{current_span.name} is active." end - self.current_span.set_tags(kvs) - self.current_span.close + current_span.set_tags(kvs) + current_span.close - self.current_span = self.current_span.parent || nil + self.current_span = current_span.parent || nil end # Closes out the current span in the current trace @@ -193,14 +197,14 @@ def log_exit(name, kvs = {}) # @param kvs [Hash] list of key values to be reported in the span # def log_end(name, kvs = {}, end_time = ::Instana::Util.now_in_ms) - return unless self.current_span + return unless current_span - if self.current_span.name != name - @logger.warn "Span mismatch: Attempt to end #{name} span but #{self.current_span.name} is active." + if current_span.name != name + @logger.warn "Span mismatch: Attempt to end #{name} span but #{current_span.name} is active." end - self.current_span.set_tags(kvs) - self.current_span.close(end_time) + current_span.set_tags(kvs) + current_span.close(end_time) self.current_span = nil end @@ -220,7 +224,7 @@ def log_end(name, kvs = {}, end_time = ::Instana::Util.now_in_ms) def log_async_entry(name, kvs) return unless tracing? - new_span = Span.new(name, parent_ctx: self.current_span) + new_span = Span.new(name, current_span) new_span.set_tags(kvs) unless kvs.empty? new_span end @@ -239,8 +243,8 @@ def log_async_info(kvs, span) # @param e [Exception] Add exception to the current span # @param span [Span] the span for this Async op (previously returned from `log_async_entry`) # - def log_async_error(e, span) - span.add_error(e) + def log_async_error(error, span) + span.record_exception(error) end # Closes out an asynchronous span @@ -268,7 +272,7 @@ def tracing? # The non-nil value of this instance variable # indicates if we are currently tracing # in this thread or not. - (self.current_span ? true : false) || + (current_span ? true : false) || (::Instana.config[:allow_exit_as_root] && ::Instana.config[:tracing][:enabled]) end @@ -280,9 +284,10 @@ def tracing? # @return [Boolean] # def tracing_span?(name) - if self.current_span - return self.current_span.name == name + if current_span + return current_span.name == name end + false end @@ -291,8 +296,9 @@ def tracing_span?(name) # @return [SpanContext] or nil if not tracing # def context - return unless self.current_span - self.current_span.context + return unless current_span + + current_span.context end # Used in the test suite, this resets the tracer to non-tracing state. @@ -300,5 +306,21 @@ def context def clear! self.current_span = nil end + + def in_span(name, attributes: nil, links: nil, start_timestamp: nil, kind: nil) + return if !::Instana.agent.ready? || !::Instana.config[:tracing][:enabled] + + super + end + + def start_span(name, with_parent: nil, attributes: nil, links: nil, start_timestamp: ::Instana::Util.now_in_ms, kind: nil) # rubocop:disable Metrics/ParameterLists + return if !::Instana.agent.ready? || !::Instana.config[:tracing][:enabled] + + with_parent ||= OpenTelemetry::Context.current + name ||= 'empty' + kind ||= :internal + start_timestamp ||= ::Instana::Util.now_in_ms + self.current_span = @tracer_provider.internal_start_span(name, kind, attributes, links, start_timestamp, with_parent, @instrumentation_scope) + end end end diff --git a/lib/instana/trace/tracer_provider.rb b/lib/instana/trace/tracer_provider.rb new file mode 100644 index 00000000..ea217efc --- /dev/null +++ b/lib/instana/trace/tracer_provider.rb @@ -0,0 +1,198 @@ +# (c) Copyright IBM Corp. 2025 + +require 'opentelemetry/trace/tracer_provider' +require 'instana/samplers/samplers' +require 'instana/trace/span_limits' +require 'instana/trace/export' + +module Instana + module Trace + # {TracerProvider} is the Instana implementation of {OpenTelemetry::Trace::TracerProvider}. + class TracerProvider < OpenTelemetry::Trace::TracerProvider + Key = Struct.new(:name, :version) + private_constant(:Key) + + attr_accessor :span_limits, :id_generator, :sampler + attr_reader :resource + + # Returns a new {TracerProvider} instance. + # + # @param [optional Sampler] sampler The sampling policy for new spans + # @param [optional Resource] resource The resource to associate with spans + # created by Tracers created by this TracerProvider + # @param [optional IDGenerator] id_generator The trace and span ID generation + # policy + # @param [optional SpanLimits] span_limits The limits to apply to attribute, + # event and link counts for Spans created by Tracers created by this + # TracerProvider + # + # @return [TracerProvider] + # def initialize + # super() + # end + + def initialize(sampler: sampler_from_environment(Samplers.parent_based(root: Samplers::ALWAYS_ON)), + resource: nil, # Instana::Resources::Resource.create + id_generator: ::Instana::Trace, + span_limits: SpanLimits::DEFAULT) + super() + @mutex = Mutex.new + @registry = {} + @registry_mutex = Mutex.new + @span_processors = [] + @span_limits = span_limits + @sampler = sampler + @id_generator = id_generator + @stopped = false + @resource = resource + end + + # Returns a {Tracer} instance. + # + # @param [optional String] name Instrumentation package name + # @param [optional String] version Instrumentation package version + # + # @return [Tracer] + def tracer(name = nil, version = nil) + name ||= '' + version ||= '' + ::Instana.logger.warn 'calling TracerProvider#tracer without providing a tracer name.' if name.empty? + @registry_mutex.synchronize { @registry[Key.new(name, version)] ||= Tracer.new(name, version, self) } + end + + # Attempts to stop all the activity for this {TracerProvider}. Calls + # SpanProcessor#shutdown for all registered SpanProcessors. + # + # This operation may block until all the Spans are processed. Must be + # called before turning off the main application to ensure all data are + # processed and exported. + # + # After this is called all the newly created {Span}s will be no-op. + # + # @param [optional Numeric] timeout An optional timeout in seconds. + # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if + # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred. + def shutdown(timeout: nil) + @mutex.synchronize do + if @stopped + ::Instana.logger.warn('calling Tracer#shutdown multiple times.') + return Export::FAILURE + end + + start_time = Instana::Util.timeout_timestamp + results = @span_processors.map do |processor| + remaining_timeout = Instana::Util.maybe_timeout(timeout, start_time) + break [Export::TIMEOUT] if remaining_timeout&.zero? + + processor.shutdown(timeout: remaining_timeout) + end + @stopped = true + results.max || Export::SUCCESS + end + end + + # Immediately export all spans that have not yet been exported for all the + # registered SpanProcessors. + # + # This method should only be called in cases where it is absolutely + # necessary, such as when using some FaaS providers that may suspend + # the process after an invocation, but before the `Processor` exports + # the completed spans. + # + # @param [optional Numeric] timeout An optional timeout in seconds. + # @return [Integer] Export::SUCCESS if no error occurred, Export::FAILURE if + # a non-specific failure occurred, Export::TIMEOUT if a timeout occurred. + def force_flush(timeout: nil) + @mutex.synchronize do + return Export::SUCCESS if @stopped + + start_time = Instana::Util.timeout_timestamp + results = @span_processors.map do |processor| + remaining_timeout = Instana::Util.maybe_timeout(timeout, start_time) + return Export::TIMEOUT if remaining_timeout&.zero? + + processor.force_flush(timeout: remaining_timeout) + end + results.max || Export::SUCCESS + end + end + + # Adds a new SpanProcessor to this {Tracer}. + # + # @param span_processor the new SpanProcessor to be added. + def add_span_processor(span_processor) + @mutex.synchronize do + if @stopped + ::Instana.logger.warn('calling Tracer#add_span_processor after shutdown.') + return + end + @span_processors = @span_processors.dup.push(span_processor) + end + end + + # This method serves as the primary entry point for span creation. It initializes + # an Instana span, handles context, and manages sampling before returning the created span. + def internal_start_span(name, kind, attributes, links, start_timestamp, parent_context, instrumentation_scope) # rubocop:disable Metrics/ParameterLists + parent_span = OpenTelemetry::Trace.current_span(parent_context) + parent_span_context = parent_span.context if parent_span + if parent_span_context&.valid? + parent_span_id = parent_span_context.span_id + trace_id = parent_span_context.trace_id + span_id = @id_generator.generate_span_id + end + trace_id ||= @id_generator.generate_trace_id + + if OpenTelemetry::Common::Utilities.untraced?(parent_context) + span_id = parent_span_id || @id_generator.generate_span_id + return OpenTelemetry::Trace.non_recording_span(OpenTelemetry::Trace::SpanContext.new(trace_id: trace_id, span_id: span_id)) + end + + result = @sampler.should_sample?(trace_id: trace_id, parent_context: parent_context, links: links, name: name, kind: kind, attributes: attributes) + span_id ||= @id_generator.generate_span_id + if !@stopped && result.recording? && !@stopped + trace_flags = result.sampled? ? OpenTelemetry::Trace::TraceFlags::SAMPLED : OpenTelemetry::Trace::TraceFlags::DEFAULT + context = Instana::SpanContext.new(trace_id: trace_id, span_id: span_id, trace_flags: trace_flags, tracestate: result.tracestate) + attributes = attributes&.merge(result.attributes) || result.attributes.dup + Instana::Span.new( + name, + parent_span_context, + context, + parent_span, + kind, + parent_span_id, + @span_limits, + @span_processors, + attributes, + links, + start_timestamp, + @resource, + instrumentation_scope + ) + else + Instana::Trace.non_recording_span(Instana::Trace::SpanContext.new(trace_id: trace_id, span_id: span_id, tracestate: result.tracestate)) # Todo add tracestate so that the trcing doesnot happen for this span # rubocop:disable Layout/LineLength + end + end + + private + + def sampler_from_environment(default_sampler) + case ENV['OTEL_TRACES_SAMPLER'] + when 'always_on' then Samplers::ALWAYS_ON + when 'always_off' then Samplers::ALWAYS_OFF + when 'traceidratio' then Samplers.trace_id_ratio_based(Float(ENV.fetch('OTEL_TRACES_SAMPLER_ARG', 1.0))) + when 'parentbased_always_on' then Samplers.parent_based(root: Samplers::ALWAYS_ON) + when 'parentbased_always_off' then Samplers.parent_based(root: Samplers::ALWAYS_OFF) + when 'parentbased_traceidratio' then Samplers.parent_based(root: Samplers.trace_id_ratio_based(Float(ENV.fetch('OTEL_TRACES_SAMPLER_ARG', 1.0)))) + else default_sampler + end + rescue StandardError => e + OpenTelemetry.handle_error(exception: e, message: "installing default sampler #{default_sampler.description}") + default_sampler + end + + def timeout_timestamp + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + end + end +end diff --git a/lib/instana/tracing/span.rb b/lib/instana/tracing/span.rb deleted file mode 100644 index 9847c3a5..00000000 --- a/lib/instana/tracing/span.rb +++ /dev/null @@ -1,431 +0,0 @@ -# (c) Copyright IBM Corp. 2021 -# (c) Copyright Instana Inc. 2016 - -module Instana - class Span - REGISTERED_SPANS = [ :actioncontroller, :actionview, :activerecord, :excon, - :memcache, :'net-http', :rack, :render, :'rpc-client', - :'rpc-server', :'sidekiq-client', :'sidekiq-worker', - :redis, :'resque-client', :'resque-worker', :'graphql.server', :dynamodb, :s3, :sns, :sqs, :'aws.lambda.entry', :activejob, :log, :"mail.actionmailer", - :"aws.lambda.invoke", :mongo, :sequel ].freeze - ENTRY_SPANS = [ :rack, :'resque-worker', :'rpc-server', :'sidekiq-worker', :'graphql.server', :sqs, - :'aws.lambda.entry' ].freeze - EXIT_SPANS = [ :activerecord, :excon, :'net-http', :'resque-client', - :'rpc-client', :'sidekiq-client', :redis, :dynamodb, :s3, :sns, :sqs, :log, :"mail.actionmailer", - :"aws.lambda.invoke", :mongo, :sequel ].freeze - HTTP_SPANS = [ :rack, :excon, :'net-http' ].freeze - - attr_accessor :parent - attr_accessor :baggage - attr_accessor :is_root - attr_accessor :context - - def initialize(name, parent_ctx: nil, start_time: ::Instana::Util.now_in_ms) - @data = {} - - if parent_ctx.is_a?(::Instana::Span) - @parent = parent_ctx - parent_ctx = parent_ctx.context - end - - if parent_ctx.is_a?(::Instana::SpanContext) - @is_root = false - - # If we have a parent trace, link to it - if parent_ctx.trace_id - @data[:t] = parent_ctx.trace_id # Trace ID - @data[:p] = parent_ctx.span_id # Parent ID - else - @data[:t] = ::Instana::Util.generate_id - end - - @data[:s] = ::Instana::Util.generate_id # Span ID - - @baggage = parent_ctx.baggage.dup - @level = parent_ctx.level - else - # No parent specified so we're starting a new Trace - this will be the root span - @is_root = true - @level = 1 - - id = ::Instana::Util.generate_id - @data[:t] = id # Trace ID - @data[:s] = id # Span ID - end - - @data[:data] = {} - - if ENV.key?('INSTANA_SERVICE_NAME') - @data[:data][:service] = ENV['INSTANA_SERVICE_NAME'] - end - - # Entity Source - @data[:f] = ::Instana.agent.source - # Start time - if start_time.is_a?(Time) - @data[:ts] = ::Instana::Util.time_to_ms(start_time) - else - @data[:ts] = start_time - end - - # Check for custom tracing - if REGISTERED_SPANS.include?(name.to_sym) - @data[:n] = name.to_sym - else - configure_custom(name) - end - - ::Instana.processor.start_span(self) - - # Attach a backtrace to all exit spans - add_stack if ::Instana.config[:collect_backtraces] && exit_span? - end - - # Adds a backtrace to this span - # - # @param limit [Integer] Limit the backtrace to the top frames - # - def add_stack(limit: 30, stack: Kernel.caller) - cleaner = ::Instana.config[:backtrace_cleaner] - stack = cleaner.call(stack) if cleaner - - @data[:stack] = stack - .map do |call| - file, line, *method = call.split(':') - - { - c: file, - n: line, - m: method.join(' ') - } - end.take(limit > 40 ? 40 : limit) - end - - # Log an error into the span - # - # @param e [Exception] The exception to be logged - # - def add_error(e) - @data[:error] = true - - if @data.key?(:ec) - @data[:ec] = @data[:ec] + 1 - else - @data[:ec] = 1 - end - - # If a valid exception has been passed in, log the information about it - # In case of just logging an error for things such as HTTP client 5xx - # responses, an exception/backtrace may not exist. - if e - if e.backtrace.is_a?(Array) - add_stack(stack: e.backtrace) - end - - if HTTP_SPANS.include?(@data[:n]) - set_tags(:http => { :error => "#{e.class}: #{e.message}" }) - elsif @data[:n] == :activerecord - @data[:data][:activerecord][:error] = e.message - else - log(:error, Time.now, message: e.message, parameters: e.class.to_s) - end - e.instance_variable_set(:@instana_logged, true) - end - self - end - - - # Configure this span to be a custom span per the - # SDK generic span type. - # - # Default to an intermediate kind span. Can be overridden by - # setting a span.kind tag. - # - # @param name [String] name of the span - # @param kvs [Hash] list of key values to be reported in the span - # - def configure_custom(name) - @data[:n] = :sdk - @data[:data] = { :sdk => { :name => name.to_sym } } - @data[:data][:sdk][:custom] = { :tags => {}, :logs => {} } - - if @is_root - # For custom root spans (via SDK or opentracing), default to entry type - @data[:k] = 1 - @data[:data][:sdk][:type] = :entry - else - @data[:k] = 3 - @data[:data][:sdk][:type] = :intermediate - end - self - end - - # Closes out the span. This difference between this and - # the finish method tells us how the tracing is being - # performed (with OpenTracing or Instana default) - # - # @param end_time [Time] custom end time, if not now - # @return [Span] - # - def close(end_time = ::Instana::Util.now_in_ms) - if end_time.is_a?(Time) - end_time = ::Instana::Util.time_to_ms(end_time) - end - - @data[:d] = end_time - @data[:ts] - - # Add this span to the queue for reporting - ::Instana.processor.add_span(self) - - self - end - - ############################################################# - # Accessors - ############################################################# - - # Retrieve the context of this span. - # - # @return [Instana::SpanContext] - # - def context - @context ||= ::Instana::SpanContext.new(@data[:t], @data[:s], @level, @baggage) - end - - # Retrieve the ID for this span - # - # @return [Integer] the span ID - def id - @data[:s] - end - - # Retrieve the Trace ID for this span - # - # @return [Integer] the Trace ID - def trace_id - @data[:t] - end - - # Retrieve the parent ID of this span - # - # @return [Integer] parent span ID - def parent_id - @data[:p] - end - - # Set the parent ID of this span - # - # @return [Integer] parent span ID - def parent_id=(id) - @data[:p] = id - end - - # Get the name (operation) of this Span - # - # @return [String] or [Symbol] representing the span name - def name - if custom? - @data[:data][:sdk][:name] - else - @data[:n] - end - end - - # Set the name (operation) for this Span - # - # @params name [String] or [Symbol] - # - def name=(n) - if custom? - @data[:data][:sdk][:name] = n - else - @data[:n] = n - end - end - - # Get the duration value for this Span - # - # @return [Integer] the duration in milliseconds - def duration - @data[:d] - end - - # Hash accessor to the internal @data hash - # - def [](key) - @data[key.to_sym] - end - - # Hash setter to the internal @data hash - # - def []=(key, value) - @data[key.to_sym] = value - end - - # Hash key query to the internal @data hash - # - def key?(k) - @data.key?(k.to_sym) - end - - # Get the raw @data hash that summarizes this span - # - def raw - @data - end - - # Indicates whether this span is a custom or registered Span - def custom? - @data[:n] == :sdk - end - - def inspect - @data.inspect - end - - # Check to see if the current span indicates an exit from application - # code and into an external service - def exit_span? - EXIT_SPANS.include?(@data[:n]) - end - - ############################################################# - # OpenTracing Compatibility Methods - ############################################################# - - # Set the name of the operation - # Spec: OpenTracing API - # - # @params name [String] or [Symbol] - # - def operation_name=(name) - @data[:n] = name - end - - # Set a tag value on this span - # Spec: OpenTracing API - # - # @param key [String] the key of the tag - # @param value [String, Numeric, Boolean] the value of the tag. If it's not - # a String, Numeric, or Boolean it will be encoded with to_s - # - def set_tag(key, value) - if ![Symbol, String].include?(key.class) - key = key.to_s - end - - # If is not a Symbol, String, Array, Hash or Numeric - convert to string - if ![Symbol, String, Array, TrueClass, FalseClass, Hash].include?(value.class) && !value.is_a?(Numeric) - value = value.to_s - end - - if custom? - @data[:data][:sdk][:custom] ||= {} - @data[:data][:sdk][:custom][:tags] ||= {} - @data[:data][:sdk][:custom][:tags][key] = value - - if key.to_sym == :'span.kind' - case value.to_sym - when :entry, :server, :consumer - @data[:data][:sdk][:type] = :entry - @data[:k] = 1 - when :exit, :client, :producer - @data[:data][:sdk][:type] = :exit - @data[:k] = 2 - else - @data[:data][:sdk][:type] = :intermediate - @data[:k] = 3 - end - end - else - if value.is_a?(Hash) && @data[:data][key].is_a?(Hash) - @data[:data][key].merge!(value) - else - @data[:data][key] = value - end - end - self - end - - # Helper method to add multiple tags to this span - # - # @params tags [Hash] - # @return [Span] - # - def set_tags(tags) - return unless tags.is_a?(Hash) - tags.each do |k,v| - set_tag(k, v) - end - self - end - - # Set a baggage item on the span - # Spec: OpenTracing API - # - # @param key [String] the key of the baggage item - # @param value [String] the value of the baggage item - def set_baggage_item(key, value) - @baggage ||= {} - @baggage[key] = value - - # Init/Update the SpanContext item - if @context - @context.baggage = @baggage - else - @context ||= ::Instana::SpanContext.new(@data[:t], @data[:s], @level, @baggage) - end - self - end - - # Get a baggage item - # Spec: OpenTracing API - # - # @param key [String] the key of the baggage item - # @return Value of the baggage item - # - def get_baggage_item(key) - @baggage[key] - end - - # Retrieve the hash of tags for this span - # - def tags(key = nil) - if custom? - tags = @data[:data][:sdk][:custom][:tags] - else - tags = @data[:data] - end - key ? tags[key] : tags - end - - # Add a log entry to this span - # Spec: OpenTracing API - # - # @param event [String] event name for the log - # @param timestamp [Time] time of the log - # @param fields [Hash] Additional information to log - # - def log(event = nil, timestamp = Time.now, **fields) - ts = ::Instana::Util.time_to_ms(timestamp).to_s - if custom? - @data[:data][:sdk][:custom][:logs][ts] = fields - @data[:data][:sdk][:custom][:logs][ts][:event] = event - else - set_tags(:log => fields) - end - rescue StandardError => e - Instana.logger.debug { "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" } - end - - # Finish the {Span} - # Spec: OpenTracing API - # - # @param end_time [Time] custom end time, if not now - # - def finish(end_time = ::Instana::Util.now_in_ms) - close(end_time) - self - end - end -end diff --git a/lib/instana/util.rb b/lib/instana/util.rb index 5ce58ee4..28f49fbd 100644 --- a/lib/instana/util.rb +++ b/lib/instana/util.rb @@ -170,6 +170,17 @@ def header_to_id(given) return '' unless given.match(/\A[a-z\d]{16,32}\z/i) given end + + def timeout_timestamp + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + + def maybe_timeout(timeout, start_time) + return nil if timeout.nil? + + timeout -= (timeout_timestamp - start_time) + timeout.positive? ? timeout : 0 + end end end end diff --git a/lib/opentracing.rb b/lib/opentracing.rb deleted file mode 100644 index 402256d8..00000000 --- a/lib/opentracing.rb +++ /dev/null @@ -1,32 +0,0 @@ -# (c) Copyright IBM Corp. 2021 -# (c) Copyright Instana Inc. 2017 - -require "instana/open_tracing/carrier" -require "instana/open_tracing/instana_tracer" - -module OpenTracing - class << self - # Text format for #inject and #extract - FORMAT_TEXT_MAP = 1 - - # Binary format for #inject and #extract - FORMAT_BINARY = 2 - - # Ruby Specific format to handle how Rack changes environment variables. - FORMAT_RACK = 3 - - attr_accessor :global_tracer - - def method_missing(method_name, *args, **kwargs, &block) - @global_tracer.send(method_name, *args, **kwargs, &block) - end - - def respond_to_missing?(name, all) - @global_tracer.respond_to?(name, all) - end - end -end - -# Set the global tracer to our OT tracer -# which supports the OT specification -OpenTracing.global_tracer = OpenTracing::InstanaTracer.new(::Instana.tracer) diff --git a/test/instrumentation/aws_test.rb b/test/instrumentation/aws_test.rb index 74ca4976..69f57bb2 100644 --- a/test/instrumentation/aws_test.rb +++ b/test/instrumentation/aws_test.rb @@ -17,7 +17,7 @@ def test_dynamo_db ) assert_raises Aws::DynamoDB::Errors::ResourceNotFoundException do - Instana::Tracer.start_or_continue_trace(:dynamo_test, {}) do + Instana.tracer.in_span(:dynamo_test, attributes: {}) do dynamo.get_item( table_name: 'sample_table', key: { s: 'sample_item' } @@ -45,7 +45,7 @@ def test_s3 ) assert_raises Aws::S3::Errors::NoSuchBucket do - Instana::Tracer.start_or_continue_trace(:s3_test, {}) do + Instana.tracer.in_span(:s3_test, attributes: {}) do s3_client.get_object( bucket: 'sample-bucket', key: 'sample_key' @@ -73,7 +73,7 @@ def test_sns_publish ) assert_raises Aws::SNS::Errors::NotFound do - Instana::Tracer.start_or_continue_trace(:sns_test, {}) do + Instana.tracer.in_span(:sns_test, attributes: {}) do sns.publish( topic_arn: 'topic:arn', target_arn: 'target:arn', @@ -104,7 +104,7 @@ def test_sns_other endpoint: "http://localhost:9911" ) - Instana::Tracer.start_or_continue_trace(:sns_test, {}) do + Instana.tracer.in_span(:sns_test, attributes: {}) do sns.list_subscriptions end @@ -127,7 +127,7 @@ def test_sqs create_response = nil get_url_response = nil - Instana::Tracer.start_or_continue_trace(:sqs_test, {}) do + Instana.tracer.in_span(:sqs_test, attributes: {}) do create_response = sqs.create_queue(queue_name: 'test') get_url_response = sqs.get_queue_url(queue_name: 'test') sqs.send_message(queue_url: create_response.queue_url, message_body: 'Sample') @@ -177,7 +177,7 @@ def test_lambda secret_access_key: "test" ) - Instana::Tracer.start_or_continue_trace(:lambda_test, {}) do + Instana.tracer.in_span(:lambda_test, attributes: {}) do lambda.invoke( function_name: 'Test', invocation_type: 'Event', @@ -216,7 +216,7 @@ def test_lambda_with_500_status ) assert_raises(RuntimeError) do - Instana::Tracer.start_or_continue_trace(:lambda_test, {}) do + Instana.tracer.in_span(:lambda_test, attributes: {}) do lambda.invoke( function_name: 'Test', invocation_type: 'Event', diff --git a/test/instrumentation/dalli_test.rb b/test/instrumentation/dalli_test.rb index d30fdb79..6658038b 100644 --- a/test/instrumentation/dalli_test.rb +++ b/test/instrumentation/dalli_test.rb @@ -25,7 +25,7 @@ def test_basic_get @dc.set(:instana, :boom) result = nil - ::Instana.tracer.start_or_continue_trace(:dalli_test) do + ::Instana.tracer.in_span(:dalli_test) do result = @dc.get(:instana) end @@ -87,7 +87,7 @@ def test_basic_set clear_all! result = nil - ::Instana.tracer.start_or_continue_trace(:dalli_test) do + ::Instana.tracer.in_span(:dalli_test) do result = @dc.set(:instana, :rocks) end @@ -121,7 +121,7 @@ def test_replace @dc.set(:instana, :rocks) result = nil - ::Instana.tracer.start_or_continue_trace(:dalli_test) do + ::Instana.tracer.in_span(:dalli_test) do result = @dc.replace(:instana, :rocks) end @@ -155,7 +155,7 @@ def test_delete @dc.set(:instana, :rocks) result = nil - ::Instana.tracer.start_or_continue_trace(:dalli_test) do + ::Instana.tracer.in_span(:dalli_test) do result = @dc.delete(:instana) end @@ -189,7 +189,7 @@ def test_incr result = nil @dc.set(:counter, 0, nil, :raw => true) - ::Instana.tracer.start_or_continue_trace(:dalli_test) do + ::Instana.tracer.in_span(:dalli_test) do result = @dc.incr(:counter, 1, nil, 0) end @@ -223,7 +223,7 @@ def test_decr result = nil @dc.set(:counter, 0, nil, :raw => true) - ::Instana.tracer.start_or_continue_trace(:dalli_test) do + ::Instana.tracer.in_span(:dalli_test) do result = @dc.decr(:counter, 1, nil, 0) end @@ -258,7 +258,7 @@ def test_get_multi @dc.set(:one, 1) @dc.set(:three, 3) - ::Instana.tracer.start_or_continue_trace(:dalli_test) do + ::Instana.tracer.in_span(:dalli_test) do @dc.get_multi(:one, :two, :three, :four) end @@ -295,7 +295,7 @@ def test_get_error_logging result = nil begin - ::Instana.tracer.start_or_continue_trace(:dalli_test) do + ::Instana.tracer.in_span(:dalli_test) do result = broken_dc.get(:instana) end rescue diff --git a/test/instrumentation/excon_test.rb b/test/instrumentation/excon_test.rb index 67372b67..3708784b 100644 --- a/test/instrumentation/excon_test.rb +++ b/test/instrumentation/excon_test.rb @@ -26,7 +26,7 @@ def test_basic_get url = "http://127.0.0.1:6511" connection = Excon.new(url) - Instana.tracer.start_or_continue_trace(:'excon-test') do + Instana.tracer.in_span(:'excon-test') do connection.get(:path => '/?basic_get') end @@ -104,7 +104,7 @@ def test_basic_get_with_error begin connection = Excon.new(url) - Instana.tracer.start_or_continue_trace('excon-test') do + Instana.tracer.in_span('excon-test') do connection.get(:path => '/error') end rescue @@ -145,7 +145,7 @@ def test_pipelined_requests connection = Excon.new(url) request = { :method => :get, :path => '/?pipelined_request' } - Instana.tracer.start_or_continue_trace('excon-test') do + Instana.tracer.in_span('excon-test') do connection.requests([request, request, request]) end diff --git a/test/instrumentation/graphql_test.rb b/test/instrumentation/graphql_test.rb index 7404359f..cd8b1c9b 100644 --- a/test/instrumentation/graphql_test.rb +++ b/test/instrumentation/graphql_test.rb @@ -122,7 +122,7 @@ def test_query } } - results = Instana.tracer.start_or_continue_trace('graphql-test') { Schema.execute(query) } + results = Instana.tracer.in_span('graphql-test') { Schema.execute(query) } query_span, root_span = *Instana.processor.queued_spans assert_equal expected_results, results.to_h @@ -200,7 +200,7 @@ def test_query_with_fragment } } - results = Instana.tracer.start_or_continue_trace('graphql-test') { Schema.execute(query) } + results = Instana.tracer.in_span('graphql-test') { Schema.execute(query) } query_span, root_span = *Instana.processor.queued_spans assert_equal expected_results, results.to_h @@ -244,7 +244,7 @@ def test_query_union_with_fragment } } - results = Instana.tracer.start_or_continue_trace('graphql-test') { Schema.execute(query) } + results = Instana.tracer.in_span('graphql-test') { Schema.execute(query) } query_span, root_span = *Instana.processor.queued_spans assert_equal expected_results, results.to_h @@ -278,7 +278,7 @@ def test_mutation } } - results = Instana.tracer.start_or_continue_trace('graphql-test') { Schema.execute(query) } + results = Instana.tracer.in_span('graphql-test') { Schema.execute(query) } query_span, root_span = *Instana.processor.queued_spans assert_equal expected_results, results.to_h diff --git a/test/instrumentation/grpc_test.rb b/test/instrumentation/grpc_test.rb index 33157803..85d3e734 100644 --- a/test/instrumentation/grpc_test.rb +++ b/test/instrumentation/grpc_test.rb @@ -45,7 +45,7 @@ def test_request_response clear_all! response = nil - Instana.tracer.start_or_continue_trace(:rpctests) do + Instana.tracer.in_span(:rpctests) do response = client_stub.ping( PingPongService::PingRequest.new(message: 'Hello World') ) @@ -123,7 +123,7 @@ def test_client_streamer clear_all! response = nil - Instana.tracer.start_or_continue_trace(:rpctests) do + Instana.tracer.in_span(:rpctests) do response = client_stub.ping_with_client_stream( (0..5).map do |index| PingPongService::PingRequest.new(message: index.to_s) @@ -168,7 +168,7 @@ def test_server_streamer clear_all! responses = [] - Instana.tracer.start_or_continue_trace(:rpctests) do + Instana.tracer.in_span(:rpctests) do responses = client_stub.ping_with_server_stream( PingPongService::PingRequest.new(message: 'Hello World') ) @@ -209,7 +209,7 @@ def test_bidi_streamer clear_all! responses = [] - Instana.tracer.start_or_continue_trace(:rpctests) do + Instana.tracer.in_span(:rpctests) do responses = client_stub.ping_with_bidi_stream( (0..5).map do |index| PingPongService::PingRequest.new(message: (index * 2).to_s) @@ -252,7 +252,7 @@ def test_bidi_streamer def test_request_response_failure clear_all! - Instana.tracer.start_or_continue_trace(:rpctests) do + Instana.tracer.in_span(:rpctests) do begin client_stub.fail_to_ping( PingPongService::PingRequest.new(message: 'Hello World')) rescue @@ -292,7 +292,7 @@ def test_request_response_failure def test_client_streamer_failure clear_all! - Instana.tracer.start_or_continue_trace(:rpctests) do + Instana.tracer.in_span(:rpctests) do begin client_stub.fail_to_ping_with_client_stream( (0..5).map do |index| @@ -337,7 +337,7 @@ def test_client_streamer_failure def test_server_streamer_failure clear_all! - Instana.tracer.start_or_continue_trace(:rpctests) do + Instana.tracer.in_span(:rpctests) do begin client_stub.fail_to_ping_with_server_stream( PingPongService::PingRequest.new(message: 'Hello World') @@ -379,7 +379,7 @@ def test_server_streamer_failure def test_bidi_streamer_failure clear_all! - Instana.tracer.start_or_continue_trace(:rpctests) do + Instana.tracer.in_span(:rpctests) do client_stub.fail_to_ping_with_bidi_stream( (0..5).map do |index| PingPongService::PingRequest.new(message: (index * 2).to_s) diff --git a/test/instrumentation/mongo_test.rb b/test/instrumentation/mongo_test.rb index 6c9b24ba..3be181f5 100644 --- a/test/instrumentation/mongo_test.rb +++ b/test/instrumentation/mongo_test.rb @@ -13,7 +13,7 @@ def teardown end def test_mongo - Instana.tracer.start_or_continue_trace(:'mongo-test') do + Instana.tracer.in_span(:'mongo-test') do client = Mongo::Client.new('mongodb://127.0.0.1:27017/instana') client[:people].delete_many({ name: /$S*/ }) client[:people].insert_many([{ _id: 1, name: "Stan" }]) diff --git a/test/instrumentation/net_http_test.rb b/test/instrumentation/net_http_test.rb index bd10fa93..41b389e8 100644 --- a/test/instrumentation/net_http_test.rb +++ b/test/instrumentation/net_http_test.rb @@ -19,7 +19,7 @@ def test_get_with_query clear_all! WebMock.allow_net_connect! - Instana.tracer.start_or_continue_trace(:"net-http-test") do + Instana.tracer.in_span(:"net-http-test") do Net::HTTP.get(URI('http://127.0.0.1:6511/?query_value=true')) end @@ -37,7 +37,7 @@ def test_get_without_query clear_all! WebMock.allow_net_connect! - Instana.tracer.start_or_continue_trace(:"net-http-test") do + Instana.tracer.in_span(:"net-http-test") do Net::HTTP.get(URI('http://127.0.0.1:6511/')) end @@ -76,7 +76,7 @@ def test_block_request req = Net::HTTP::Get.new(uri) response = nil - Instana.tracer.start_or_continue_trace('net-http-test') do + Instana.tracer.in_span('net-http-test') do Net::HTTP.start(req.uri.hostname, req.uri.port, :open_timeout => 1, :read_timeout => 1) do |http| response = http.request(req) end @@ -118,7 +118,7 @@ def test_basic_post_without_uri WebMock.allow_net_connect! response = nil - Instana.tracer.start_or_continue_trace('net-http-test') do + Instana.tracer.in_span('net-http-test') do http = Net::HTTP.new("127.0.0.1", 6511) response = http.request(Net::HTTP::Post.new("/")) end @@ -159,7 +159,7 @@ def test_request_with_dns_error WebMock.allow_net_connect! begin - Instana.tracer.start_or_continue_trace('net-http-error-test') do + Instana.tracer.in_span('net-http-error-test') do http = Net::HTTP.new("asdfasdf.asdfsadf", 80) http.request(Net::HTTP::Get.new("/blah")) end @@ -189,7 +189,7 @@ def test_request_with_5xx_response WebMock.allow_net_connect! response = nil - Instana.tracer.start_or_continue_trace('net-http-error-test') do + Instana.tracer.in_span('net-http-error-test') do http = Net::HTTP.new("127.0.0.1", 6511) response = http.request(Net::HTTP::Get.new("/error")) end diff --git a/test/instrumentation/rails_action_cable_test.rb b/test/instrumentation/rails_action_cable_test.rb index aad59a00..9dba00b4 100644 --- a/test/instrumentation/rails_action_cable_test.rb +++ b/test/instrumentation/rails_action_cable_test.rb @@ -42,7 +42,7 @@ def test_transmit_parent connection = mock_connection connection.instance_variable_set( :@instana_trace_context, - Instana::SpanContext.new('ABC', 'ABC') + Instana::SpanContext.new(trace_id: 'ABC', span_id: 'ABC') ) channel_klass = Class.new(ActionCable::Channel::Base) @@ -95,7 +95,7 @@ def test_action_parent connection = mock_connection connection.instance_variable_set( :@instana_trace_context, - Instana::SpanContext.new('ABC', 'ABC') + Instana::SpanContext.new(trace_id: 'ABC', span_id: 'ABC') ) channel_klass = Class.new(ActionCable::Channel::Base) do def sample diff --git a/test/instrumentation/rails_action_mailer_test.rb b/test/instrumentation/rails_action_mailer_test.rb index 731623f6..dd296592 100644 --- a/test/instrumentation/rails_action_mailer_test.rb +++ b/test/instrumentation/rails_action_mailer_test.rb @@ -39,7 +39,7 @@ def teardown end def test_mailer - Instana.tracer.start_or_continue_trace(:test) do + Instana.tracer.in_span(:test) do TestMailer.sample_email.deliver_now end diff --git a/test/instrumentation/rails_active_job_test.rb b/test/instrumentation/rails_active_job_test.rb index 5f18fc29..78cc1735 100644 --- a/test/instrumentation/rails_active_job_test.rb +++ b/test/instrumentation/rails_active_job_test.rb @@ -37,7 +37,7 @@ def test_enqueue_perform # ActiveJob::QueueAdapters::TestAdapter.new doesn't work for this test on any version less than 6 skip unless Rails::VERSION::MAJOR >= 6 - Instana.tracer.start_or_continue_trace(:peform_test) do + Instana.tracer.in_span(:peform_test) do SampleJob.perform_later("test_enqueue_perform") end diff --git a/test/instrumentation/rails_active_record_database_missing_test.rb b/test/instrumentation/rails_active_record_database_missing_test.rb index 69b26907..98fc109d 100644 --- a/test/instrumentation/rails_active_record_database_missing_test.rb +++ b/test/instrumentation/rails_active_record_database_missing_test.rb @@ -8,7 +8,7 @@ class RailsActiveRecordDatabaseMissingTest < Minitest::Test def setup skip unless ENV['DATABASE_URL'] - + clear_all! @old_url = ENV['DATABASE_URL'] SQLite3::Database.new('/tmp/test.db') ENV['DATABASE_URL'] = 'sqlite3:///tmp/test.db' @@ -29,13 +29,12 @@ def teardown def test_error_on_missing_database assert_raises(ActiveRecord::StatementInvalid) do - Instana::Tracer.start_or_continue_trace(:ar_test, {}) do + Instana.tracer.in_span(:ar_test, attributes: {}) do b = Block.new FileUtils.rm('/tmp/test.db') b.save! end end - spans = ::Instana.processor.queued_spans span = find_first_span_by_name(spans, :activerecord) diff --git a/test/instrumentation/rails_active_record_test.rb b/test/instrumentation/rails_active_record_test.rb index 14d120a3..afbc39ad 100644 --- a/test/instrumentation/rails_active_record_test.rb +++ b/test/instrumentation/rails_active_record_test.rb @@ -6,6 +6,7 @@ class RailsActiveRecordTest < Minitest::Test def setup + clear_all! skip unless ENV['DATABASE_URL'] @connection = ActiveRecord::Base.establish_connection(ENV['DATABASE_URL']) ActiveRecord::Migration.suppress_messages do @@ -28,7 +29,8 @@ def test_config_defaults end def test_create - Instana::Tracer.start_or_continue_trace(:ar_test, {}) do + # clear_all! + Instana.tracer.in_span(:ar_test, attributes: {}) do Block.create(name: 'core', color: 'blue') end @@ -42,10 +44,9 @@ def test_create def test_read Block.create(name: 'core', color: 'blue') - Instana::Tracer.start_or_continue_trace(:ar_test, {}) do + Instana.tracer.in_span(:ar_test, attributes: {}) do Block.find_by(name: 'core') end - spans = ::Instana.processor.queued_spans assert_equal 2, spans.length span = find_first_span_by_name(spans, :activerecord) @@ -58,7 +59,7 @@ def test_update Block.create(name: 'core', color: 'blue') b = Block.find_by(name: 'core') - Instana::Tracer.start_or_continue_trace(:ar_test, {}) do + Instana.tracer.in_span(:ar_test, attributes: {}) do b.color = 'red' b.save end @@ -74,7 +75,7 @@ def test_update def test_delete b = Block.create(name: 'core', color: 'blue') - Instana::Tracer.start_or_continue_trace(:ar_test, {}) do + Instana.tracer.in_span(:ar_test, attributes: {}) do b.delete end @@ -87,7 +88,7 @@ def test_delete end def test_raw - Instana::Tracer.start_or_continue_trace(:ar_test, {}) do + Instana.tracer.in_span(:ar_test, attributes: {}) do ActiveRecord::Base.connection.execute('SELECT 1') end @@ -101,7 +102,7 @@ def test_raw def test_raw_error assert_raises ActiveRecord::StatementInvalid do - Instana::Tracer.start_or_continue_trace(:ar_test, {}) do + Instana.tracer.in_span(:ar_test, attributes: {}) do ActiveRecord::Base.connection.execute('INVALID') end end diff --git a/test/instrumentation/redis_test.rb b/test/instrumentation/redis_test.rb index 95503bca..8e2a5f09 100644 --- a/test/instrumentation/redis_test.rb +++ b/test/instrumentation/redis_test.rb @@ -16,7 +16,7 @@ def setup def test_normal_call clear_all! - Instana.tracer.start_or_continue_trace(:redis_test) do + Instana.tracer.in_span(:redis_test) do @redis_client.set('hello', 'world') end @@ -49,7 +49,7 @@ def test_normal_call_as_root_exit_span def test_georadius clear_all! - Instana.tracer.start_or_continue_trace(:redis_test) do + Instana.tracer.in_span(:redis_test) do @redis_client.georadius('Sicily', '15', '37', '200', 'km', 'WITHCOORD', 'WITHDIST') end @@ -59,7 +59,7 @@ def test_georadius def test_normal_call_with_error clear_all! - Instana.tracer.start_or_continue_trace(:redis_test) do + Instana.tracer.in_span(:redis_test) do begin @redis_client.zadd('hello', 'invalid', 'value') rescue; end @@ -71,7 +71,7 @@ def test_normal_call_with_error def test_pipeline_call clear_all! - Instana.tracer.start_or_continue_trace(:redis_test) do + Instana.tracer.in_span(:redis_test) do @redis_client.pipelined do |pipeline| pipeline.set('hello', 'world') pipeline.set('other', 'world') @@ -84,7 +84,7 @@ def test_pipeline_call def test_pipeline_call_with_error clear_all! - Instana.tracer.start_or_continue_trace(:redis_test) do + Instana.tracer.in_span(:redis_test) do begin @redis_client.pipelined do |pipeline| pipeline.set('other', 'world') @@ -99,7 +99,7 @@ def test_pipeline_call_with_error def test_multi_call clear_all! - Instana.tracer.start_or_continue_trace(:redis_test) do + Instana.tracer.in_span(:redis_test) do @redis_client.multi do |multi| multi.set('hello', 'world') multi.set('other', 'world') @@ -112,7 +112,7 @@ def test_multi_call def test_multi_call_with_error clear_all! - Instana.tracer.start_or_continue_trace(:redis_test) do + Instana.tracer.in_span(:redis_test) do begin @redis_client.multi do |multi| multi.set('other', 'world') diff --git a/test/instrumentation/resque_test.rb b/test/instrumentation/resque_test.rb index fd8c3591..5c752330 100644 --- a/test/instrumentation/resque_test.rb +++ b/test/instrumentation/resque_test.rb @@ -19,7 +19,7 @@ def teardown end def test_enqueue - ::Instana.tracer.start_or_continue_trace(:'resque-client_test') do + ::Instana.tracer.in_span(:'resque-client_test') do ::Resque.enqueue(FastJob) end @@ -62,7 +62,7 @@ def test_enqueue_as_root_exit_span end def test_enqueue_to - ::Instana.tracer.start_or_continue_trace(:'resque-client_test') do + ::Instana.tracer.in_span(:'resque-client_test') do ::Resque.enqueue_to(:critical, FastJob) end @@ -84,7 +84,7 @@ def test_enqueue_to end def test_dequeue - ::Instana.tracer.start_or_continue_trace(:'resque-client_test', '', {}) do + ::Instana.tracer.in_span(:'resque-client_test') do ::Resque.dequeue(FastJob, { :generate => :farfalla }) end @@ -102,7 +102,7 @@ def test_dequeue end def test_worker_job - ::Instana.tracer.start_or_continue_trace(:'resque-client_test') do + ::Instana.tracer.in_span(:'resque-client_test') do ::Resque.enqueue_to(:critical, FastJob) end @@ -135,7 +135,7 @@ def test_worker_job def test_worker_job_no_propagate ::Instana.config[:'resque-client'][:propagate] = false - ::Instana.tracer.start_or_continue_trace(:'resque-client_test') do + ::Instana.tracer.in_span(:'resque-client_test') do ::Resque.enqueue_to(:critical, FastJob) end diff --git a/test/instrumentation/rest_client_test.rb b/test/instrumentation/rest_client_test.rb index 2a44805a..1ae33a29 100644 --- a/test/instrumentation/rest_client_test.rb +++ b/test/instrumentation/rest_client_test.rb @@ -26,7 +26,7 @@ def test_basic_get url = "http://127.0.0.1:6511/" - Instana.tracer.start_or_continue_trace('restclient-test') do + Instana.tracer.in_span('restclient-test') do RestClient.get url end @@ -81,7 +81,6 @@ def test_basic_get_as_root_exit_span rack_span = find_first_span_by_name(spans, :rack) rest_span = find_first_span_by_name(spans, :'rest-client') net_span = find_first_span_by_name(spans, :'net-http') - # Span name validation assert_equal :rack, rack_span[:n] assert_equal :sdk, rest_span[:n] diff --git a/test/instrumentation/sequel_test.rb b/test/instrumentation/sequel_test.rb index 54ba934b..9d46f414 100644 --- a/test/instrumentation/sequel_test.rb +++ b/test/instrumentation/sequel_test.rb @@ -28,7 +28,8 @@ def test_config_defaults end def test_create - Instana::Tracer.start_or_continue_trace(:sequel_test, {}) do + clear_all! + Instana.tracer.in_span(:sequel_test, attributes: {}) do @model.insert(name: 'core', color: 'blue') end spans = ::Instana.processor.queued_spans @@ -39,8 +40,9 @@ def test_create end def test_read + clear_all! @model.insert(name: 'core', color: 'blue') - Instana::Tracer.start_or_continue_trace(:sequel_test, {}) do + Instana.tracer.in_span(:sequel_test, attributes: {}) do @model.where(name: 'core').first end spans = ::Instana.processor.queued_spans @@ -52,8 +54,9 @@ def test_read end def test_update + clear_all! @model.insert(name: 'core', color: 'blue') - Instana::Tracer.start_or_continue_trace(:sequel_test, {}) do + Instana.tracer.in_span(:sequel_test, attributes: {}) do @model.where(name: 'core').update(color: 'red') end spans = ::Instana.processor.queued_spans @@ -66,8 +69,9 @@ def test_update end def test_delete + clear_all! @model.insert(name: 'core', color: 'blue') - Instana::Tracer.start_or_continue_trace(:sequel_test, {}) do + Instana.tracer.in_span(:sequel_test, attributes: {}) do @model.where(name: 'core').delete end spans = ::Instana.processor.queued_spans @@ -80,7 +84,8 @@ def test_delete end def test_raw - Instana::Tracer.start_or_continue_trace(:sequel_test, {}) do + clear_all! + Instana.tracer.in_span(:sequel_test, attributes: {}) do @db.run('SELECT 1') end spans = ::Instana.processor.queued_spans @@ -92,8 +97,9 @@ def test_raw end def test_raw_error + clear_all! assert_raises Sequel::DatabaseError do - Instana::Tracer.start_or_continue_trace(:sequel_test, {}) do + Instana.tracer.in_span(:sequel_test, attributes: {}) do @db.run('INVALID') end end diff --git a/test/instrumentation/sidekiq-client_test.rb b/test/instrumentation/sidekiq-client_test.rb index fb5c4ff1..dc936b18 100644 --- a/test/instrumentation/sidekiq-client_test.rb +++ b/test/instrumentation/sidekiq-client_test.rb @@ -21,7 +21,7 @@ def test_config_defaults def test_enqueue clear_all! - Instana.tracer.start_or_continue_trace(:sidekiqtests) do + Instana.tracer.in_span(:sidekiqtests) do disable_redis_instrumentation ::Sidekiq::Client.push( 'queue' => 'some_random_queue', @@ -72,7 +72,7 @@ def test_enqueue_as_root_exit_span def test_enqueue_failure clear_all! - Instana.tracer.start_or_continue_trace(:sidekiqtests) do + Instana.tracer.in_span(:sidekiqtests) do disable_redis_instrumentation add_sidekiq_exception_middleware begin diff --git a/test/instrumentation/sidekiq-worker_test.rb b/test/instrumentation/sidekiq-worker_test.rb index 90845dd6..b1ac9646 100644 --- a/test/instrumentation/sidekiq-worker_test.rb +++ b/test/instrumentation/sidekiq-worker_test.rb @@ -77,7 +77,7 @@ def test_successful_worker_continues_previous_trace $sidekiq_mode = :server inject_instrumentation - Instana.tracer.start_or_continue_trace(:sidekiqtests) do + Instana.tracer.in_span(:sidekiqtests) do disable_redis_instrumentation ::Sidekiq::Client.push( 'queue' => 'important', @@ -110,7 +110,7 @@ def test_failed_worker_continues_previous_trace $sidekiq_mode = :server inject_instrumentation - Instana.tracer.start_or_continue_trace(:sidekiqtests) do + Instana.tracer.in_span(:sidekiqtests) do disable_redis_instrumentation ::Sidekiq::Client.push( 'queue' => 'important', diff --git a/test/tracing/custom_test.rb b/test/trace/custom_test.rb similarity index 82% rename from test/tracing/custom_test.rb rename to test/trace/custom_test.rb index 6446d5e7..4e8d7b97 100644 --- a/test/tracing/custom_test.rb +++ b/test/trace/custom_test.rb @@ -9,11 +9,12 @@ def test_custom_tracing assert_equal false, ::Instana.tracer.tracing? # Start tracing - ::Instana.tracer.log_start_or_continue(:custom_trace, {:one => 1}) + span = ::Instana.tracer.start_span(:custom_trace, attributes: {:one => 1}) assert_equal true, ::Instana.tracer.tracing? - ::Instana.tracer.log_info({:info_logged => 1}) + span.add_attributes({:info_logged => 1}) # End tracing - ::Instana.tracer.log_end(:custom_trace, {:close_one => 1}) + span.add_attributes({:close_one => 1}) + span.finish assert_equal false, ::Instana.tracer.tracing? spans = ::Instana.processor.queued_spans @@ -23,7 +24,7 @@ def test_custom_tracing assert_equal :sdk, first_span[:n] assert first_span[:ts].is_a?(Integer) - assert first_span[:ts] > 0 + assert first_span[:ts].positive? assert first_span[:d].is_a?(Integer) assert first_span[:d].between?(0, 5) @@ -50,21 +51,21 @@ def test_custom_tracing_with_nested_automagic kvs = {} kvs[:on_entry_kv] = 1 - kvs[:arguments] = [[1,2,3], "test_arg", :ok] + kvs[:arguments] = [[1, 2, 3], "test_arg", :ok] kvs[:return] = true # Start tracing - ::Instana.tracer.log_start_or_continue(:rack, :on_trace_start => 1) + span = ::Instana.tracer.start_span(:rack, attributes: {:on_trace_start => 1}) assert_equal true, ::Instana.tracer.tracing? # Now the automagic - ::Instana.tracer.trace(:custom_span, kvs) do + ::Instana.tracer.in_span(:custom_span, attributes: kvs) do answer = 42 * 1 active_span = ::Instana.tracer.current_span active_span.set_tag(:answer, answer) # And now nested automagic - ::Instana.tracer.trace(:custom_span2, kvs) do + ::Instana.tracer.in_span(:custom_span2, attributes: kvs) do was_here = 'stan' active_span = ::Instana.tracer.current_span active_span.set_tag(:was_here, was_here) @@ -72,7 +73,8 @@ def test_custom_tracing_with_nested_automagic end # End tracing - ::Instana.tracer.log_end(:rack, {:on_trace_end => 1}) + span.add_attributes({:on_trace_end => 1}) + span.finish assert_equal false, ::Instana.tracer.tracing? spans = ::Instana.processor.queued_spans @@ -111,20 +113,23 @@ def test_custom_tracing_with_args assert_equal false, ::Instana.tracer.tracing? # Start tracing - ::Instana.tracer.log_start_or_continue(:rack, :on_trace_start => 1) + span1 = ::Instana.tracer.start_span(:rack, attributes: {:on_trace_start => 1}) assert_equal true, ::Instana.tracer.tracing? kvs = {} kvs[:on_entry_kv] = 1 - kvs[:arguments] = [[1,2,3], "test_arg", :ok] + kvs[:arguments] = [[1, 2, 3], "test_arg", :ok] kvs[:return] = true - ::Instana.tracer.log_entry(:custom_span, kvs) - ::Instana.tracer.log_info({:on_info_kv => 1}) - ::Instana.tracer.log_exit(:custom_span, :on_exit_kv => 1) + span2 = ::Instana.tracer.start_span(:custom_span, attributes: kvs) + span2.set_tags({:on_info_kv => 1}) + span2.set_tags({:on_exit_kv => 1}) + span2.finish # End tracing - ::Instana.tracer.log_end(:rack, {:on_trace_end => 1}) + span1.set_tags({:on_trace_end => 1}) + span1.finish + assert_equal false, ::Instana.tracer.tracing? spans = ::Instana.processor.queued_spans @@ -134,7 +139,7 @@ def test_custom_tracing_with_args second_span = find_first_span_by_name(spans, :custom_span) assert first_span[:ts].is_a?(Integer) - assert first_span[:ts] > 0 + assert first_span[:ts].positive? assert first_span[:d].is_a?(Integer) assert first_span[:d].between?(0, 5) @@ -159,28 +164,30 @@ def test_custom_tracing_with_args assert_equal 1, second_span[:data][:sdk][:custom][:tags][:on_exit_kv] end - def test_custom_tracing_with_error + def test_custom_tracing_with_error # rubocop:disable Metrics/MethodLength clear_all! assert_equal false, ::Instana.tracer.tracing? # Start tracing - ::Instana.tracer.log_start_or_continue(:rack, :on_trace_start => 1) + span1 = ::Instana.tracer.start_span(:rack, attributes: {:on_trace_start => 1}) assert_equal true, ::Instana.tracer.tracing? begin kvs = {} kvs[:on_entry_kv] = 1 - kvs[:arguments] = [[1,2,3], "test_arg", :ok] + kvs[:arguments] = [[1, 2, 3], "test_arg", :ok] kvs[:return] = true - ::Instana.tracer.log_entry(:custom_span, kvs) + span2 = ::Instana.tracer.start_span(:custom_span, attributes: kvs) raise "custom tracing error. This is only a test" rescue => e - ::Instana.tracer.log_error(e) + span2.record_exception(e) ensure - ::Instana.tracer.log_exit(:custom_span, :on_exit_kv => 1) + span2.set_tags(:on_exit_kv => 1) + span2.finish end - ::Instana.tracer.log_end(:rack, {:on_trace_end => 1}) + span1.set_tags(:on_trace_end => 1) + span1.finish assert_equal false, ::Instana.tracer.tracing? spans = ::Instana.processor.queued_spans @@ -190,7 +197,7 @@ def test_custom_tracing_with_error second_span = find_first_span_by_name(spans, :custom_span) assert first_span[:ts].is_a?(Integer) - assert first_span[:ts] > 0 + assert first_span[:ts].positive? assert first_span[:d].is_a?(Integer) assert first_span[:d].between?(0, 5) @@ -202,7 +209,7 @@ def test_custom_tracing_with_error assert_equal 1, first_span[:data][:on_trace_end] assert second_span[:ts].is_a?(Integer) - assert second_span[:ts] > 0 + assert second_span[:ts].positive? assert second_span[:d].is_a?(Integer) assert second_span[:d].between?(0, 5) diff --git a/test/tracing/id_management_test.rb b/test/trace/id_management_test.rb similarity index 99% rename from test/tracing/id_management_test.rb rename to test/trace/id_management_test.rb index 31aa8b89..a4e564cd 100644 --- a/test/tracing/id_management_test.rb +++ b/test/trace/id_management_test.rb @@ -1,5 +1,3 @@ - - # (c) Copyright IBM Corp. 2021 # (c) Copyright Instana Inc. 2016 diff --git a/test/tracing/instrumented_logger_test.rb b/test/trace/instrumented_logger_test.rb similarity index 93% rename from test/tracing/instrumented_logger_test.rb rename to test/trace/instrumented_logger_test.rb index 0f14dd1b..94abaf28 100644 --- a/test/tracing/instrumented_logger_test.rb +++ b/test/trace/instrumented_logger_test.rb @@ -11,7 +11,7 @@ def setup def test_log_warn_error subject = Instana::InstrumentedLogger.new('/dev/null') - Instana::Tracer.start_or_continue_trace(:test_logging) do + Instana::Tracer.in_span(:test_logging) do subject.warn('warn') subject.debug('test') subject.error('error') diff --git a/test/tracing/processor_test.rb b/test/trace/processor_test.rb similarity index 80% rename from test/tracing/processor_test.rb rename to test/trace/processor_test.rb index b8500187..6b034c85 100644 --- a/test/tracing/processor_test.rb +++ b/test/trace/processor_test.rb @@ -13,12 +13,12 @@ def test_queued_spans_valid_level clear_all! subject = Instana::Processor.new - span_context = Instana::SpanContext.new('9', '8', 0) - span = Instana::Span.new(:rack, parent_ctx: span_context) + span_context = Instana::SpanContext.new(trace_id: '9', span_id: '8', level: 0) + span = Instana::Span.new(:rack, span_context) span2 = Instana::Span.new(:"net-http") - subject.add_span(span) - subject.add_span(span2) + subject.on_finish(span) + subject.on_finish(span2) spans = subject.queued_spans valid_span, = spans @@ -29,7 +29,7 @@ def test_queued_spans_valid_level def test_queued_spans_invalid_type subject = Instana::Processor.new - subject.add_span(false) + subject.on_finish(false) assert_equal [], subject.queued_spans end @@ -39,7 +39,7 @@ def test_send subject = Instana::Processor.new span = Instana::Span.new(:rack) - subject.add_span(span) + subject.on_finish(span) was_invoked = false diff --git a/test/tracing/span_context_test.rb b/test/trace/span_context_test.rb similarity index 64% rename from test/tracing/span_context_test.rb rename to test/trace/span_context_test.rb index 46a6d5dd..f275f5e3 100644 --- a/test/tracing/span_context_test.rb +++ b/test/trace/span_context_test.rb @@ -5,17 +5,17 @@ class SpanContextTest < Minitest::Test def test_to_hash - subject = Instana::SpanContext.new('trace', 'span') + subject = Instana::SpanContext.new(trace_id: 'trace', span_id: 'span') assert_equal({trace_id: 'trace', span_id: 'span'}, subject.to_hash) end def test_invalid - subject = Instana::SpanContext.new(nil, nil) + subject = Instana::SpanContext.new(trace_id: nil, span_id: nil) refute subject.valid? end def test_flags_level_zero - subject = Instana::SpanContext.new('trace', 'span', 0, {external_state: 'cn=test'}) + subject = Instana::SpanContext.new(trace_id: 'trace', span_id: 'span', level: 0, baggage: {external_state: 'cn=test'}) assert_equal '00-000000000000000000000000000trace-000000000000span-00', subject.trace_parent_header assert_equal 'cn=test', subject.trace_state_header end diff --git a/test/tracing/span_test.rb b/test/trace/span_test.rb similarity index 91% rename from test/tracing/span_test.rb rename to test/trace/span_test.rb index 38dd213a..e3d21593 100644 --- a/test/tracing/span_test.rb +++ b/test/trace/span_test.rb @@ -39,15 +39,15 @@ def test_exit_span end def test_span_from_contetx - context = Instana::SpanContext.new('test', 'test', 0) - span = Instana::Span.new(:test, parent_ctx: context) + context = Instana::SpanContext.new(trace_id: 'test', span_id: 'test', level: 0) + span = Instana::Span.new(:test, context) assert_equal 'test', span.parent_id assert_equal 'test', span.trace_id end def test_span_from_contetx_invalid - context = Instana::SpanContext.new(nil, nil, 1) + context = Instana::SpanContext.new(trace_id: nil, span_id: nil, level: 1) span = Instana::Span.new(:test, parent_ctx: context) assert_nil span.parent_id @@ -93,16 +93,16 @@ def test_multiple_errors span = Instana::Span.new(:activerecord) span.set_tag(:activerecord, {}) - span.add_error(StandardError.new('Test1')) - span.add_error(StandardError.new('Test2')) + span.record_exception(StandardError.new('Test1')) + span.record_exception(StandardError.new('Test2')) assert_equal 2, span[:ec] assert_equal 'Test2', span[:data][:activerecord][:error] end - def test_add_error_nil + def test_record_exception_nil span = Instana::Span.new(:activerecord) - span.add_error(nil) + span.record_exception(nil) assert_equal 1, span[:ec] end diff --git a/test/tracing/tracer_async_test.rb b/test/trace/tracer_async_test.rb similarity index 72% rename from test/tracing/tracer_async_test.rb rename to test/trace/tracer_async_test.rb index fb28282a..360ec7a5 100644 --- a/test/tracing/tracer_async_test.rb +++ b/test/trace/tracer_async_test.rb @@ -8,7 +8,7 @@ def test_same_thread_async_tracing clear_all! # Start tracing - ::Instana.tracer.log_start_or_continue(:rack, {:rack_start_kv => 1}) + ::Instana.tracer.start_span(:rack, attributes: {:rack_start_kv => 1}) # Start an asynchronous span span = ::Instana.tracer.log_async_entry(:my_async_op, { :entry_kv => 1}) @@ -54,26 +54,33 @@ def test_diff_thread_async_tracing clear_all! # Start tracing - ::Instana.tracer.log_start_or_continue(:rack, {:rack_start_kv => 1}) + span = ::Instana.tracer.start_span(:rack, attributes: {:rack_start_kv => 1}) t_context = ::Instana.tracer.context refute_nil t_context.trace_id refute_nil t_context.span_id Thread.new do - ::Instana.tracer.log_start_or_continue(:async_thread, { :async_start => 1 }, t_context) - ::Instana.tracer.log_entry(:sleepy_time, { :tired => 1 }) + span1 = ::Instana::Trace.with_span(OpenTelemetry::Trace.non_recording_span(t_context)) do + ::Instana.tracer.start_span(:async_thread, attributes: { :async_start => 1 }) + end + span2 = ::Instana::Trace.with_span(span1) do + ::Instana.tracer.start_span(:sleepy_time, attributes: { :tired => 1 }) + end + # Sleep beyond the end of this root trace sleep 0.5 - ::Instana.tracer.log_exit(:sleepy_time, { :wake_up => 1}) - ::Instana.tracer.log_end(:async_thread, { :async_end => 1 }) + span2.set_tags({ :wake_up => 1}) + span2.finish + span1.set_tags({:async_end => 1}) + span1.finish end - # Current span should still be rack assert_equal :rack, ::Instana.tracer.current_span.name - + span.set_tags({:rack_end_kv => 1}) + span.finish # End tracing - ::Instana.tracer.log_end(:rack, {:rack_end_kv => 1}) + # ::Instana.tracer.log_end(:rack, {:rack_end_kv => 1}) assert_equal false, ::Instana.tracer.tracing? @@ -106,15 +113,14 @@ def test_diff_thread_async_tracing assert async_span2[:d] assert_equal 1, async_span2[:data][:sdk][:custom][:tags][:tired] assert_equal 1, async_span2[:data][:sdk][:custom][:tags][:wake_up] - # Validate linkage # All spans have the same trace ID - assert rack_span[:t]==async_span1[:t] && async_span1[:t]==async_span2[:t] + assert rack_span[:t] == async_span1[:t] && async_span1[:t] == async_span2[:t] assert_equal async_span2[:p], async_span1[:s] assert_equal async_span1[:p], rack_span[:s] - assert rack_span[:t] == rack_span[:s] + assert rack_span[:t] == rack_span[:s] assert async_span1[:t] != async_span1[:s] assert async_span2[:t] != async_span2[:s] end @@ -123,15 +129,19 @@ def test_out_of_order_async_tracing clear_all! # Start tracing - ::Instana.tracer.log_start_or_continue(:rack, {:rack_start_kv => 1}) + span = ::Instana.tracer.start_span(:rack, attributes: {:rack_start_kv => 1}) # Start three asynchronous spans - span1 = ::Instana.tracer.log_async_entry(:my_async_op1, { :entry_kv => 1}) - span2 = ::Instana.tracer.log_async_entry(:my_async_op2, { :entry_kv => 2}) - span3 = ::Instana.tracer.log_async_entry(:my_async_op3, { :entry_kv => 3}) + span1, span2, span3 = ::Instana::Trace.with_span(span) do + span1 = ::Instana.tracer.start_span(:my_async_op1, attributes: { :entry_kv => 1}) + span2 = ::Instana.tracer.start_span(:my_async_op2, attributes: { :entry_kv => 2}) + span3 = ::Instana.tracer.start_span(:my_async_op3, attributes: { :entry_kv => 3}) + return span1, span2, span3 + end + # Context awareness when using Opentelemetry is done through Opentelemetry::Context so the below test is invalid # Current span should still be rack - assert_equal :rack, ::Instana.tracer.current_span.name + # assert_equal :rack, ::Instana.tracer.current_span.name # Log info to the async spans (out of order) span2.set_tags({ :info_kv => 2 }) @@ -139,8 +149,8 @@ def test_out_of_order_async_tracing span3.set_tags({ :info_kv => 3 }) # Log out of order errors to the async spans - span3.add_error(Exception.new("Async span 3")) - span2.add_error(Exception.new("Async span 3")) + span3.record_exception(Exception.new("Async span 3")) + span2.record_exception(Exception.new("Async span 3")) # End two out of order asynchronous spans span3.set_tags({ :exit_kv => 3 }) @@ -148,14 +158,16 @@ def test_out_of_order_async_tracing span2.set_tags({ :exit_kv => 2 }) span2.close + # Context awareness when using Opentelemetry is done through Opentelemetry::Context so the below test is invalid # Current span should still be rack - assert_equal :rack, ::Instana.tracer.current_span.name + # assert_equal :rack, ::Instana.tracer.current_span.name # End tracing - ::Instana.tracer.log_end(:rack, {:rack_end_kv => 1}) + span.set_tags({:rack_end_kv => 1}) + span.finish # Log an error to and close out the remaining async span after the parent trace has finished - span1.add_error(Exception.new("Async span 1")) + span1.record_exception(Exception.new("Async span 1")) span1.set_tags({ :exit_kv => 1 }) span1.close @@ -201,12 +213,12 @@ def test_out_of_order_async_tracing def test_async_helpers clear_all! - ::Instana.tracer.log_start_or_continue(:rack) + ::Instana.tracer.start_span(:rack) - span = ::Instana.tracer.log_async_entry(:async, {}) - ::Instana.tracer.log_async_info({a: 1}, span) - ::Instana.tracer.log_async_error(StandardError.new('Error'), span) - ::Instana.tracer.log_async_exit(nil, {}, span) + span1 = ::Instana.tracer.start_span(:async, attributes: {}) + span1.set_tags({a: 1}) + span1.record_exception(StandardError.new('Error')) + span1.finish spans = ::Instana.processor.queued_spans span, = spans @@ -217,10 +229,11 @@ def test_async_helpers def test_async_helpers_tag_exit clear_all! - ::Instana.tracer.log_start_or_continue(:rack) + ::Instana.tracer.start_span(:rack) - span = ::Instana.tracer.log_async_entry(:async, {}) - ::Instana.tracer.log_async_exit(nil, {a: 1}, span) + span1 = ::Instana.tracer.start_span(:async, attributes: {}) + span1.set_tags({a: 1}) + span1.finish spans = ::Instana.processor.queued_spans span, = spans diff --git a/test/trace/tracer_provider_test.rb b/test/trace/tracer_provider_test.rb new file mode 100644 index 00000000..cc79e140 --- /dev/null +++ b/test/trace/tracer_provider_test.rb @@ -0,0 +1,148 @@ +# (c) Copyright IBM Corp. 2025 +# (c) Copyright Instana Inc. 2025 + +require 'test_helper' +require 'instana/trace/tracer_provider' +require 'instana/trace/export' + +class TracerProviderTest < Minitest::Test + def setup + @tracer_provider = Instana.tracer_provider + end + + def test_tracer + # This tests the global tracer is the same as tracer from tracer_provider + assert_equal Instana.tracer, @tracer_provider.tracer("instana_tracer") + end + + def test_shutdown_with_timeout + @tracer_provider = ::Instana::Trace::TracerProvider.new + @tracer_provider.add_span_processor(DummyProcessor.new) + timeout = 10 + result = @tracer_provider.shutdown(timeout: timeout) + assert_equal Instana::Trace::Export::SUCCESS, result + @span_processors = @tracer_provider.instance_variable_get(:@span_processors) + assert_equal 1, @tracer_provider.instance_variable_get(:@span_processors).length + assert_equal [Instana::Trace::Export::SUCCESS], @tracer_provider.instance_variable_get(:@span_processors).map(&:shutdown) + assert @tracer_provider.instance_variable_get(:@stopped) + end + + def test_shutdown_without_timeout + @tracer_provider = ::Instana::Trace::TracerProvider.new + @tracer_provider.add_span_processor(DummyProcessor.new) + # @tracer = @tracer_provider.tracer('test_shutdown_without_timeout') + result = @tracer_provider.shutdown + assert_equal Instana::Trace::Export::SUCCESS, result + + assert_equal 1, @tracer_provider.instance_variable_get(:@span_processors).length + assert_equal [Instana::Trace::Export::SUCCESS], @tracer_provider.instance_variable_get(:@span_processors).map(&:shutdown) + assert @tracer_provider.instance_variable_get(:@stopped) + end + + def test_shutdown_called_multiple_times + @tracer_provider = ::Instana::Trace::TracerProvider.new + @tracer_provider.add_span_processor(DummyProcessor.new) + # @tracer = @tracer_provider.tracer('test_shutdown_called_multiple_times') + + result1 = @tracer_provider.shutdown + result2 = @tracer_provider.shutdown + + assert_equal Instana::Trace::Export::SUCCESS, result1 + assert_equal Instana::Trace::Export::FAILURE, result2 + + assert_equal 1, @tracer_provider.instance_variable_get(:@span_processors).length + assert_equal [Instana::Trace::Export::SUCCESS], @tracer_provider.instance_variable_get(:@span_processors).map(&:shutdown) + assert @tracer_provider.instance_variable_get(:@stopped) + end + + def test_shutdown_with_zero_timeout + @tracer_provider = ::Instana::Trace::TracerProvider.new + @tracer_provider.add_span_processor(DummyProcessor.new) + # @tracer = @tracer_provider.tracer('test_shutdown_with_zero_timeout') + timeout = 0 + result = @tracer_provider.shutdown(timeout: timeout) + assert_equal Instana::Trace::Export::TIMEOUT, result + + assert_equal 1, @tracer_provider.instance_variable_get(:@span_processors).length + assert_equal [Instana::Trace::Export::SUCCESS], @tracer_provider.instance_variable_get(:@span_processors).map(&:shutdown) + assert @tracer_provider.instance_variable_get(:@stopped) + end + + def test_force_flush_with_timeout + @tracer_provider = ::Instana::Trace::TracerProvider.new + @tracer_provider.add_span_processor(DummyProcessor.new) + timeout = 10 + result = @tracer_provider.force_flush(timeout: timeout) + assert_equal Instana::Trace::Export::SUCCESS, result + @span_processors = @tracer_provider.instance_variable_get(:@span_processors) + assert_equal 1, @tracer_provider.instance_variable_get(:@span_processors).length + assert_equal [Instana::Trace::Export::SUCCESS], @tracer_provider.instance_variable_get(:@span_processors).map(&:shutdown) + end + + def test_force_flush_without_timeout + @tracer_provider = ::Instana::Trace::TracerProvider.new + @tracer_provider.add_span_processor(DummyProcessor.new) + result = @tracer_provider.force_flush + assert_equal Instana::Trace::Export::SUCCESS, result + + assert_equal 1, @tracer_provider.instance_variable_get(:@span_processors).length + assert_equal [Instana::Trace::Export::SUCCESS], @tracer_provider.instance_variable_get(:@span_processors).map(&:shutdown) + end + + def test_force_flush_with_zero_timeout + @tracer_provider = ::Instana::Trace::TracerProvider.new + @tracer_provider.add_span_processor(DummyProcessor.new) + timeout = 0 + result = @tracer_provider.force_flush(timeout: timeout) + assert_equal Instana::Trace::Export::TIMEOUT, result + + assert_equal 1, @tracer_provider.instance_variable_get(:@span_processors).length + assert_equal [Instana::Trace::Export::SUCCESS], @tracer_provider.instance_variable_get(:@span_processors).map(&:shutdown) + end + + def test_add_span_processor_after_shutdown + @tracer_provider = ::Instana::Trace::TracerProvider.new + @tracer_provider.add_span_processor(DummyProcessor.new) + @tracer_provider.shutdown + result = @tracer_provider.add_span_processor(DummyProcessor.new) + assert_nil result + # No new span processor was added as tracer_provider is stopped + assert_equal 1, @tracer_provider.instance_variable_get(:@span_processors).length + end + + def test_internal_start_span_untraced + @tracer_provider = ::Instana::Trace::TracerProvider.new + Minitest::Mock.new + result = @tracer_provider.internal_start_span('test_span', 'kind', {}, [], Time.now, nil, @instrumentation_scope) + assert_instance_of(Instana::Span, result) + # Todo add proper testcase + end + + def test_internal_start_span_traced + @tracer_provider = ::Instana::Trace::TracerProvider.new + Minitest::Mock.new + result = @tracer_provider.internal_start_span('test_span', 'kind', {}, [], Time.now, nil, @instrumentation_scope) + assert_instance_of(Instana::Span, result) + # Todo add proper testcase + end + + def test_internal_start_span_stopped + @tracer_provider = ::Instana::Trace::TracerProvider.new + Minitest::Mock.new + result = @tracer_provider.internal_start_span('test_span', 'kind', {}, [], Time.now, nil, @instrumentation_scope) + assert_instance_of(Instana::Span, result) + # Todo add proper testcase + end +end + +class DummyProcessor + def initialize; end + + def shutdown(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument + Instana::Trace::Export::SUCCESS + end + + def force_flush(timeout: nil) # rubocop:disable Lint/UnusedMethodArgument + Instana::Trace::Export::SUCCESS + end +end diff --git a/test/tracing/tracer_test.rb b/test/trace/tracer_test.rb similarity index 77% rename from test/tracing/tracer_test.rb rename to test/trace/tracer_test.rb index 0d0be892..983bd03b 100644 --- a/test/tracing/tracer_test.rb +++ b/test/trace/tracer_test.rb @@ -15,7 +15,7 @@ def test_obey_tracing_config ::Instana.config[:tracing][:enabled] = false assert_equal false, ::Instana.tracer.tracing? - ::Instana.tracer.start_or_continue_trace(:rack, {:one => 1}) do + ::Instana.tracer.in_span(:rack, attributes: {:one => 1}) do assert_equal false, ::Instana.tracer.tracing? end @@ -27,7 +27,7 @@ def test_basic_trace_block assert_equal false, ::Instana.tracer.tracing? - ::Instana.tracer.start_or_continue_trace(:rack, {:one => 1}) do + ::Instana.tracer.in_span(:rack, attributes: {:one => 1}) do assert_equal true, ::Instana.tracer.tracing? sleep 0.1 end @@ -53,7 +53,7 @@ def test_exotic_tag_types ipv4 = '111.111.111.111' - ::Instana.tracer.start_or_continue_trace(:rack, {:ipaddr => ipv4}) do + ::Instana.tracer.in_span(:rack, attributes: {:ipaddr => ipv4}) do assert_equal true, ::Instana.tracer.tracing? sleep 0.1 end @@ -77,10 +77,10 @@ def test_errors_are_properly_propagated clear_all! exception_raised = false begin - ::Instana.tracer.start_or_continue_trace(:rack, {:one => 1}) do - raise Exception.new('Error in block - this should continue to propogate outside of tracing') + ::Instana.tracer.in_span(:rack, attributes: {:one => 1}) do + raise StandardError, 'Error in block - this should continue to propogate outside of tracing' end - rescue Exception + rescue StandardError exception_raised = true end @@ -92,7 +92,7 @@ def test_errors_are_properly_propagated first_span = spans.first assert_equal :rack, first_span[:n] assert first_span[:ts].is_a?(Integer) - assert first_span[:ts] > 0 + assert first_span[:ts].positive? assert first_span[:d].is_a?(Integer) assert first_span[:d].between?(0, 5) assert first_span.key?(:data) @@ -105,9 +105,9 @@ def test_errors_are_properly_propagated def test_complex_trace_block clear_all! - ::Instana.tracer.start_or_continue_trace(:rack, {:one => 1}) do + ::Instana.tracer.in_span(:rack, attributes: {:one => 1}) do sleep 0.2 - ::Instana.tracer.trace(:sub_block, {:sub_two => 2}) do + ::Instana.tracer.in_span(:sub_block, attributes: {:sub_two => 2}) do sleep 0.2 end end @@ -132,9 +132,9 @@ def test_complex_trace_block def test_custom_complex_trace_block clear_all! - ::Instana.tracer.start_or_continue_trace(:root_span, {:one => 1}) do + ::Instana.tracer.in_span(:root_span, attributes: {:one => 1}) do sleep 0.2 - ::Instana.tracer.trace(:sub_span, {:sub_two => 2}) do + ::Instana.tracer.in_span(:sub_span, attributes: {:sub_two => 2}) do sleep 0.2 end end @@ -167,11 +167,12 @@ def test_basic_low_level_tracing assert_equal false, ::Instana.tracer.tracing? # Start tracing - ::Instana.tracer.log_start_or_continue(:rack, {:one => 1}) + span = ::Instana.tracer.start_span(:rack, attributes: {:one => 1}) assert_equal true, ::Instana.tracer.tracing? - ::Instana.tracer.log_info({:info_logged => 1}) + span.set_tags({:info_logged => 1}) # End tracing - ::Instana.tracer.log_end(:rack, {:close_one => 1}) + span.set_tags({:close_one => 1}) + span.finish assert_equal false, ::Instana.tracer.tracing? spans = ::Instana.processor.queued_spans @@ -184,20 +185,25 @@ def test_complex_low_level_tracing assert_equal false, ::Instana.tracer.tracing? # Start tracing - ::Instana.tracer.log_start_or_continue(:rack, {:one => 1}) + span = ::Instana.tracer.start_span(:rack, attributes: {:one => 1}) assert_equal true, ::Instana.tracer.tracing? - ::Instana.tracer.log_info({:info_logged => 1}) + span.set_tags({:info_logged => 1}) - # Start tracing a sub span - ::Instana.tracer.log_entry(:sub_task) + # Start tracing a sub span with context propagation + span1 = ::Instana::Trace.with_span(span) do + ::Instana.tracer.start_span(:sub_task) + end assert_equal true, ::Instana.tracer.tracing? - ::Instana.tracer.log_info({:sub_task_info => 1}) + span1.set_tags({:sub_task_info => 1}) # Exit from the sub span - ::Instana.tracer.log_exit(:sub_task, {:sub_task_exit_info => 1}) + span1.set_tags({:sub_task_exit_info => 1}) + + span1.finish assert_equal true, ::Instana.tracer.tracing? # End tracing - ::Instana.tracer.log_end(:rack, {:close_one => 1}) + span.set_tags({:close_one => 1}) + span.finish assert_equal false, ::Instana.tracer.tracing? spans = ::Instana.processor.queued_spans @@ -227,12 +233,12 @@ def test_block_tracing_error_capture clear_all! exception_raised = false begin - ::Instana.tracer.start_or_continue_trace(:test_trace, {:one => 1}) do - ::Instana.tracer.trace(:test_trace_two) do - raise Exception.new("Block exception test error") + ::Instana.tracer.in_span(:test_trace, attributes: {:one => 1}) do + ::Instana.tracer.in_span(:test_trace_two) do + raise StandardError, "Block exception test error" end end - rescue Exception + rescue StandardError exception_raised = true end @@ -255,10 +261,14 @@ def test_block_tracing_error_capture def test_low_level_error_logging clear_all! - ::Instana.tracer.log_start_or_continue(:test_trace, {:one => 1}) - ::Instana.tracer.log_info({:info_logged => 1}) - ::Instana.tracer.log_error(Exception.new("Low level tracing api error")) - ::Instana.tracer.log_end(:test_trace, {:close_one => 1}) + span = ::Instana.tracer.start_span(:test_trace, attributes: {:one => 1}) + span.set_tags({:info_logged => 1}) + span.record_exception(Exception.new("Low level tracing api error")) + span.set_tags({:close_one => 1}) + span.finish + # ::Instana.tracer.log_info({:info_logged => 1}) + # ::Instana.tracer.log_error(Exception.new("Low level tracing api error")) + # ::Instana.tracer.log_end(:test_trace, {:close_one => 1}) spans = ::Instana.processor.queued_spans assert_equal 1, spans.length @@ -294,16 +304,17 @@ def test_tracing_span clear_all! refute ::Instana.tracer.tracing_span?(:rack) - ::Instana.tracer.log_start_or_continue(:rack) + ::Instana.tracer.start_span(:rack) assert ::Instana.tracer.tracing_span?(:rack) end def test_log_exit_warn_span_name logger = Minitest::Mock.new logger.expect(:warn, true, [String]) - subject = Instana::Tracer.new(logger: logger) - subject.log_start_or_continue(:sample) + subject = Instana::Tracer.new(nil, nil, ::Instana::Trace::TracerProvider.new, logger) + + subject.start_span(:sample) subject.log_exit(:roda) logger.verify @@ -314,9 +325,9 @@ def test_log_end_warn_span_name logger = Minitest::Mock.new logger.expect(:warn, true, [String]) - subject = Instana::Tracer.new(logger: logger) + subject = Instana::Tracer.new(nil, nil, ::Instana::Trace::TracerProvider.new, logger) - subject.log_start_or_continue(:sample) + subject.start_span(:sample) subject.log_end(:roda) logger.verify @@ -325,7 +336,7 @@ def test_log_end_warn_span_name def test_log_entry_span clear_all! - subject = Instana::Tracer.new + subject = Instana::Tracer.new(nil, nil, ::Instana::Trace::TracerProvider.new) span = Instana::Span.new(:rack) subject.log_entry(:sample, {}, ::Instana::Util.now_in_ms, span) @@ -336,8 +347,8 @@ def test_log_entry_span def test_log_entry_span_context clear_all! - subject = Instana::Tracer.new - span_context = Instana::SpanContext.new('test', 'test') + subject = Instana::Tracer.new(nil, nil, nil) + span_context = Instana::SpanContext.new(trace_id: 'test', span_id: 'test') subject.log_entry(:sample, {}, ::Instana::Util.now_in_ms, span_context) assert subject.tracing? diff --git a/test/tracing/opentracing_test.rb b/test/tracing/opentracing_test.rb deleted file mode 100644 index cebdd5a2..00000000 --- a/test/tracing/opentracing_test.rb +++ /dev/null @@ -1,382 +0,0 @@ -# (c) Copyright IBM Corp. 2021 -# (c) Copyright Instana Inc. 2017 - -require 'test_helper' -require 'rack/test' - -if Rack.release >= '3.0.0' - require 'rackup/lobster' -else - require 'rack/lobster' -end - -require "opentracing" - -module Instana - class OTRack1 - def initialize(app) - @app = app - end - - def call(env) - otrack1_span = OpenTracing.start_span(:otrack1) - result = @app.call(env) - otrack1_span.finish - result - end - end - - class OTRack2 - def initialize(app) - @app = app - end - - def call(env) - otrack2_span = OpenTracing.start_span(:otrack2) - result = @app.call(env) - otrack2_span.finish - result - end - end -end - -class OpenTracerTest < Minitest::Test - include Rack::Test::Methods - - def app - @app = Rack::Builder.new { - use Instana::Rack - use Instana::OTRack1 - use Rack::CommonLogger - use Rack::ShowExceptions - use Instana::OTRack2 - map "/mrlobster" do - if Rack.release >= '3.0.0' - run Rackup::Lobster.new - else - run Rack::Lobster.new - end - end - } - end - - def test_supplies_all_ot_interfaces - clear_all! - assert defined?(OpenTracing) - assert OpenTracing.respond_to?(:global_tracer) - assert OpenTracing.global_tracer.respond_to?(:start_span) - assert OpenTracing.global_tracer.respond_to?(:inject) - assert OpenTracing.global_tracer.respond_to?(:extract) - - assert OpenTracing.respond_to?(:start_span) - - assert defined?(OpenTracing::Carrier) - carrier = OpenTracing::Carrier.new - assert carrier.respond_to?(:[]) - assert carrier.respond_to?(:[]=) - assert carrier.respond_to?(:each) - - span = OpenTracing.start_span(:blah) - assert span.respond_to?(:finish) - assert span.respond_to?(:set_tag) - assert span.respond_to?(:tags) - assert span.respond_to?(:operation_name=) - assert span.respond_to?(:set_baggage_item) - assert span.respond_to?(:get_baggage_item) - assert span.respond_to?(:context) - assert span.respond_to?(:log) - end - - def test_basic_get_with_opentracing - clear_all! - get '/mrlobster' - assert last_response.ok? - - spans = ::Instana.processor.queued_spans - assert_equal 3, spans.length - - first_span = find_first_span_by_name(spans, :rack) - second_span = find_first_span_by_name(spans, :otrack1) - third_span = find_first_span_by_name(spans, :otrack2) - - assert_equal :rack, first_span[:n] - assert first_span[:ts].is_a?(Integer) - assert first_span[:ts] > 0 - assert first_span[:d].is_a?(Integer) - assert first_span[:d].between?(0, 5) - assert first_span.key?(:data) - assert first_span[:data].key?(:http) - assert_equal "GET", first_span[:data][:http][:method] - assert_equal "/mrlobster", first_span[:data][:http][:url] - assert_equal 200, first_span[:data][:http][:status] - assert_equal 'example.org', first_span[:data][:http][:host] - assert_equal :otrack1, second_span[:data][:sdk][:name] - assert second_span.key?(:data) - assert second_span[:data].key?(:sdk) - assert second_span[:data][:sdk].key?(:name) - assert_equal :otrack2, third_span[:data][:sdk][:name] - assert third_span.key?(:data) - assert third_span[:data].key?(:sdk) - assert third_span[:data][:sdk].key?(:name) - - # ID Validation - refute_equal first_span[:t], second_span[:t] - refute_equal second_span[:t], third_span[:t] - end - - def test_get_with_inject_extract - clear_all! - - trace_id = ::Instana::Util.generate_id - span_id = ::Instana::Util.generate_id - - header 'X-Instana-T', ::Instana::Util.id_to_header(trace_id) - header 'X-Instana-S', ::Instana::Util.id_to_header(span_id) - - get '/mrlobster' - assert last_response.ok? - - spans = ::Instana.processor.queued_spans - - assert_equal 3, spans.length - first_span = find_first_span_by_name(spans, :rack) - - # Make sure context was picked up and continued in the resulting - # trace - assert_equal trace_id, first_span[:t] - assert_equal span_id, first_span[:p] - end - - def test_start_span_with_tags - clear_all! - span = OpenTracing.start_span('my_app_entry') - - assert span.is_a?(::Instana::Span) - - span.set_tag(:tag_integer, 1234) - span.set_tag(:tag_boolean, true) - span.set_tag(:tag_array, [1,2,3,4]) - span.set_tag(:tag_string, "1234") - - assert_equal 1234, span.tags(:tag_integer) - assert_equal true, span.tags(:tag_boolean) - assert_equal [1,2,3,4], span.tags(:tag_array) - assert_equal "1234", span.tags(:tag_string) - span.finish - end - - def test_start_span_with_custom_start_time - clear_all! - now = Time.now - now_in_ms = ::Instana::Util.time_to_ms(now) - - span = OpenTracing.start_span('my_app_entry', :start_time => now) - - assert span.is_a?(::Instana::Span) - - span.set_tag(:tag_integer, 1234) - span.set_tag(:tag_boolean, true) - span.set_tag(:tag_array, [1,2,3,4]) - span.set_tag(:tag_string, "1234") - - assert_equal 1234, span.tags(:tag_integer) - assert_equal true, span.tags(:tag_boolean) - assert_equal [1,2,3,4], span.tags(:tag_array) - assert_equal "1234", span.tags(:tag_string) - span.finish - - assert span[:ts].is_a?(Integer) - assert_equal now_in_ms, span[:ts] - assert span[:d].is_a?(Integer) - assert span[:d].between?(0, 5) - end - - def test_span_kind_translation - clear_all! - span = OpenTracing.start_span('my_app_entry') - - assert span.is_a?(::Instana::Span) - - span.set_tag(:'span.kind', :server) - assert_equal :entry, span[:data][:sdk][:type] - assert_equal 1, span[:k] - - span.set_tag(:'span.kind', :consumer) - assert_equal :entry, span[:data][:sdk][:type] - assert_equal 1, span[:k] - - span.set_tag(:'span.kind', :client) - assert_equal :exit, span[:data][:sdk][:type] - assert_equal 2, span[:k] - - span.set_tag(:'span.kind', :producer) - assert_equal :exit, span[:data][:sdk][:type] - assert_equal 2, span[:k] - - span[:data][:sdk].delete(:type) - span.set_tag(:'span.kind', :blah) - assert_equal :intermediate, span[:data][:sdk][:type] - assert_equal 3, span[:k] - assert_equal :blah, span[:data][:sdk][:custom][:tags][:'span.kind'] - - span.finish - end - - def test_start_span_with_baggage - clear_all! - span = OpenTracing.start_span('my_app_entry') - span.set_baggage_item(:baggage_integer, 1234) - span.set_baggage_item(:baggage_boolean, false) - span.set_baggage_item(:baggage_array, [1,2,3,4]) - span.set_baggage_item(:baggage_string, '1234') - - assert_equal 1234, span.get_baggage_item(:baggage_integer) - assert_equal false, span.get_baggage_item(:baggage_boolean) - assert_equal [1,2,3,4], span.get_baggage_item(:baggage_array) - assert_equal "1234", span.get_baggage_item(:baggage_string) - span.finish - end - - def test_start_span_with_timestamps - clear_all! - span_tags = {:start_tag => 1234, :another_tag => 'tag_value'} - - ts_start = Time.now - 1 # Put start time a bit in the past - ts_start_ms = ::Instana::Util.time_to_ms(ts_start) - - span = OpenTracing.start_span('my_app_entry', tags: span_tags, start_time: ts_start) - sleep 0.1 - - ts_finish = Time.now + 5 # Put end time in the future - ts_finish_ms = ::Instana::Util.time_to_ms(ts_finish) - - span.finish(ts_finish) - - assert_equal ts_start_ms, span[:ts] - assert_equal (ts_finish_ms - ts_start_ms), span[:d] - - assert_equal 1234, span[:data][:sdk][:custom][:tags][:start_tag] - assert_equal 'tag_value', span[:data][:sdk][:custom][:tags][:another_tag] - end - - def test_nested_spans_using_child_of - clear_all! - entry_span = OpenTracing.start_span(:rack) - ac_span = OpenTracing.start_span(:action_controller, child_of: entry_span) - av_span = OpenTracing.start_span(:action_view, child_of: ac_span) - sleep 0.1 - av_span.finish - ac_span.finish - entry_span.finish - - spans = ::Instana.processor.queued_spans - assert_equal 3, spans.length - - first_span = find_first_span_by_name(spans, :rack) - second_span = find_first_span_by_name(spans, :action_controller) - third_span = find_first_span_by_name(spans, :action_view) - - # IDs - assert_equal first_span[:t], second_span[:t] - assert_equal second_span[:t], third_span[:t] - - # Linkage - assert first_span[:p].nil? - assert_equal first_span[:s], second_span[:p] - assert_equal second_span[:s], third_span[:p] - end - - def test_nested_spans_with_baggage - clear_all! - entry_span = OpenTracing.start_span(:rack) - ac_span = OpenTracing.start_span(:action_controller, child_of: entry_span) - ac_span.set_baggage_item(:my_bag, 1) - av_span = OpenTracing.start_span(:action_view, child_of: ac_span) - sleep 0.1 - av_span.finish - ac_span.finish - entry_span.finish - - spans = ::Instana.processor.queued_spans - assert_equal 3, spans.length - - first_span = find_first_span_by_name(spans, :rack) - second_span = find_first_span_by_name(spans, :action_controller) - third_span = find_first_span_by_name(spans, :action_view) - - # IDs - assert_equal first_span[:t], second_span[:t] - assert_equal second_span[:t], third_span[:t] - - # Linkage - assert first_span[:p].nil? - assert_equal first_span[:s], second_span[:p] - assert_equal second_span[:s], third_span[:p] - - # Every span should have baggage - assert_equal({}, entry_span.context.baggage) - assert_equal({:my_bag=>1}, ac_span.context.baggage) - assert_equal({:my_bag=>1}, av_span.context.baggage) - end - - def test_context_should_carry_baggage - clear_all! - - entry_span = OpenTracing.start_span(:rack) - entry_span_context = entry_span.context - - ac_span = OpenTracing.start_span(:action_controller, child_of: entry_span) - ac_span.set_baggage_item(:my_bag, 1) - ac_span_context = ac_span.context - - av_span = OpenTracing.start_span(:action_view, child_of: entry_span) - av_span_context = av_span.context - - sleep 0.1 - - av_span.finish - ac_span.finish - entry_span.finish - - spans = ::Instana.processor.queued_spans - assert_equal 3, spans.length - - assert_equal({}, entry_span.context.baggage) - assert_equal({:my_bag=>1}, ac_span.context.baggage) - assert_equal({}, av_span.context.baggage) - end - - def test_start_active_span - clear_all! - - span = OpenTracing.start_active_span(:rack) - assert_equal ::Instana::Tracer.current_span, span - - sleep 0.1 - - span.finish - - spans = ::Instana.processor.queued_spans - assert_equal 1, spans.length - end - - def test_active_span - clear_all! - - span = OpenTracing.start_active_span(:rack) - assert_equal OpenTracing.active_span, span - end - - def test_active_span_block - clear_all! - - obj = OpenTracing.start_active_span(:rack) { 1 } - assert_equal 1, obj - end - - def test_span_rename - span = OpenTracing.start_active_span(:rack) - span.operation_name = 'test' - assert_equal 'test', span.name - end -end