Skip to content

Commit

Permalink
Add support for writing B3 single header (#166)
Browse files Browse the repository at this point in the history
* Add support for writing B3 single header

* downcase 'b3'

* Update CHANGELOG.md
  • Loading branch information
ykitamura-mdsol authored and jcarres-mdsol committed Oct 25, 2019
1 parent 79c9980 commit a748303
Show file tree
Hide file tree
Showing 14 changed files with 310 additions and 104 deletions.
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.

# 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]

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

0 comments on commit a748303

Please sign in to comment.