diff --git a/README.md b/README.md index 272496d..a665469 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ OpenTracing Tracer implementation for Zipkin in Ruby +## Requirements + +Zipkin version >= 2.0.0 + ## Installation Add this line to your application's Gemfile: diff --git a/lib/zipkin/collector.rb b/lib/zipkin/collector.rb index ff1dc6e..e0dcbaf 100644 --- a/lib/zipkin/collector.rb +++ b/lib/zipkin/collector.rb @@ -18,37 +18,28 @@ def send_span(span, end_time) finish_ts = Timestamp.create(end_time) start_ts = Timestamp.create(span.start_time) duration = finish_ts - start_ts - is_server = %w[server consumer].include?(span.tags['span.kind'] || 'server') @buffer << { traceId: span.context.trace_id, id: span.context.span_id, parentId: span.context.parent_id, name: span.operation_name, + kind: (span.tags['span.kind'] || 'SERVER').upcase, timestamp: start_ts, duration: duration, - annotations: LogAnnotations.build(span, @local_endpoint) + [ - { - timestamp: start_ts, - value: is_server ? 'sr' : 'cs', - endpoint: @local_endpoint - }, - { - timestamp: finish_ts, - value: is_server ? 'ss' : 'cr', - endpoint: @local_endpoint - } - ], - binaryAnnotations: build_binary_annotations(span) + debug: false, + shared: false, + localEndpoint: @local_endpoint, + remoteEndpoint: Endpoint.remote_endpoint(span), + annotations: LogAnnotations.build(span), + tags: build_tags(span) } end private - def build_binary_annotations(span) - span.tags.map do |name, value| - { key: name, value: value.to_s } - end + def build_tags(span) + span.tags.map { |key, value| [key.to_s, value.to_s] }.to_h end class Buffer diff --git a/lib/zipkin/collector/log_annotations.rb b/lib/zipkin/collector/log_annotations.rb index ce45adb..ebabfef 100644 --- a/lib/zipkin/collector/log_annotations.rb +++ b/lib/zipkin/collector/log_annotations.rb @@ -1,12 +1,11 @@ module Zipkin class Collector module LogAnnotations - def self.build(span, endpoint) + def self.build(span) span.logs.map do |log| { timestamp: Timestamp.create(log.fetch(:timestamp)), - value: format_log_value(log), - endpoint: endpoint + value: format_log_value(log) } end end diff --git a/lib/zipkin/endpoint.rb b/lib/zipkin/endpoint.rb index 20728ce..308fbc9 100644 --- a/lib/zipkin/endpoint.rb +++ b/lib/zipkin/endpoint.rb @@ -7,11 +7,53 @@ class Endpoint Socket.ip_address_list.reverse.detect(&:ipv4?) ).ip_address + module SpanKind + SERVER = 'server'.freeze + CLIENT = 'client'.freeze + PRODUCER = 'producer'.freeze + CONSUMER = 'consumer'.freeze + end + + module PeerInfo + SERVICE = 'peer.service'.freeze + IPV4 = 'peer.ipv4'.freeze + IPV6 = 'peer.ipv6'.freeze + PORT = 'peer.port'.freeze + + def self.keys + [SERVICE, IPV4, IPV6, PORT] + end + end + def self.local_endpoint(service_name) { serviceName: service_name, ipv4: LOCAL_IP } end + + def self.remote_endpoint(span) + tags = span.tags + kind = tags['span.kind'] || SpanKind::SERVER + + case kind + when SpanKind::SERVER, SpanKind::CLIENT + return nil if (tags.keys & PeerInfo.keys).empty? + + { + serviceName: tags[PeerInfo::SERVICE], + ipv4: tags[PeerInfo::IPV4], + ipv6: tags[PeerInfo::IPV6], + port: tags[PeerInfo::PORT] + } + when SpanKind::PRODUCER, SpanKind::CONSUMER + { + serviceName: 'broker' + } + else + warn "Unkown span kind: #{kind}" + nil + end + end end end diff --git a/lib/zipkin/json_client.rb b/lib/zipkin/json_client.rb index 27c8f25..40f8821 100644 --- a/lib/zipkin/json_client.rb +++ b/lib/zipkin/json_client.rb @@ -7,7 +7,7 @@ class JsonClient def initialize(url:, collector:, flush_interval:, logger: Logger.new(STDOUT)) @collector = collector @flush_interval = flush_interval - @spans_uri = URI.parse("#{url}/api/v1/spans") + @spans_uri = URI.parse("#{url}/api/v2/spans") @logger = logger end diff --git a/script/create_trace b/script/create_trace index 8c52085..4eb923f 100755 --- a/script/create_trace +++ b/script/create_trace @@ -12,7 +12,9 @@ tracer2 = Zipkin::Tracer.build(url: url, service_name: 'downstream-service') outer_span = tracer1.start_span( 'receive request', - tags: { 'span.kind' => 'server' } + tags: { + 'span.kind' => 'server' + } ) sleep 1 diff --git a/spec/zipkin/collector/log_annotations_spec.rb b/spec/zipkin/collector/log_annotations_spec.rb index 10630b3..ea00045 100644 --- a/spec/zipkin/collector/log_annotations_spec.rb +++ b/spec/zipkin/collector/log_annotations_spec.rb @@ -2,16 +2,14 @@ RSpec.describe Zipkin::Collector::LogAnnotations do let(:span) { Zipkin::Span.new(nil, 'operation_name', nil) } - let(:endpoint) { 'local-endpoint' } context 'when log includes only event and timestamp' do it 'uses event as the annotation value' do message = 'some message' span.log_kv(event: message) - expect(described_class.build(span, endpoint)).to include( + expect(described_class.build(span)).to include( timestamp: instance_of(Integer), - value: message, - endpoint: endpoint + value: message ) end end @@ -19,10 +17,9 @@ context 'when log includes multiple fields' do it 'converts fields into string form' do span.log_kv(foo: 'bar', baz: 'buz') - expect(described_class.build(span, endpoint)).to include( + expect(described_class.build(span)).to include( timestamp: instance_of(Integer), - value: 'foo=bar baz=buz', - endpoint: endpoint + value: 'foo=bar baz=buz' ) end end diff --git a/spec/zipkin/endpoint_spec.rb b/spec/zipkin/endpoint_spec.rb new file mode 100644 index 0000000..97beeff --- /dev/null +++ b/spec/zipkin/endpoint_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +RSpec.describe Zipkin::Endpoint do + let(:span) { Zipkin::Span.new(nil, 'operation_name', nil) } + + shared_examples 'a rpc endpoint' do + it 'returns nil if no peer info' do + expect(remote_endpoint(span)).to eq(nil) + end + + it 'includes service name' do + service_name = 'service-name' + span.set_tag('peer.service', service_name) + expect(remote_endpoint(span)).to include(serviceName: service_name) + end + + it 'includes ipv4 address' do + ipv4 = '8.7.6.5' + span.set_tag('peer.ipv4', ipv4) + expect(remote_endpoint(span)).to include(ipv4: ipv4) + end + + it 'includes ipv6 address' do + ipv6 = '2001:0db8:85a3:0000:0000:8a2e:0370:7334' + span.set_tag('peer.ipv6', ipv6) + expect(remote_endpoint(span)).to include(ipv6: ipv6) + end + + it 'includes port' do + port = 3000 + span.set_tag('peer.port', port) + expect(remote_endpoint(span)).to include(port: port) + end + end + + describe '.remote_endpoint' do + context 'when span kind is undefined' do + it_behaves_like 'a rpc endpoint' + end + + context 'when span kind is server' do + before { span.set_tag('span.kind', 'server') } + + it_behaves_like 'a rpc endpoint' + end + + context 'when span kind is client' do + before { span.set_tag('span.kind', 'client') } + + it_behaves_like 'a rpc endpoint' + end + + context 'when span kind is producer' do + before { span.set_tag('span.kind', 'producer') } + + it 'returns broker as service name' do + expect(remote_endpoint(span)).to eq(serviceName: 'broker') + end + end + + context 'when span kind is consumer' do + before { span.set_tag('span.kind', 'consumer') } + + it 'returns broker as service name' do + expect(remote_endpoint(span)).to eq(serviceName: 'broker') + end + end + + context 'when unknown span kind' do + before { span.set_tag('span.kind', 'something-else') } + + it 'returns nil' do + expect(remote_endpoint(span)).to eq(nil) + end + end + end + + def remote_endpoint(span) + described_class.remote_endpoint(span) + end +end