spec/elastic_apm/middleware_spec.rb (191 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 'spec_helper'
module ElasticAPM
RSpec.describe Middleware, :intercept do
it 'surrounds the request in a transaction' do
with_agent do
app = Middleware.new(->(_) { [200, {}, ['ok']] })
status, = app.call(Rack::MockRequest.env_for('/'))
expect(status).to be 200
end
expect(@intercepted.transactions.length).to be 1
transaction, = @intercepted.transactions
expect(transaction.result).to eq 'HTTP 2xx'
expect(transaction.context.response.status_code).to eq 200
expect(transaction.outcome).to eq 'success'
end
it 'ignores url patterns' do
with_agent transaction_ignore_urls: %w[/status/*/ping] do
expect(ElasticAPM).to_not receive(:start_transaction)
app = Middleware.new(->(_) { [200, {}, ['ok']] })
status, = app.call(Rack::MockRequest.env_for('/status/something/ping'))
expect(status).to be 200
end
end
it 'catches exceptions' do
class MiddlewareTestError < StandardError; end
allow(ElasticAPM).to receive(:report)
app = Middleware.new(lambda do |*_|
raise MiddlewareTestError, 'Yikes!'
end)
expect do
app.call(Rack::MockRequest.env_for('/'))
end.to raise_error(MiddlewareTestError)
expect(ElasticAPM).to have_received(:report)
.with(MiddlewareTestError, context: nil, handled: false)
end
it 'attaches a new trace_context' do
with_agent do
app = Middleware.new(->(_) { [200, {}, ['ok']] })
status, = app.call(Rack::MockRequest.env_for('/'))
expect(status).to be 200
end
trace_context = @intercepted.transactions.first.trace_context
expect(trace_context).to_not be_nil
expect(trace_context).to be_recorded
expect(trace_context.tracestate.sample_rate).to_not be nil
end
it 'sets outcome to `failure` for http status code >= 500', :intercept do
with_agent do
app = Middleware.new(->(_) { [500, {}, ['Internal Server Error']] })
app.call(Rack::MockRequest.env_for('/'))
end
expect(@intercepted.transactions.length).to be 1
transaction, = @intercepted.transactions
expect(transaction.result).to eq 'HTTP 5xx'
expect(transaction.context.response.status_code).to eq 500
expect(transaction.outcome).to eq 'failure'
end
it 'sets outcome to `failure` for failed requests', :intercept do
class MiddlewareTestError < StandardError; end
app = Middleware.new(lambda do |*_|
raise MiddlewareTestError, 'Yikes!'
end)
expect do
with_agent do
app.call(Rack::MockRequest.env_for('/'))
end
end.to raise_error(MiddlewareTestError)
transaction, = @intercepted.transactions
expect(transaction.outcome).to eq 'failure'
end
describe 'Distributed Tracing' do
let(:app) { Middleware.new(->(_) { [200, {}, ['ok']] }) }
context 'with valid header' do
it 'recognizes trace_context' do
with_agent do
# The apm server version must be < 8.0 in order for a transaction
# to be enqueued when it's unsampled.
# See issue https://github.com/elastic/apm-agent-ruby/issues/1340
WebMock.stub_request(:get, %r{^http://localhost:8200/$}).
to_return(body: '{"version":"7.17.4"}')
app.call(
Rack::MockRequest.env_for(
'/',
'HTTP_ELASTIC_APM_TRACEPARENT' =>
'00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00'
)
)
end
trace_context = @intercepted.transactions.first.trace_context
expect(trace_context.version).to eq '00'
expect(trace_context.trace_id)
.to eq '0af7651916cd43dd8448eb211c80319c'
expect(trace_context.parent_id).to eq 'b7ad6b7169203331'
expect(trace_context).to_not be_recorded
end
end
context 'with tracestate' do
it 'recognizes trace_context' do
with_agent do
# The apm server version must be < 8.0 in order for a transaction
# to be enqueued when it's unsampled.
# See issue https://github.com/elastic/apm-agent-ruby/issues/1340
WebMock.stub_request(:get, %r{^http://localhost:8200/$}).
to_return(body: '{"version":"7.17.4"}')
app.call(
Rack::MockRequest.env_for(
'/',
'HTTP_ELASTIC_APM_TRACEPARENT' =>
'00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00',
'HTTP_TRACESTATE' => 'es=s:0.75,abc=123'
)
)
end
trace_context = @intercepted.transactions.first.trace_context
expect(trace_context.tracestate).to be_a(TraceContext::Tracestate)
expect(trace_context.tracestate.to_header)
.to match('es=s:0.75,abc=123')
end
end
context 'with an invalid header' do
it 'skips trace_context, makes new' do
with_agent do
app.call(
Rack::MockRequest.env_for(
'/',
'HTTP_ELASTIC_APM_TRACEPARENT' =>
'00-0af7651916cd43dd8448eb211c80319c-INVALID##9203331-00'
)
)
end
trace_context = @intercepted.transactions.first.trace_context
expect(trace_context.trace_id)
.to_not eq '0af7651916cd43dd8448eb211c80319c'
expect(trace_context.parent_id).to_not match(/INVALID/)
end
end
context 'with a blank header' do
it 'skips trace_context, makes new' do
with_agent do
app.call(
Rack::MockRequest.env_for(
'/', 'HTTP_ELASTIC_APM_TRACEPARENT' => ''
)
)
end
trace_context = @intercepted.transactions.first.trace_context
expect(trace_context.trace_id)
.to_not eq '0af7651916cd43dd8448eb211c80319c'
end
end
context 'with a prefix-less header' do
it 'recognizes trace_context' do
with_agent do
# The apm server version must be < 8.0 in order for a transaction
# to be enqueued when it's unsampled.
# See issue https://github.com/elastic/apm-agent-ruby/issues/1340
WebMock.stub_request(:get, %r{^http://localhost:8200/$}).
to_return(body: '{"version":"7.17.4"}')
app.call(
Rack::MockRequest.env_for(
'/',
'HTTP_TRACEPARENT' =>
'00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-00'
)
)
end
trace_context = @intercepted.transactions.first.trace_context
expect(trace_context.version).to eq '00'
expect(trace_context.trace_id)
.to eq '0af7651916cd43dd8448eb211c80319c'
expect(trace_context.parent_id).to eq 'b7ad6b7169203331'
expect(trace_context).to_not be_recorded
end
end
context 'with both types of headers' do
it 'picks the prefixed' do
with_agent do
# The apm server version must be < 8.0 in order for a transaction
# to be enqueued when it's unsampled.
# See issue https://github.com/elastic/apm-agent-ruby/issues/1340
WebMock.stub_request(:get, %r{^http://localhost:8200/$}).
to_return(body: '{"version":"7.17.4"}')
app.call(
Rack::MockRequest.env_for(
'/',
'HTTP_TRACEPARENT' =>
'00-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-aaaaaaaaaaaaaaaa-00',
'HTTP_ELASTIC_APM_TRACEPARENT' =>
'00-bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-bbbbbbbbbbbbbbbb-00'
)
)
end
trace_context = @intercepted.transactions.first.trace_context
expect(trace_context.version).to eq '00'
expect(trace_context.trace_id)
.to eq 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
expect(trace_context.parent_id).to eq 'bbbbbbbbbbbbbbbb'
expect(trace_context).to_not be_recorded
end
end
end
end
end