spec/integration/opentracing_spec.rb (305 lines of code) (raw):
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# frozen_string_literal: true
require 'integration_helper'
require 'elastic_apm/opentracing'
RSpec.describe 'OpenTracing bridge', :intercept do
let(:tracer) { ::OpenTracing.global_tracer }
before :context do
::OpenTracing.global_tracer = ElasticAPM::OpenTracing::Tracer.new
end
context 'without an agent' do
it 'is a noop' do
thing = double(ran: 'success')
result = tracer.start_active_span('namest') do |scope|
expect(scope).to be_a ElasticAPM::OpenTracing::Scope
tracer.start_active_span('nested') do |nested_scope|
expect(nested_scope.span).to be ::OpenTracing::Span::NOOP_INSTANCE
thing.ran('…')
end
end
expect(thing).to have_received(:ran).with('…')
expect(result).to eq 'success'
end
end
context 'with an APM Agent' do
before do
intercept!
ElasticAPM.start
end
after { ElasticAPM.stop }
describe '#start_span' do
context 'as root' do
subject! { ::OpenTracing.start_span('namest') }
after { subject.finish }
it { should be_an ElasticAPM::OpenTracing::Span }
its(:elastic_span) { should be_an ElasticAPM::Transaction }
its(:context) { should be_an ElasticAPM::OpenTracing::SpanContext }
it 'is not active' do
expect(::OpenTracing.active_span).to be nil
end
end
context 'as a child' do
let(:parent) { ::OpenTracing.start_span('parent') }
subject! { ::OpenTracing.start_span('namest', child_of: parent) }
after do
subject.finish
parent.finish
end
it 'has a child context' do
expect(subject.context.parent_id).to eq parent.context.id
expect(subject.context.id).not_to eq parent.context.id
expect(subject.context.trace_id).to eq parent.context.trace_id
end
its(:elastic_span) { should be_a ElasticAPM::Span }
end
end
describe '#start_active_span' do
context 'as root' do
subject! { ::OpenTracing.start_active_span('namest') }
after { subject.close }
it { should be_an ElasticAPM::OpenTracing::Scope }
its(:elastic_span) { should be_a ElasticAPM::Transaction }
it 'is active' do
expect(::OpenTracing.active_span).to be subject.span
end
end
context 'as child_of' do
let(:parent) { ::OpenTracing.start_span('parent') }
subject! { ::OpenTracing.start_active_span('namest', child_of: parent) }
after do
subject.close
parent.finish
end
it 'is the correct span' do
expect(subject.span.elastic_span).to be_an ElasticAPM::Span
end
it 'is active' do
expect(::OpenTracing.active_span).to be subject.span
end
end
context 'when a block is passed' do
it 'returns the result of the block' do
thing = double(ran: 'success')
result = tracer.start_active_span('namest') do
thing.ran('…')
end
expect(thing).to have_received(:ran).with('…')
expect(result).to eq 'success'
end
end
end
describe 'activation' do
it 'sets the span as active in scope' do
span = OpenTracing.start_span('name')
scope = OpenTracing.scope_manager.activate(span)
expect(OpenTracing.active_span).to be span
scope.close
end
end
describe '#inject' do
let(:context) do
ElasticAPM::TraceContext.new(
traceparent: ElasticAPM::TraceContext::Traceparent.parse(
'00-11111111111111111111111111111111-2222222222222222-00'
)
)
end
let(:carrier) { {} }
subject { ::OpenTracing.inject(context, format, carrier) }
context 'Rack' do
let(:format) { ::OpenTracing::FORMAT_RACK }
it 'sets a header' do
subject
expect(carrier['elastic-apm-traceparent'])
.to eq context.traceparent.to_header
end
end
context 'Text map' do
let(:format) { ::OpenTracing::FORMAT_TEXT_MAP }
it 'sets a header' do
subject
expect(carrier['elastic-apm-traceparent'])
.to eq context.traceparent.to_header
end
end
context 'Binary' do
let(:format) { ::OpenTracing::FORMAT_BINARY }
it 'warns about lack of support' do
expect(tracer).to receive(:warn).with(/Only injection via/)
subject
end
end
end
describe '#extract' do
subject { ::OpenTracing.extract(format, carrier) }
context 'Rack' do
let(:format) { ::OpenTracing::FORMAT_RACK }
let(:carrier) do
{ 'HTTP_ELASTIC_APM_TRACEPARENT' =>
'00-11111111111111111111111111111111-2222222222222222-00' }
end
it 'returns a span context' do
expect(subject).to be_a ElasticAPM::OpenTracing::SpanContext
expect(subject.trace_context.trace_id)
.to eq '11111111111111111111111111111111'
expect(subject.trace_context.id).to eq '2222222222222222'
expect(subject.trace_context.parent_id).to be_nil
end
end
context 'Text map' do
let(:format) { ::OpenTracing::FORMAT_TEXT_MAP }
let(:carrier) do
{ 'elastic-apm-traceparent' =>
'00-11111111111111111111111111111111-2222222222222222-00' }
end
it 'returns a span context' do
expect(subject).to be_a ElasticAPM::OpenTracing::SpanContext
expect(subject.trace_context.trace_id)
.to eq '11111111111111111111111111111111'
expect(subject.trace_context.id).to eq '2222222222222222'
expect(subject.trace_context.parent_id).to be_nil
end
end
context 'Binary' do
let(:format) { ::OpenTracing::FORMAT_BINARY }
let(:carrier) { {} }
it 'warns about lack of support' do
expect(tracer).to receive(:warn).with(/Only extraction from/)
subject
end
end
end
end
describe 'example', :intercept do
before do
intercept!
ElasticAPM.start
end
after { ElasticAPM.stop }
matcher :be_a_child_of do |parent_scope|
match do |scope|
scope.span.context.parent_id == parent_scope.span.context.id
end
end
it 'traces nested spans' do
OpenTracing.start_active_span(
'operation_name',
tags: { test: '0' }
) do |scope|
expect(scope).to be_a(ElasticAPM::OpenTracing::Scope)
expect(OpenTracing.active_span).to be scope.span
expect(OpenTracing.active_span).to be_a ElasticAPM::OpenTracing::Span
OpenTracing.start_active_span(
'nested',
tags: { test: '1' }
) do |nested_scope|
expect(OpenTracing.active_span).to be nested_scope.span
expect(nested_scope).to be_a_child_of scope
OpenTracing.start_active_span('namest') do |further_nested|
expect(OpenTracing.active_span).to be further_nested.span
expect(further_nested).to_not be nested_scope
expect(further_nested).to be_a_child_of nested_scope
end
end
end
expect(@intercepted.transactions.length).to be 1
expect(@intercepted.spans.length).to be 2
transaction, = @intercepted.transactions
expect(transaction.context.labels).to match(test: '0')
span = @intercepted.spans.last
expect(span.context.labels).to match(test: '1')
end
end
describe ElasticAPM::OpenTracing::Span do
before do
intercept!
ElasticAPM.start
end
after { ElasticAPM.stop }
let(:elastic_span) do
ElasticAPM::Transaction.new config: ElasticAPM::Config.new
end
describe 'log_kv' do
subject { described_class.new(elastic_span, nil) }
it 'logs exceptions' do
subject.log_kv('error.object': actual_exception)
expect(@intercepted.errors.length).to be 1
end
it 'logs messages' do
subject.log_kv(message: 'message')
expect(@intercepted.errors.length).to be 1
end
it 'ignores unknown logs' do
subject.log_kv(other: 1)
expect(@intercepted.errors.length).to be 0
end
end
describe 'set_tag' do
subject { described_class.new(elastic_span, trace_context) }
shared_examples :opengraph_span do
it 'can set operation name' do
subject.operation_name = 'Test'
expect(elastic_span.name).to eq 'Test'
end
describe 'set_tag' do
it 'sets elastic span label' do
subject.set_tag :custom_key, 'custom_type'
expect(subject.elastic_span.context.labels[:custom_key])
.to eq 'custom_type'
end
it 'returns self' do
expect(subject.set_tag(:custom_key, 'custom_type'))
.to be_a ElasticAPM::OpenTracing::Span
end
end
end
context 'when transaction' do
let(:elastic_span) do
ElasticAPM::Transaction.new config: ElasticAPM::Config.new
end
let(:trace_context) { nil }
it_behaves_like :opengraph_span
it 'knows user fields' do
subject.set_tag 'user.id', 1
subject.set_tag 'user.username', 'someone'
subject.set_tag 'user.email', 'someone@example.com'
subject.set_tag 'user.other_field', 'someone@example.com'
user = subject.elastic_span.context.user
expect(user.id).to eq 1
expect(user.username).to eq 'someone'
expect(user.email).to eq 'someone@example.com'
end
end
context 'when span' do
let(:elastic_span) do
transaction =
ElasticAPM::Transaction.new(config: ElasticAPM::Config.new)
ElasticAPM::Span.new(
name: 'Span',
transaction: transaction,
parent: transaction,
trace_context: trace_context
)
end
let(:trace_context) { nil }
it_behaves_like :opengraph_span
it "doesn't explode on user fields" do
expect { subject.set_tag 'user.id', 1 }
.to_not raise_error
end
end
end
describe '#finish' do
subject do
::OpenTracing.start_span(
'namest',
start_time: Time.new(2020, 1, 1, 0, 0, 1)
)
end
context 'when the span is finished with a `Time` as end_time' do
before { subject.finish(end_time: Time.new(2020, 1, 1, 0, 0, 2)) }
it 'converts to monotonic time' do
expect(subject.elastic_span.duration).to eq(1_000_000)
end
end
end
end
end