Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for writing B3 single header #166

Merged
merged 3 commits into from
Oct 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# 0.41.0
* Add support for writing B3 single header.
* Omit ParentSpanId header for root spans.

# 0.40.1
* Fix to pass `async` option to the HTTP sender.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry for not picking this up in #167 and thanks for adding it 🙇


# 0.40.0
* Add support for reading B3 single header.

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use ZipkinTracer::RackHandler, config
* `:trace_id_128bit` - When set to true, high 8-bytes will be prepended to trace_id. The upper 4-bytes are epoch seconds and the lower 4-bytes are random. This makes it convertible to Amazon X-Ray trace ID format v1. (See http://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-request-tracing.html)
* `:async` - By default senders will flush traces asynchronously. Set to `false` to make that process synchronous. Only supported by the HTTP, RabbitMQ, and SQS senders.
* `:logger` - The default logger for Rails apps is `Rails.logger`, else it is `STDOUT`. Use this option to pass a custom logger.
* `:write_b3_single_format` - When set to true, only writes a single b3 header for outbound propagation.

#### Sender specific
* `:json_api_host` - Hostname with protocol of a zipkin api instance (e.g. `https://zipkin.example.com`) to use the HTTP sender
Expand Down
1 change: 1 addition & 0 deletions lib/zipkin-tracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
require 'zipkin-tracer/trace_generator'
require 'zipkin-tracer/trace_wrapper'
require 'zipkin-tracer/zipkin_b3_single_header_format'
require 'zipkin-tracer/zipkin_b3_header_helper'

begin
require 'faraday'
Expand Down
9 changes: 7 additions & 2 deletions lib/zipkin-tracer/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class Config
attr_reader :service_name, :sample_rate, :sampled_as_boolean, :trace_id_128bit, :async, :logger,
:json_api_host, :zookeeper, :kafka_producer, :kafka_topic, :sqs_queue_name, :sqs_region, :log_tracing,
:annotate_plugin, :filter_plugin, :whitelist_plugin, :rabbit_mq_connection, :rabbit_mq_exchange,
:rabbit_mq_routing_key
:rabbit_mq_routing_key, :write_b3_single_format

def initialize(app, config_hash)
config = config_hash || Application.config(app)
Expand Down Expand Up @@ -53,9 +53,13 @@ def initialize(app, config_hash)
# This makes it convertible to Amazon X-Ray trace ID format v1.
# (See http://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-request-tracing.html)
@trace_id_128bit = config[:trace_id_128bit].nil? ? DEFAULTS[:trace_id_128bit] : config[:trace_id_128bit]
# When set to true, only writes a single b3 header for outbound propagation.
@write_b3_single_format =
config[:write_b3_single_format].nil? ? DEFAULTS[:write_b3_single_format] : config[:write_b3_single_format]
codefromthecrypt marked this conversation as resolved.
Show resolved Hide resolved

Trace.sample_rate = @sample_rate
Trace.trace_id_128bit = @trace_id_128bit
Trace.write_b3_single_format = @write_b3_single_format

Trace.default_endpoint = Trace::Endpoint.local_endpoint(
domain_service_name(@service_name)
Expand Down Expand Up @@ -91,7 +95,8 @@ def domain_service_name(default_name)
DEFAULTS = {
sample_rate: 0.1,
sampled_as_boolean: true,
trace_id_128bit: false
trace_id_128bit: false,
write_b3_single_format: false
}

def present?(str)
Expand Down
17 changes: 3 additions & 14 deletions lib/zipkin-tracer/excon/zipkin-tracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

module ZipkinTracer
class ExconHandler < Excon::Middleware::Base
include B3HeaderHelper

def initialize(_)
super
end
Expand All @@ -15,10 +17,7 @@ def request_call(datum)
trace_id = TraceGenerator.new.next_trace_id

TraceContainer.with_trace_id(trace_id) do
b3_headers.each do |method, header|
datum[:headers][header] = trace_id.send(method).to_s
end

set_b3_header(datum[:headers], trace_id)
trace!(datum, trace_id) if Trace.tracer && trace_id.sampled?
end

Expand All @@ -36,16 +35,6 @@ def response_call(datum)

private

def b3_headers
{
trace_id: 'X-B3-TraceId',
parent_id: 'X-B3-ParentSpanId',
span_id: 'X-B3-SpanId',
sampled: 'X-B3-Sampled',
flags: 'X-B3-Flags'
}
end

def remote_endpoint(url, service_name)
Trace::Endpoint.remote_endpoint(url, service_name) # The endpoint we are calling.
end
Expand Down
16 changes: 3 additions & 13 deletions lib/zipkin-tracer/faraday/zipkin-tracer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
module ZipkinTracer
# Faraday middleware. It will add CR/CS annotations to outgoing connections done by Faraday
class FaradayHandler < ::Faraday::Middleware
include B3HeaderHelper

def initialize(app, service_name = nil)
@app = app
@service_name = service_name
Expand All @@ -12,9 +14,7 @@ def initialize(app, service_name = nil)
def call(env)
trace_id = TraceGenerator.new.next_trace_id
TraceContainer.with_trace_id(trace_id) do
b3_headers.each do |method, header|
env[:request_headers][header] = trace_id.send(method).to_s
end
set_b3_header(env[:request_headers], trace_id)
if Trace.tracer && trace_id.sampled?
trace!(env, trace_id)
else
Expand All @@ -25,16 +25,6 @@ def call(env)

private

def b3_headers
{
trace_id: 'X-B3-TraceId',
parent_id: 'X-B3-ParentSpanId',
span_id: 'X-B3-SpanId',
sampled: 'X-B3-Sampled',
flags: 'X-B3-Flags'
}
end

def trace!(env, trace_id)
response = nil
# handle either a URI object (passed by Faraday v0.8.x in testing), or something string-izable
Expand Down
2 changes: 1 addition & 1 deletion lib/zipkin-tracer/trace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ module Trace
# Most of these are set by the config class and then used around.
# TODO: Move this out of the Trace module , take out that extend self and be happier
extend self
attr_accessor :trace_id_128bit
attr_accessor :trace_id_128bit, :write_b3_single_format

# This method is deprecated, please use TraceGenerator.current
# Note that this method will always return a trace, it will
Expand Down
2 changes: 1 addition & 1 deletion lib/zipkin-tracer/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module ZipkinTracer
VERSION = '0.40.1'.freeze
VERSION = '0.41.0'.freeze
end
30 changes: 30 additions & 0 deletions lib/zipkin-tracer/zipkin_b3_header_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

module ZipkinTracer
module B3HeaderHelper
private

B3_SINGLE_HEADER = 'b3'

def set_b3_header(headers, trace_id)
if Trace.write_b3_single_format
headers[B3_SINGLE_HEADER] = B3SingleHeaderFormat.create_header(trace_id)
else
b3_headers.each do |method, header|
header_value = trace_id.send(method).to_s
headers[header] = header_value unless header_value.empty?
end
end
end

def b3_headers
{
trace_id: 'X-B3-TraceId',
parent_id: 'X-B3-ParentSpanId',
span_id: 'X-B3-SpanId',
sampled: 'X-B3-Sampled',
flags: 'X-B3-Flags'
}
end
end
end
14 changes: 12 additions & 2 deletions lib/zipkin-tracer/zipkin_b3_single_header_format.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ module ZipkinTracer
# b3: {x-b3-traceid}-{x-b3-spanid}-{if x-b3-flags 'd' else x-b3-sampled}-{x-b3-parentspanid}
# For details, see: https://github.com/openzipkin/b3-propagation
class B3SingleHeaderFormat
SAMPLED = '1'
NOT_SAMPLED = '0'
DEBUG = 'd'

def self.parse_from_header(b3_single_header)
if b3_single_header.size == 1
flag = b3_single_header
Expand All @@ -16,13 +20,19 @@ def self.parse_from_header(b3_single_header)

def self.parse_sampled(flag)
case flag
when '1', '0'
when SAMPLED, NOT_SAMPLED
flag
end
end

def self.parse_flags(flag)
flag == 'd' ? Trace::Flags::DEBUG : Trace::Flags::EMPTY
flag == DEBUG ? Trace::Flags::DEBUG : Trace::Flags::EMPTY
end

def self.create_header(trace_id)
flag = trace_id.debug? ? DEBUG : (trace_id.sampled? ? SAMPLED : NOT_SAMPLED)
parent_id_with_hyphen = "-#{trace_id.parent_id}" unless trace_id.parent_id.nil?
"#{trace_id.trace_id}-#{trace_id.span_id}-#{flag}#{parent_id_with_hyphen}"
end
end
end
3 changes: 2 additions & 1 deletion spec/lib/excon/zipkin-tracer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ def process(body, url, headers = {})
'X-B3-ParentSpanId' => req.headers['X-B3-Parentspanid'],
'X-B3-SpanId' => req.headers['X-B3-Spanid'],
'X-B3-Sampled' => req.headers['X-B3-Sampled'],
'X-B3-Flags' => req.headers['X-B3-Flags']
'X-B3-Flags' => req.headers['X-B3-Flags'],
'b3' => req.headers['B3']
}
}).to have_been_made

Expand Down
98 changes: 71 additions & 27 deletions spec/lib/middleware_shared_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
let(:raw_url) { "https://#{hostname}#{url_path}" }
let(:tracer) { Trace.tracer }
let(:trace_id) { ::Trace::TraceId.new(1, 2, 3, true, ::Trace::Flags::DEBUG) }
let(:write_b3_single_format) { false }
before { allow(Trace).to receive(:write_b3_single_format).and_return(write_b3_single_format) }

# helper to check host component of annotation
def expect_host(host, host_ip, service_name)
Expand Down Expand Up @@ -91,18 +93,33 @@ def expect_tracing
end
end

it 'sets the X-B3 request headers with a new spanID' do
request_headers = nil
ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
request_headers = process('', url)
context 'write_b3_single_format is false' do
it 'sets the X-B3 request headers with a new spanID' do
request_headers = nil
ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
request_headers = process('', url)
end

expect(request_headers['X-B3-TraceId']).to eq('0000000000000001')
expect(request_headers['X-B3-ParentSpanId']).to eq('0000000000000003')
expect(request_headers['X-B3-SpanId']).not_to eq('0000000000000002')
expect(request_headers['X-B3-SpanId']).to match(HEX_REGEX)
expect(request_headers['X-B3-Sampled']).to eq('true')
expect(request_headers['X-B3-Flags']).to eq('1')
end
end

context 'write_b3_single_format is true' do
let(:write_b3_single_format) { true }

it 'sets the B3 single request header with a new spanID' do
request_headers = nil
ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
request_headers = process('', url)
end

expect(request_headers['X-B3-TraceId']).to eq('0000000000000001')
expect(request_headers['X-B3-ParentSpanId']).to eq('0000000000000003')
expect(request_headers['X-B3-SpanId']).not_to eq('0000000000000003')
expect(request_headers['X-B3-SpanId']).to match(HEX_REGEX)
expect(request_headers['X-B3-Sampled']).to eq('true')
expect(request_headers['X-B3-Flags']).to eq('1')
expect(request_headers['b3']).to match(/\A0000000000000001-\h{16}-d-0000000000000003\z/)
end
end

it 'the original spanID is restored after the calling the middleware' do
Expand All @@ -122,32 +139,59 @@ def expect_tracing
end
end

it 'generates a new ID, and sets the X-B3 request headers' do
request_headers = process('', url)
context 'write_b3_single_format is false' do
it 'generates a new ID, and sets the X-B3 request headers' do
request_headers = process('', url)

expect(request_headers['X-B3-TraceId']).to match(HEX_REGEX)
expect(request_headers['X-B3-ParentSpanId']).to match(HEX_REGEX)
expect(request_headers['X-B3-SpanId']).to match(HEX_REGEX)
expect(request_headers['X-B3-Sampled']).to match(/(true|false)/)
expect(request_headers['X-B3-Flags']).to match(/(1|0)/)
expect(request_headers['X-B3-TraceId']).to match(HEX_REGEX)
expect(request_headers['X-B3-ParentSpanId']).to match(HEX_REGEX)
expect(request_headers['X-B3-SpanId']).to match(HEX_REGEX)
expect(request_headers['X-B3-Sampled']).to match(/(true|false)/)
expect(request_headers['X-B3-Flags']).to match(/(1|0)/)
end
end

context 'write_b3_single_format is true' do
let(:write_b3_single_format) { true }

it 'generates a new ID, and sets the B3 single request header' do
request_headers = process('', url)

expect(request_headers['b3']).to match(/\A\h{16}-\h{16}-[01d]-\h{16}\z/)
end
end
end

context 'Trace has not been sampled' do
let(:trace_id) { ::Trace::TraceId.new(1, 2, 3, false, 0) }

it 'sets the X-B3 request headers with a new spanID' do
request_headers = nil
ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
request_headers = process('', url)
context 'write_b3_single_format is false' do
it 'sets the X-B3 request headers with a new spanID' do
request_headers = nil
ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
request_headers = process('', url)
end

expect(request_headers['X-B3-TraceId']).to eq('0000000000000001')
expect(request_headers['X-B3-ParentSpanId']).to eq('0000000000000003')
expect(request_headers['X-B3-SpanId']).not_to eq('0000000000000003')
expect(request_headers['X-B3-SpanId']).to match(HEX_REGEX)
expect(request_headers['X-B3-Sampled']).to eq('false')
expect(request_headers['X-B3-Flags']).to eq('0')
end
end

context 'write_b3_single_format is true' do
let(:write_b3_single_format) { true }

expect(request_headers['X-B3-TraceId']).to eq('0000000000000001')
expect(request_headers['X-B3-ParentSpanId']).to eq('0000000000000003')
expect(request_headers['X-B3-SpanId']).not_to eq('0000000000000003')
expect(request_headers['X-B3-SpanId']).to match(HEX_REGEX)
expect(request_headers['X-B3-Sampled']).to eq('false')
expect(request_headers['X-B3-Flags']).to eq('0')
it 'sets the B3 single request header with a new spanID' do
request_headers = nil
ZipkinTracer::TraceContainer.with_trace_id(trace_id) do
request_headers = process('', url)
end

expect(request_headers['b3']).to match(/\A0000000000000001-\h{16}-0-0000000000000003\z/)
end
end

it 'the original spanID is restored after the calling the middleware' do
Expand Down
Loading