Embed jaeger-client

This commit is contained in:
Sruthi Chandran 2019-03-14 13:21:19 +05:30
parent d1412c0ef5
commit 5a90db87f7
69 changed files with 3606 additions and 0 deletions

View file

@ -0,0 +1,12 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
# rspec failure tracking
.rspec_status

View file

@ -0,0 +1,3 @@
[submodule "idl"]
path = idl
url = https://github.com/jaegertracing/jaeger-idl.git

View file

@ -0,0 +1,2 @@
--format documentation
--color

View file

@ -0,0 +1,50 @@
require: rubocop-rspec
AllCops:
Exclude:
- 'thrift/**/*'
Style/Documentation:
Enabled: no
Style/IfUnlessModifier:
Enabled: no
RSpec/NestedGroups:
Max: 4
RSpec/ExampleLength:
Enabled: no
RSpec/MultipleExpectations:
Enabled: no
RSpec/MessageSpies:
Enabled: no
Metrics/BlockLength:
Enabled: no
Metrics/MethodLength:
Enabled: no
Metrics/AbcSize:
Enabled: no
Metrics/ClassLength:
Enabled: no
Metrics/ParameterLists:
Enabled: no
Lint/UnusedMethodArgument:
Enabled: no
Style/FrozenStringLiteralComment:
Enabled: yes
EnforcedStyle: always
Include:
- 'lib/**/*'
Metrics/LineLength:
Max: 120

View file

@ -0,0 +1,14 @@
sudo: false
language: ruby
rvm:
- 2.5.0
services:
- docker
before_install: gem install bundler -v 1.17.2
script:
- bundle exec rake
- make crossdock

View file

@ -0,0 +1,4 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in jaeger-client.gemspec
gemspec

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2017 Indrek Juhkam
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1 @@
-include crossdock/rules.mk

View file

@ -0,0 +1,158 @@
Jaeger::Client
================
[![Gem Version](https://badge.fury.io/rb/jaeger-client.svg)](https://rubygems.org/gems/jaeger-client)
[![Build Status](https://travis-ci.org/salemove/jaeger-client-ruby.svg)](https://travis-ci.org/salemove/jaeger-client-ruby)
OpenTracing Tracer implementation for Jaeger in Ruby
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'jaeger-client'
```
## Usage
```ruby
require 'jaeger/client'
OpenTracing.global_tracer = Jaeger::Client.build(host: 'localhost', port: 6831, service_name: 'echo')
OpenTracing.start_active_span('span name') do
# do something
OpenTracing.start_active_span('inner span name') do
# do something else
end
end
```
See [opentracing-ruby](https://github.com/opentracing/opentracing-ruby) for more examples.
### Reporters
#### RemoteReporter (default)
RemoteReporter buffers spans in memory and sends them out of process using Sender.
There are two senders: `UdpSender` (default) and `HttpSender`.
To use `HttpSender`:
```ruby
OpenTracing.global_tracer = Jaeger::Client.build(
service_name: 'service_name',
reporter: Jaeger::Reporters::RemoteReporter.new(
sender: Jaeger::HttpSender.new(
url: 'http://localhost:14268/api/traces',
headers: { 'key' => 'value' }, # headers key is optional
encoder: Jaeger::Encoders::ThriftEncoder.new(service_name: 'service_name')
),
flush_interval: 10
)
)
```
#### NullReporter
NullReporter ignores all spans.
```ruby
OpenTracing.global_tracer = Jaeger::Client.build(
service_name: 'service_name',
reporter: Jaeger::Reporters::NullReporter.new
)
```
#### LoggingReporter
LoggingReporter prints some details about the span using `logger`. This is meant only for debugging. Do not parse and use this information for anything critical. The implemenation can change at any time.
```ruby
OpenTracing.global_tracer = Jaeger::Client.build(
service_name: 'service_name',
reporter: Jaeger::Reporters::LoggingReporter.new
)
```
LoggingReporter can also use a custom logger. For this provide logger using `logger` keyword argument.
### Samplers
#### Const sampler
`Const` sampler always makes the same decision for new traces depending on the initialization value. Set `sampler` to: `Jaeger::Samplers::Const.new(true)` to mark all new traces as sampled.
#### Probabilistic sampler
`Probabilistic` sampler samples traces with probability equal to `rate` (must be between 0.0 and 1.0). This can be enabled by setting `Jaeger::Samplers::Probabilistic.new(rate: 0.1)`
#### RateLimiting sampler
`RateLimiting` sampler samples at most `max_traces_per_second`. The distribution of sampled traces follows burstiness of the service, i.e. a service with uniformly distributed requests will have those requests sampled uniformly as well, but if requests are bursty, especially sub-second, then a number of sequential requests can be sampled each second.
Set `sampler` to `Jaeger::Samplers::RateLimiting.new(max_traces_per_second: 100)`
#### GuaranteedThroughputProbabilistic sampler
`GuaranteedThroughputProbabilistic` is a sampler that guarantees a throughput by using a Probabilistic sampler and RateLimiting sampler The RateLimiting sampler is used to establish a lower_bound so that every operation is sampled at least once in the time interval defined by the lower_bound.
Set `sampler` to `Jaeger::Samplers::GuaranteedThroughputProbabilistic.new(lower_bound: 10, rate: 0.001)`
#### PerOperation sampler
`PerOperation` sampler leverages both Probabilistic sampler and RateLimiting sampler via the GuaranteedThroughputProbabilistic sampler. This sampler keeps track of all operations and delegates calls the the respective GuaranteedThroughputProbabilistic sampler.
Set `sampler` to
```ruby
Jaeger::Samplers::PerOperation.new(
strategies: {
per_operation_strategies: [
{ operation: 'GET /articles', probabilistic_sampling: 0.5 },
{ operation: 'POST /articles', probabilistic_sampling: 1.0 }
],
default_sampling_probability: 0.001,
default_lower_bound_traces_per_second: 1.0 / (10.0 * 60.0)
},
max_operations: 1000
)
```
### Zipkin HTTP B3 compatible header propagation
Jaeger Tracer supports Zipkin B3 Propagation HTTP headers, which are used by a lot of Zipkin tracers. This means that you can use Jaeger in conjunction with OpenZipkin tracers.
To set it up you need to change FORMAT_RACK injector and extractor.
```ruby
OpenTracing.global_tracer = Jaeger::Client.build(
service_name: 'service_name',
injectors: {
OpenTracing::FORMAT_RACK => [Jaeger::Injectors::B3RackCodec]
},
extractors: {
OpenTracing::FORMAT_RACK => [Jaeger::Extractors::B3RackCodec]
}
)
```
It's also possible to set up multiple injectors and extractors. Each injector will be called in sequence. Note that if multiple injectors are using the same keys then the values will be overwritten.
If multiple extractors is used then the span context from the first match will be returned.
## Development
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/salemove/jaeger-client-ruby
## License
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).

View file

@ -0,0 +1,9 @@
require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
require 'rubocop/rake_task'
RSpec::Core::RakeTask.new(:spec)
RuboCop::RakeTask.new(:rubocop)
task default: %i[rubocop spec]

View file

@ -0,0 +1,14 @@
#!/usr/bin/env ruby
require 'bundler/setup'
require 'jaeger/client'
# You can add fixtures and/or initialization code here to make experimenting
# with your gem easier. You can also use a different console, if you like.
# (If you use this, don't forget to add pry to your Gemfile!)
# require "pry"
# Pry.start
require 'irb'
IRB.start(__FILE__)

View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
set -vx
bundle install
# Do any other automated setup that you need to do here

View file

@ -0,0 +1,29 @@
FROM ruby:2.5-alpine
ENV APP_HOME /app
# git is required by bundler to run jaeger gem with local path
RUN apk add --no-cache git
# Add only files needed for installing gem dependencies. This allows us to
# change other files without needing to install gems every time when building
# the docker image.
ADD Gemfile Gemfile.lock jaeger-client.gemspec $APP_HOME/
ADD lib/jaeger/client/version.rb $APP_HOME/lib/jaeger/client/
ADD crossdock/Gemfile crossdock/Gemfile.lock $APP_HOME/crossdock/
RUN apk add --no-cache --virtual .app-builddeps build-base \
&& cd $APP_HOME && bundle install \
&& cd $APP_HOME/crossdock && bundle install \
&& apk del .app-builddeps
ADD . $APP_HOME
RUN chown -R nobody:nogroup $APP_HOME
USER nobody
WORKDIR $APP_HOME/crossdock
CMD ["bundle", "exec", "./server"]
EXPOSE 8080-8082

View file

@ -0,0 +1,6 @@
source 'https://rubygems.org'
gem 'jaeger-client', path: '../'
gem 'rack'
gem 'sinatra'
gem 'webrick', '~> 1.4.2'

View file

@ -0,0 +1,35 @@
PATH
remote: ..
specs:
jaeger-client (0.7.1)
opentracing (~> 0.3)
thrift
GEM
remote: https://rubygems.org/
specs:
mustermann (1.0.3)
opentracing (0.4.3)
rack (2.0.6)
rack-protection (2.0.4)
rack
sinatra (2.0.4)
mustermann (~> 1.0)
rack (~> 2.0)
rack-protection (= 2.0.4)
tilt (~> 2.0)
thrift (0.11.0.0)
tilt (2.0.9)
webrick (1.4.2)
PLATFORMS
ruby
DEPENDENCIES
jaeger-client!
rack
sinatra
webrick (~> 1.4.2)
BUNDLED WITH
1.16.2

View file

@ -0,0 +1,68 @@
version: '2'
services:
crossdock:
image: crossdock/crossdock
links:
- test_driver
- go
- python
- java
- ruby
environment:
- WAIT_FOR=test_driver,go,python,java,ruby
- WAIT_FOR_TIMEOUT=60s
- CALL_TIMEOUT=60s
- AXIS_CLIENT=go
- AXIS_S1NAME=go,python,java,ruby
- AXIS_SAMPLED=true,false
- AXIS_S2NAME=go,python,java,ruby
- AXIS_S2TRANSPORT=http
- AXIS_S3NAME=go,python,java,ruby
- AXIS_S3TRANSPORT=http
- BEHAVIOR_TRACE=client,s1name,sampled,s2name,s2transport,s3name,s3transport
- AXIS_TESTDRIVER=test_driver
- AXIS_SERVICES=ruby
- BEHAVIOR_ENDTOEND=testdriver,services
- REPORT=compact
go:
image: jaegertracing/xdock-go
ports:
- "8080-8082"
java:
image: jaegertracing/xdock-java
depends_on:
- jaeger-agent
ports:
- "8080-8082"
python:
image: jaegertracing/xdock-py
depends_on:
- jaeger-agent
ports:
- "8080-8082"
ruby:
build:
context: ../.
dockerfile: crossdock/Dockerfile
ports:
- "8080-8082"
test_driver:
image: jaegertracing/test-driver
depends_on:
- jaeger-query
- jaeger-collector
- jaeger-agent
ports:
- "8080"

View file

@ -0,0 +1,48 @@
version: '2'
services:
jaeger-collector:
image: jaegertracing/jaeger-collector
command: ["--cassandra.keyspace=jaeger_v1_dc1", "--cassandra.servers=cassandra", "--collector.zipkin.http-port=9411"]
ports:
- "14269"
- "14268:14268"
- "14267"
- "14250"
- "9411:9411"
restart: on-failure
depends_on:
- cassandra-schema
jaeger-query:
image: jaegertracing/jaeger-query
command: ["--cassandra.keyspace=jaeger_v1_dc1", "--cassandra.servers=cassandra"]
ports:
- "16686:16686"
- "16687"
restart: on-failure
depends_on:
- cassandra-schema
jaeger-agent:
image: jaegertracing/jaeger-agent
# FIXME temporarily switching back to tchannel
# https://github.com/jaegertracing/jaeger/issues/1229
command: ["--reporter.tchannel.host-port=jaeger-collector:14267"]
ports:
- "5775:5775/udp"
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
restart: on-failure
depends_on:
- jaeger-collector
cassandra:
image: cassandra:3.9
cassandra-schema:
image: jaegertracing/jaeger-cassandra-schema
depends_on:
- cassandra

View file

@ -0,0 +1,35 @@
XDOCK_YAML=crossdock/docker-compose.yml
TRACETEST_THRIFT=idl/thrift/crossdock/tracetest.thrift
JAEGER_COMPOSE_URL=https://raw.githubusercontent.com/jaegertracing/jaeger/master/crossdock/jaeger-docker-compose.yml
XDOCK_JAEGER_YAML=crossdock/jaeger-docker-compose.yml
.PHONY: clean-compile
clean-compile:
find . -name '*.pyc' -exec rm {} \;
.PHONY: docker
docker: clean-compile crossdock-download-jaeger
docker build -f crossdock/Dockerfile -t jaeger-client-ruby .
.PHONY: crossdock
crossdock: ${TRACETEST_THRIFT} crossdock-download-jaeger
docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) kill ruby
docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) rm -f ruby
docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) build ruby
docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) run crossdock
.PHONY: crossdock-fresh
crossdock-fresh: ${TRACETEST_THRIFT} crossdock-download-jaeger
docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) kill
docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) rm --force
docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) pull
docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) build
docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) run crossdock
.PHONY: crossdock-logs crossdock-download-jaeger
crossdock-logs:
docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) logs
.PHONY: crossdock-download-jaeger
crossdock-download-jaeger:
curl -o $(XDOCK_JAEGER_YAML) $(JAEGER_COMPOSE_URL)

View file

@ -0,0 +1,173 @@
#!/usr/bin/env ruby
$stdout.sync = true
require 'sinatra/base'
require 'webrick'
require 'jaeger/client'
require 'net/http'
require 'uri'
class HealthServer < Sinatra::Application
get '/' do
status 200
end
end
class HttpServer < Sinatra::Application
post '/start_trace' do
puts "Got request to start trace: #{trace_request}"
parent_context = tracer.extract(OpenTracing::FORMAT_RACK, request.env)
server_span = tracer.start_span('/start_trace', child_of: parent_context)
server_span.set_baggage_item('crossdock-baggage-key', trace_request['baggage'])
if trace_request.key?('sampled')
server_span.set_tag('sampling.priority', trace_request['sampled'] ? 1 : 0)
end
response = {
span: observe_span(server_span),
notImplementedError: ''
}
if trace_request['downstream']
downstream = trace_request['downstream']
transport = downstream['transport']
response[:downstream] =
if transport == 'HTTP'
call_downstream_http(downstream, server_span)
elsif transport == 'DUMMY'
{ notImplementedError: 'Dummy has not been implemented' }
else
{ notImplementedError: "Unrecognized transport received: #{transport}" }
end
end
puts "Response: #{response}"
server_span.finish
body JSON.dump(response)
end
post '/join_trace' do
puts 'Got request to join trace' \
"\n Params: #{trace_request}" \
"\n Headers: #{request_headers(request)}"
parent_context = tracer.extract(OpenTracing::FORMAT_RACK, request.env)
server_span = tracer.start_span('/join_trace', child_of: parent_context)
response = {
span: observe_span(server_span),
notImplementedError: ''
}
if trace_request['downstream']
downstream = trace_request['downstream']
transport = downstream['transport']
response[:downstream] =
if transport == 'HTTP'
call_downstream_http(downstream, server_span)
elsif transport == 'DUMMY'
{ notImplementedError: 'Dummy has not been implemented' }
else
{ notImplementedError: "Unrecognized transport received: #{transport}" }
end
end
puts "Response: #{response}"
server_span.finish
body JSON.dump(response)
end
post '/create_traces' do
puts "Got request to create traces: #{trace_request}"
trace_request['count'].times do
span = tracer.start_span(trace_request['operation'], tags: trace_request['tags'])
span.finish
end
status 200
end
private
def tracer
@tracer ||= Jaeger::Client.build(
service_name: 'crossdock-ruby',
host: 'jaeger-agent',
port: 6831,
flush_interval: 1,
sampler: Jaeger::Samplers::Const.new(true)
)
end
def trace_request
@trace_request ||= begin
request.body.rewind
JSON.parse(request.body.read)
end
end
def observe_span(span)
if span
{
traceId: span.context.to_trace_id,
sampled: span.context.sampled?,
baggage: span.get_baggage_item('crossdock-baggage-key')
}
else
{
traceId: 'no span found',
sampled: false,
baggage: 'no span found'
}
end
end
def call_downstream_http(downstream, server_span)
downstream_url = "http://#{downstream['host']}:#{downstream['port']}/join_trace"
client_span = tracer.start_span('client-span', child_of: server_span)
headers = { 'Content-Type' => 'application/json' }
tracer.inject(client_span.context, OpenTracing::FORMAT_RACK, headers)
response = Net::HTTP.post(
URI(downstream_url),
JSON.dump(
serverRole: downstream['serverRole'],
downstream: downstream['downstream']
),
headers
)
client_span.finish
if response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
else
{ error: response.body }
end
end
def request_headers(request)
request.env.select do |key, _value|
key.start_with?('HTTP_')
end
end
end
threads = []
threads << Thread.new do
Rack::Handler::WEBrick.run(HealthServer, Port: 8080, Host: '0.0.0.0')
end
threads << Thread.new do
Rack::Handler::WEBrick.run(HttpServer, Port: 8081, Host: '0.0.0.0')
end
threads.each(&:join)

View file

@ -0,0 +1,32 @@
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'jaeger/client/version'
Gem::Specification.new do |spec|
spec.name = 'jaeger-client'
spec.version = Jaeger::Client::VERSION
spec.authors = ['SaleMove TechMovers']
spec.email = ['techmovers@salemove.com']
spec.summary = 'OpenTracing Tracer implementation for Jaeger in Ruby'
spec.description = ''
spec.homepage = 'https://github.com/salemove/jaeger-client-ruby'
spec.license = 'MIT'
spec.files = `git ls-files -z`.split("\x0").reject do |f|
f.match(%r{^(test|spec|features)/})
end
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ['lib']
spec.add_development_dependency 'bundler', '~> 1.14'
spec.add_development_dependency 'rake', '~> 10.0'
spec.add_development_dependency 'rspec', '~> 3.0'
spec.add_development_dependency 'rubocop', '~> 0.54.0'
spec.add_development_dependency 'rubocop-rspec', '~> 1.24.0'
spec.add_development_dependency 'timecop', '~> 0.9'
spec.add_dependency 'opentracing', '~> 0.3'
spec.add_dependency 'thrift'
end

View file

@ -0,0 +1,70 @@
# frozen_string_literal: true
$LOAD_PATH.push(File.dirname(__FILE__) + '/../../thrift/gen-rb')
require 'opentracing'
require 'jaeger/thrift/agent'
require 'logger'
require_relative 'tracer'
require_relative 'span'
require_relative 'span_context'
require_relative 'scope'
require_relative 'scope_manager'
require_relative 'trace_id'
require_relative 'udp_sender'
require_relative 'http_sender'
require_relative 'reporters'
require_relative 'client/version'
require_relative 'samplers'
require_relative 'encoders/thrift_encoder'
require_relative 'injectors'
require_relative 'extractors'
require_relative 'rate_limiter'
module Jaeger
module Client
# We initially had everything under Jaeger::Client namespace. This however
# was not very useful and was removed. These assignments are here for
# backwards compatibility. Fine to remove in the next major version.
UdpSender = Jaeger::UdpSender
HttpSender = Jaeger::HttpSender
Encoders = Jaeger::Encoders
Samplers = Jaeger::Samplers
Reporters = Jaeger::Reporters
Injectors = Jaeger::Injectors
Extractors = Jaeger::Extractors
DEFAULT_FLUSH_INTERVAL = 10
def self.build(host: '127.0.0.1',
port: 6831,
service_name:,
flush_interval: DEFAULT_FLUSH_INTERVAL,
sampler: Samplers::Const.new(true),
logger: Logger.new(STDOUT),
sender: nil,
reporter: nil,
injectors: {},
extractors: {})
encoder = Encoders::ThriftEncoder.new(service_name: service_name)
if sender
warn '[DEPRECATION] Passing `sender` directly to Jaeger::Client.build is deprecated.' \
'Please use `reporter` instead.'
end
reporter ||= Reporters::RemoteReporter.new(
sender: sender || UdpSender.new(host: host, port: port, encoder: encoder, logger: logger),
flush_interval: flush_interval
)
Tracer.new(
reporter: reporter,
sampler: sampler,
injectors: Injectors.prepare(injectors),
extractors: Extractors.prepare(extractors)
)
end
end
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
module Jaeger
module Client
VERSION = '0.10.0'.freeze
end
end

View file

@ -0,0 +1,92 @@
# frozen_string_literal: true
module Jaeger
module Encoders
class ThriftEncoder
def initialize(service_name:)
@service_name = service_name
@tags = [
Jaeger::Thrift::Tag.new(
'key' => 'jaeger.version',
'vType' => Jaeger::Thrift::TagType::STRING,
'vStr' => 'Ruby-' + Jaeger::Client::VERSION
),
Jaeger::Thrift::Tag.new(
'key' => 'hostname',
'vType' => Jaeger::Thrift::TagType::STRING,
'vStr' => Socket.gethostname
)
]
ipv4 = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }
unless ipv4.nil? # rubocop:disable Style/GuardClause
@tags << Jaeger::Thrift::Tag.new(
'key' => 'ip',
'vType' => Jaeger::Thrift::TagType::STRING,
'vStr' => ipv4.ip_address
)
end
end
def encode(spans)
Jaeger::Thrift::Batch.new(
'process' => Jaeger::Thrift::Process.new(
'serviceName' => @service_name,
'tags' => @tags
),
'spans' => spans.map(&method(:encode_span))
)
end
private
def encode_span(span)
context = span.context
start_ts, duration = build_timestamps(span)
Jaeger::Thrift::Span.new(
'traceIdLow' => TraceId.uint64_id_to_int64(context.trace_id),
'traceIdHigh' => 0,
'spanId' => TraceId.uint64_id_to_int64(context.span_id),
'parentSpanId' => TraceId.uint64_id_to_int64(context.parent_id),
'operationName' => span.operation_name,
'references' => build_references(span.references || []),
'flags' => context.flags,
'startTime' => start_ts,
'duration' => duration,
'tags' => span.tags,
'logs' => span.logs
)
end
def build_references(references)
references.map do |ref|
Jaeger::Thrift::SpanRef.new(
'refType' => span_ref_type(ref.type),
'traceIdLow' => TraceId.uint64_id_to_int64(ref.context.trace_id),
'traceIdHigh' => 0,
'spanId' => TraceId.uint64_id_to_int64(ref.context.span_id)
)
end
end
def build_timestamps(span)
start_ts = (span.start_time.to_f * 1_000_000).to_i
end_ts = (span.end_time.to_f * 1_000_000).to_i
duration = end_ts - start_ts
[start_ts, duration]
end
def span_ref_type(type)
case type
when OpenTracing::Reference::CHILD_OF
Jaeger::Thrift::SpanRefType::CHILD_OF
when OpenTracing::Reference::FOLLOWS_FROM
Jaeger::Thrift::SpanRefType::FOLLOWS_FROM
else
warn "Jaeger::Client with format #{type} is not supported yet"
nil
end
end
end
end
end

View file

@ -0,0 +1,109 @@
# frozen_string_literal: true
module Jaeger
module Extractors
class SerializedJaegerTrace
def self.parse(trace)
return nil if !trace || trace == ''
trace_arguments = trace.split(':').map(&TraceId.method(:base16_hex_id_to_uint64))
return nil if trace_arguments.size != 4
trace_id, span_id, parent_id, flags = trace_arguments
return nil if trace_id.zero? || span_id.zero?
SpanContext.new(
trace_id: trace_id,
parent_id: parent_id,
span_id: span_id,
flags: flags
)
end
end
class JaegerTextMapCodec
def self.extract(carrier)
context = SerializedJaegerTrace.parse(carrier['uber-trace-id'])
return nil unless context
carrier.each do |key, value|
baggage_match = key.match(/\Auberctx-([\w-]+)\Z/)
if baggage_match
context.set_baggage_item(baggage_match[1], value)
end
end
context
end
end
class JaegerRackCodec
def self.extract(carrier)
serialized_trace = carrier['HTTP_UBER_TRACE_ID']
serialized_trace = CGI.unescape(serialized_trace) if serialized_trace
context = SerializedJaegerTrace.parse(serialized_trace)
return nil unless context
carrier.each do |key, value|
baggage_match = key.match(/\AHTTP_UBERCTX_(\w+)\Z/)
if baggage_match
key = baggage_match[1].downcase.tr('_', '-')
context.set_baggage_item(key, CGI.unescape(value))
end
end
context
end
end
class JaegerBinaryCodec
def self.extract(_carrier)
warn 'Jaeger::Client with binary format is not supported yet'
end
end
class B3RackCodec
def self.extract(carrier)
trace_id = TraceId.base16_hex_id_to_uint64(carrier['HTTP_X_B3_TRACEID'])
span_id = TraceId.base16_hex_id_to_uint64(carrier['HTTP_X_B3_SPANID'])
parent_id = TraceId.base16_hex_id_to_uint64(carrier['HTTP_X_B3_PARENTSPANID'])
flags = parse_flags(carrier['HTTP_X_B3_FLAGS'], carrier['HTTP_X_B3_SAMPLED'])
return nil if span_id.nil? || trace_id.nil?
return nil if span_id.zero? || trace_id.zero?
SpanContext.new(
trace_id: trace_id,
parent_id: parent_id,
span_id: span_id,
flags: flags
)
end
# if the flags header is '1' then the sampled header should not be present
def self.parse_flags(flags_header, sampled_header)
if flags_header == '1'
Jaeger::SpanContext::Flags::DEBUG
else
TraceId.base16_hex_id_to_uint64(sampled_header)
end
end
private_class_method :parse_flags
end
DEFAULT_EXTRACTORS = {
OpenTracing::FORMAT_TEXT_MAP => JaegerTextMapCodec,
OpenTracing::FORMAT_BINARY => JaegerBinaryCodec,
OpenTracing::FORMAT_RACK => JaegerRackCodec
}.freeze
def self.prepare(extractors)
DEFAULT_EXTRACTORS.reduce(extractors) do |acc, (format, default)|
provided_extractors = Array(extractors[format])
provided_extractors += [default] if provided_extractors.empty?
acc.merge(format => provided_extractors)
end
end
end
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'logger'
module Jaeger
class HttpSender
def initialize(url:, headers: {}, encoder:, logger: Logger.new(STDOUT))
@encoder = encoder
@logger = logger
@uri = URI(url)
@uri.query = 'format=jaeger.thrift'
@transport = ::Thrift::HTTPClientTransport.new(@uri.to_s)
@transport.add_headers(headers)
@serializer = ::Thrift::Serializer.new
end
def send_spans(spans)
batch = @encoder.encode(spans)
@transport.write(@serializer.serialize(batch))
@transport.flush
rescue StandardError => error
@logger.error("Failure while sending a batch of spans: #{error}")
end
end
end

View file

@ -0,0 +1,69 @@
# frozen_string_literal: true
module Jaeger
module Injectors
def self.context_as_jaeger_string(span_context)
[
span_context.trace_id.to_s(16),
span_context.span_id.to_s(16),
span_context.parent_id.to_s(16),
span_context.flags.to_s(16)
].join(':')
end
class JaegerTextMapCodec
def self.inject(span_context, carrier)
carrier['uber-trace-id'] = Injectors.context_as_jaeger_string(span_context)
span_context.baggage.each do |key, value|
carrier["uberctx-#{key}"] = value
end
end
end
class JaegerRackCodec
def self.inject(span_context, carrier)
carrier['uber-trace-id'] =
CGI.escape(Injectors.context_as_jaeger_string(span_context))
span_context.baggage.each do |key, value|
carrier["uberctx-#{key}"] = CGI.escape(value)
end
end
end
class JaegerBinaryCodec
def self.inject(_span_context, _carrier)
warn 'Jaeger::Client with binary format is not supported yet'
end
end
class B3RackCodec
def self.inject(span_context, carrier)
carrier['x-b3-traceid'] = TraceId.to_hex(span_context.trace_id)
carrier['x-b3-spanid'] = TraceId.to_hex(span_context.span_id)
carrier['x-b3-parentspanid'] = TraceId.to_hex(span_context.parent_id)
# flags (for debug) and sampled headers are mutually exclusive
if span_context.flags == Jaeger::SpanContext::Flags::DEBUG
carrier['x-b3-flags'] = '1'
else
carrier['x-b3-sampled'] = span_context.flags.to_s(16)
end
end
end
DEFAULT_INJECTORS = {
OpenTracing::FORMAT_TEXT_MAP => JaegerTextMapCodec,
OpenTracing::FORMAT_BINARY => JaegerBinaryCodec,
OpenTracing::FORMAT_RACK => JaegerRackCodec
}.freeze
def self.prepare(extractors)
DEFAULT_INJECTORS.reduce(extractors) do |acc, (format, default)|
provided_extractors = Array(extractors[format])
provided_extractors += [default] if provided_extractors.empty?
acc.merge(format => provided_extractors)
end
end
end
end

View file

@ -0,0 +1,61 @@
# frozen_string_literal: true
module Jaeger
# RateLimiter is based on leaky bucket algorithm, formulated in terms of a
# credits balance that is replenished every time check_credit() method is
# called (tick) by the amount proportional to the time elapsed since the
# last tick, up to the max_balance. A call to check_credit() takes a cost
# of an item we want to pay with the balance. If the balance exceeds the
# cost of the item, the item is "purchased" and the balance reduced,
# indicated by returned value of true. Otherwise the balance is unchanged
# and return false.
#
# This can be used to limit a rate of messages emitted by a service by
# instantiating the Rate Limiter with the max number of messages a service
# is allowed to emit per second, and calling check_credit(1.0) for each
# message to determine if the message is within the rate limit.
#
# It can also be used to limit the rate of traffic in bytes, by setting
# credits_per_second to desired throughput as bytes/second, and calling
# check_credit() with the actual message size.
class RateLimiter
def initialize(credits_per_second:, max_balance:)
@credits_per_second = credits_per_second
@max_balance = max_balance
@balance = max_balance
@last_tick = Time.now
end
def check_credit(item_cost)
update_balance
return false if @balance < item_cost
@balance -= item_cost
true
end
def update(credits_per_second:, max_balance:)
update_balance
@credits_per_second = credits_per_second
# The new balance should be proportional to the old balance
@balance = max_balance * @balance / @max_balance
@max_balance = max_balance
end
private
def update_balance
current_time = Time.now
elapsed_time = current_time - @last_tick
@last_tick = current_time
@balance += elapsed_time * @credits_per_second
return if @balance <= @max_balance
@balance = @max_balance
end
end
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
require_relative 'reporters/composite_reporter'
require_relative 'reporters/in_memory_reporter'
require_relative 'reporters/logging_reporter'
require_relative 'reporters/null_reporter'
require_relative 'reporters/remote_reporter'

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
module Jaeger
module Reporters
class CompositeReporter
def initialize(reporters:)
@reporters = reporters
end
def report(span)
@reporters.each do |reporter|
reporter.report(span)
end
end
end
end
end

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
module Jaeger
module Reporters
class InMemoryReporter
def initialize
@spans = []
@mutex = Mutex.new
end
def report(span)
@mutex.synchronize do
@spans << span
end
end
def spans
@mutex.synchronize do
@spans
end
end
def clear
@mutex.synchronize do
@spans.clear
end
end
end
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Jaeger
module Reporters
class LoggingReporter
def initialize(logger: Logger.new($stdout))
@logger = logger
end
def report(span)
span_info = {
operation_name: span.operation_name,
start_time: span.start_time.iso8601,
end_time: span.end_time.iso8601,
trace_id: span.context.to_trace_id,
span_id: span.context.to_span_id
}
@logger.info "Span reported: #{span_info}"
end
end
end
end

View file

@ -0,0 +1,11 @@
# frozen_string_literal: true
module Jaeger
module Reporters
class NullReporter
def report(_span)
# no-op
end
end
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
require_relative './remote_reporter/buffer'
module Jaeger
module Reporters
class RemoteReporter
def initialize(sender:, flush_interval:)
@sender = sender
@flush_interval = flush_interval
@buffer = Buffer.new
end
def flush
spans = @buffer.retrieve
@sender.send_spans(spans) if spans.any?
spans
end
def report(span)
return if !span.context.sampled? && !span.context.debug?
init_reporter_thread
@buffer << span
end
private
def init_reporter_thread
return if @initializer_pid == Process.pid
@initializer_pid = Process.pid
Thread.new do
loop do
flush
sleep(@flush_interval)
end
end
end
end
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
module Jaeger
module Reporters
class RemoteReporter
class Buffer
def initialize
@buffer = []
@mutex = Mutex.new
end
def <<(element)
@mutex.synchronize do
@buffer << element
true
end
end
def retrieve
@mutex.synchronize do
elements = @buffer.dup
@buffer.clear
elements
end
end
end
end
end
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
require_relative 'samplers/const'
require_relative 'samplers/guaranteed_throughput_probabilistic'
require_relative 'samplers/per_operation'
require_relative 'samplers/probabilistic'
require_relative 'samplers/rate_limiting'

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
module Jaeger
module Samplers
# Const sampler
#
# A sampler that always makes the same decision for new traces depending
# on the initialization value. Use `Jaeger::Samplers::Const.new(true)`
# to mark all new traces as sampled.
class Const
def initialize(decision)
@decision = decision
@tags = {
'sampler.type' => 'const',
'sampler.param' => @decision ? 1 : 0
}
end
def sample?(*)
[@decision, @tags]
end
end
end
end

View file

@ -0,0 +1,40 @@
# frozen_string_literal: true
module Jaeger
module Samplers
# A sampler that leverages both Probabilistic sampler and RateLimiting
# sampler. The RateLimiting is used as a guaranteed lower bound sampler
# such that every operation is sampled at least once in a time interval
# defined by the lower_bound. ie a lower_bound of 1.0 / (60 * 10) will
# sample an operation at least once every 10 minutes.
#
# The Probabilistic sampler is given higher priority when tags are
# emitted, ie. if is_sampled() for both samplers return true, the tags
# for Probabilistic sampler will be used.
class GuaranteedThroughputProbabilistic
attr_reader :tags
def initialize(lower_bound:, rate:, lower_bound_sampler: nil)
@probabilistic_sampler = Probabilistic.new(rate: rate)
@lower_bound_sampler = lower_bound_sampler || RateLimiting.new(max_traces_per_second: lower_bound)
@lower_bound_tags = {
'sampler.type' => 'lowerbound',
'sampler.param' => lower_bound
}
end
def sample?(*args)
is_sampled, probabilistic_tags = @probabilistic_sampler.sample?(*args)
if is_sampled
# We still call lower_bound_sampler to update the rate limiter budget
@lower_bound_sampler.sample?(*args)
return [is_sampled, probabilistic_tags]
end
is_sampled, _tags = @lower_bound_sampler.sample?(*args)
[is_sampled, @lower_bound_tags]
end
end
end
end

View file

@ -0,0 +1,47 @@
# frozen_string_literal: true
module Jaeger
module Samplers
# A sampler that leverages both Probabilistic sampler and RateLimiting
# sampler via the GuaranteedThroughputProbabilistic sampler. This sampler
# keeps track of all operations and delegates calls the the respective
# GuaranteedThroughputProbabilistic sampler.
class PerOperation
DEFAULT_SAMPLING_PROBABILITY = 0.001
DEFAULT_LOWER_BOUND = 1.0 / (10.0 * 60.0) # sample once every 10 minutes'
def initialize(strategies:, max_operations:)
@max_operations = max_operations
@default_sampling_probability =
strategies[:default_sampling_probability] || DEFAULT_SAMPLING_PROBABILITY
@lower_bound = strategies[:default_lower_bound_traces_per_second] || DEFAULT_LOWER_BOUND
@default_sampler = Probabilistic.new(rate: @default_sampling_probability)
@samplers = (strategies[:per_operation_strategies] || []).reduce({}) do |acc, strategy|
operation = strategy.fetch(:operation)
rate = strategy.fetch(:probabilistic_sampling)
sampler = GuaranteedThroughputProbabilistic.new(
lower_bound: @lower_bound,
rate: rate
)
acc.merge(operation => sampler)
end
end
def sample?(opts)
operation_name = opts.fetch(:operation_name)
sampler = @samplers[operation_name]
return sampler.sample?(opts) if sampler
return @default_sampler.sample?(opts) if @samplers.length >= @max_operations
sampler = GuaranteedThroughputProbabilistic.new(
lower_bound: @lower_bound,
rate: @default_sampling_probability
)
@samplers[operation_name] = sampler
sampler.sample?(opts)
end
end
end
end

View file

@ -0,0 +1,26 @@
# frozen_string_literal: true
module Jaeger
module Samplers
# Probabilistic sampler
#
# Sample a portion of traces using trace_id as the random decision
class Probabilistic
def initialize(rate: 0.001)
if rate < 0.0 || rate > 1.0
raise "Sampling rate must be between 0.0 and 1.0, got #{rate.inspect}"
end
@boundary = TraceId::TRACE_ID_UPPER_BOUND * rate
@tags = {
'sampler.type' => 'probabilistic',
'sampler.param' => rate
}
end
def sample?(trace_id:, **)
[@boundary >= trace_id, @tags]
end
end
end
end

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
module Jaeger
module Samplers
# Samples at most max_traces_per_second. The distribution of sampled
# traces follows burstiness of the service, i.e. a service with uniformly
# distributed requests will have those requests sampled uniformly as
# well, but if requests are bursty, especially sub-second, then a number
# of sequential requests can be sampled each second.
class RateLimiting
attr_reader :tags
def initialize(max_traces_per_second: 10)
if max_traces_per_second < 0.0
raise "max_traces_per_second must not be negative, got #{max_traces_per_second}"
end
@rate_limiter = RateLimiter.new(
credits_per_second: max_traces_per_second,
max_balance: [max_traces_per_second, 1.0].max
)
@tags = {
'sampler.type' => 'ratelimiting',
'sampler.param' => max_traces_per_second
}
end
def sample?(*)
[@rate_limiter.check_credit(1.0), @tags]
end
end
end
end

View file

@ -0,0 +1,38 @@
# frozen_string_literal: true
module Jaeger
# Scope represents an OpenTracing Scope
#
# See http://www.opentracing.io for more information.
class Scope
def initialize(span, scope_stack, finish_on_close:)
@span = span
@scope_stack = scope_stack
@finish_on_close = finish_on_close
@closed = false
end
# Return the Span scoped by this Scope
#
# @return [Span]
attr_reader :span
# Close scope
#
# Mark the end of the active period for the current thread and Scope,
# updating the ScopeManager#active in the process.
def close
raise "Tried to close already closed span: #{inspect}" if @closed
@closed = true
@span.finish if @finish_on_close
removed_scope = @scope_stack.pop
if removed_scope != self # rubocop:disable Style/GuardClause
raise 'Removed non-active scope, ' \
"removed: #{removed_scope.inspect}, "\
"expected: #{inspect}"
end
end
end
end

View file

@ -0,0 +1,47 @@
# frozen_string_literal: true
require_relative 'scope_manager/scope_stack'
require_relative 'scope_manager/scope_identifier'
module Jaeger
# ScopeManager represents an OpenTracing ScopeManager
#
# See http://www.opentracing.io for more information.
#
# The ScopeManager interface abstracts both the activation of Span instances
# via ScopeManager#activate and access to an active Span/Scope via
# ScopeManager#active
#
class ScopeManager
def initialize
@scope_stack = ScopeStack.new
end
# Make a span instance active
#
# @param span [Span] the Span that should become active
# @param finish_on_close [Boolean] whether the Span should automatically be
# finished when Scope#close is called
# @return [Scope] instance to control the end of the active period for the
# Span. It is a programming error to neglect to call Scope#close on the
# returned instance.
def activate(span, finish_on_close: true)
return active if active && active.span == span
scope = Scope.new(span, @scope_stack, finish_on_close: finish_on_close)
@scope_stack.push(scope)
scope
end
# Return active scope
#
# If there is a non-null Scope, its wrapped Span becomes an implicit parent
# (as Reference#CHILD_OF) of any newly-created Span at
# Tracer#start_active_span or Tracer#start_span time.
#
# @return [Scope] the currently active Scope which can be used to access the
# currently active Span.
def active
@scope_stack.peek
end
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
module Jaeger
class ScopeManager
# @api private
class ScopeIdentifier
def self.generate
# 65..90.chr are characters between A and Z
"opentracing_#{(0...8).map { rand(65..90).chr }.join}".to_sym
end
end
end
end

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
module Jaeger
class ScopeManager
# @api private
class ScopeStack
def initialize
# Generate a random identifier to use as the Thread.current key. This is
# needed so that it would be possible to create multiple tracers in one
# thread (mostly useful for testing purposes)
@scope_identifier = ScopeIdentifier.generate
end
def push(scope)
store << scope
end
def pop
store.pop
end
def peek
store.last
end
private
def store
Thread.current[@scope_identifier] ||= []
end
end
end
end

View file

@ -0,0 +1,98 @@
# frozen_string_literal: true
require_relative 'span/thrift_tag_builder'
require_relative 'span/thrift_log_builder'
module Jaeger
class Span
attr_accessor :operation_name
attr_reader :context, :start_time, :end_time, :references, :tags, :logs
# Creates a new {Span}
#
# @param context [SpanContext] the context of the span
# @param operation_name [String] the operation name
# @param reporter [#report] span reporter
#
# @return [Span] a new Span
def initialize(context, operation_name, reporter, start_time: Time.now, references: [], tags: {})
@context = context
@operation_name = operation_name
@reporter = reporter
@start_time = start_time
@references = references
@tags = []
@logs = []
tags.each { |key, value| set_tag(key, value) }
end
# Set a tag value on this span
#
# @param key [String] the key of the tag
# @param value [String, Numeric, Boolean] the value of the tag. If it's not
# a String, Numeric, or Boolean it will be encoded with to_s
def set_tag(key, value)
if key == 'sampling.priority'
if value.to_i > 0
return self if @context.debug?
@context.flags = @context.flags | SpanContext::Flags::SAMPLED | SpanContext::Flags::DEBUG
else
@context.flags = @context.flags & ~SpanContext::Flags::SAMPLED
end
return self
end
# Using Thrift::Tag to avoid unnecessary memory allocations
@tags << ThriftTagBuilder.build(key, value)
self
end
# Set a baggage item on the span
#
# @param key [String] the key of the baggage item
# @param value [String] the value of the baggage item
def set_baggage_item(key, value)
@context.set_baggage_item(key, value)
self
end
# Get a baggage item
#
# @param key [String] the key of the baggage item
#
# @return Value of the baggage item
def get_baggage_item(key)
@context.get_baggage_item(key)
end
# Add a log entry to this span
#
# @deprecated Use {#log_kv} instead.
def log(*args)
warn 'Span#log is deprecated. Please use Span#log_kv instead.'
log_kv(*args)
end
# Add a log entry to this span
#
# @param timestamp [Time] time of the log
# @param fields [Hash] Additional information to log
def log_kv(timestamp: Time.now, **fields)
# Using Thrift::Log to avoid unnecessary memory allocations
@logs << ThriftLogBuilder.build(timestamp, fields)
nil
end
# Finish the {Span}
#
# @param end_time [Time] custom end time, if not now
def finish(end_time: Time.now)
@end_time = end_time
@reporter.report(self)
end
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
module Jaeger
class Span
class ThriftLogBuilder
FIELDS = Jaeger::Thrift::Log::FIELDS
TIMESTAMP = FIELDS[Jaeger::Thrift::Log::TIMESTAMP].fetch(:name)
LOG_FIELDS = FIELDS[Jaeger::Thrift::Log::LOG_FIELDS].fetch(:name)
def self.build(timestamp, fields)
Jaeger::Thrift::Log.new(
TIMESTAMP => (timestamp.to_f * 1_000_000).to_i,
LOG_FIELDS => fields.map { |key, value| ThriftTagBuilder.build(key, value) }
)
end
end
end
end

View file

@ -0,0 +1,43 @@
# frozen_string_literal: true
module Jaeger
class Span
class ThriftTagBuilder
FIELDS = Jaeger::Thrift::Tag::FIELDS
KEY = FIELDS[Jaeger::Thrift::Tag::KEY].fetch(:name)
VTYPE = FIELDS[Jaeger::Thrift::Tag::VTYPE].fetch(:name)
VLONG = FIELDS[Jaeger::Thrift::Tag::VLONG].fetch(:name)
VDOUBLE = FIELDS[Jaeger::Thrift::Tag::VDOUBLE].fetch(:name)
VBOOL = FIELDS[Jaeger::Thrift::Tag::VBOOL].fetch(:name)
VSTR = FIELDS[Jaeger::Thrift::Tag::VSTR].fetch(:name)
def self.build(key, value)
if value.is_a?(Integer)
Jaeger::Thrift::Tag.new(
KEY => key.to_s,
VTYPE => Jaeger::Thrift::TagType::LONG,
VLONG => value
)
elsif value.is_a?(Float)
Jaeger::Thrift::Tag.new(
KEY => key.to_s,
VTYPE => Jaeger::Thrift::TagType::DOUBLE,
VDOUBLE => value
)
elsif value.is_a?(TrueClass) || value.is_a?(FalseClass)
Jaeger::Thrift::Tag.new(
KEY => key.to_s,
VTYPE => Jaeger::Thrift::TagType::BOOL,
VBOOL => value
)
else
Jaeger::Thrift::Tag.new(
KEY => key.to_s,
VTYPE => Jaeger::Thrift::TagType::STRING,
VSTR => value.to_s
)
end
end
end
end
end

View file

@ -0,0 +1,57 @@
# frozen_string_literal: true
module Jaeger
# SpanContext holds the data for a span that gets inherited to child spans
class SpanContext
module Flags
NONE = 0x00
SAMPLED = 0x01
DEBUG = 0x02
end
def self.create_from_parent_context(span_context)
new(
trace_id: span_context.trace_id,
parent_id: span_context.span_id,
span_id: TraceId.generate,
flags: span_context.flags,
baggage: span_context.baggage.dup
)
end
attr_reader :span_id, :parent_id, :trace_id, :baggage, :flags
attr_writer :flags
def initialize(span_id:, parent_id: 0, trace_id:, flags:, baggage: {})
@span_id = span_id
@parent_id = parent_id
@trace_id = trace_id
@baggage = baggage
@flags = flags
end
def sampled?
@flags & Flags::SAMPLED == Flags::SAMPLED
end
def debug?
@flags & Flags::DEBUG == Flags::DEBUG
end
def to_trace_id
@to_trace_id ||= @trace_id.to_s(16)
end
def to_span_id
@to_span_id ||= @span_id.to_s(16)
end
def set_baggage_item(key, value)
@baggage[key.to_s] = value.to_s
end
def get_baggage_item(key)
@baggage[key.to_s]
end
end
end

View file

@ -0,0 +1,39 @@
# frozen_string_literal: true
module Jaeger
module TraceId
MAX_64BIT_SIGNED_INT = (1 << 63) - 1
MAX_64BIT_UNSIGNED_INT = (1 << 64) - 1
TRACE_ID_UPPER_BOUND = MAX_64BIT_UNSIGNED_INT + 1
def self.generate
rand(TRACE_ID_UPPER_BOUND)
end
def self.base16_hex_id_to_uint64(id)
return nil unless id
value = id.to_i(16)
value > MAX_64BIT_UNSIGNED_INT || value < 0 ? 0 : value
end
# Thrift defines ID fields as i64, which is signed, therefore we convert
# large IDs (> 2^63) to negative longs
def self.uint64_id_to_int64(id)
id > MAX_64BIT_SIGNED_INT ? id - MAX_64BIT_UNSIGNED_INT - 1 : id
end
# Convert an integer id into a 0 padded hex string.
# If the string is shorter than 16 characters, it will be padded to 16.
# If it is longer than 16 characters, it is padded to 32.
def self.to_hex(id)
hex_str = id.to_s(16)
# pad the string with '0's to 16 or 32 characters
if hex_str.length > 16
hex_str.rjust(32, '0')
else
hex_str.rjust(16, '0')
end
end
end
end

View file

@ -0,0 +1,195 @@
# frozen_string_literal: true
module Jaeger
class Tracer
def initialize(reporter:, sampler:, injectors:, extractors:)
@reporter = reporter
@sampler = sampler
@injectors = injectors
@extractors = extractors
@scope_manager = ScopeManager.new
end
# @return [ScopeManager] the current ScopeManager, which may be a no-op
# but may not be nil.
attr_reader :scope_manager
# @return [Span, nil] the active span. This is a shorthand for
# `scope_manager.active.span`, and nil will be returned if
# Scope#active is nil.
def active_span
scope = scope_manager.active
scope.span if scope
end
# Starts a new span.
#
# This is similar to #start_active_span, but the returned Span will not
# be registered via the ScopeManager.
#
# @param operation_name [String] The operation name for the Span
# @param child_of [SpanContext, Span] SpanContext that acts as a parent to
# the newly-started Span. If a Span instance is provided, its
# context is automatically substituted. See [Reference] for more
# information.
#
# If specified, the `references` parameter must be omitted.
# @param references [Array<Reference>] An array of reference
# objects that identify one or more parent SpanContexts.
# @param start_time [Time] When the Span started, if not now
# @param tags [Hash] Tags to assign to the Span at start time
# @param ignore_active_scope [Boolean] whether to create an implicit
# References#CHILD_OF reference to the ScopeManager#active.
#
# @return [Span] The newly-started Span
def start_span(operation_name,
child_of: nil,
references: nil,
start_time: Time.now,
tags: {},
ignore_active_scope: false,
**)
context, sampler_tags = prepare_span_context(
operation_name: operation_name,
child_of: child_of,
references: references,
ignore_active_scope: ignore_active_scope
)
Span.new(
context,
operation_name,
@reporter,
start_time: start_time,
references: references,
tags: tags.merge(sampler_tags)
)
end
# Creates a newly started and activated Scope
#
# If the Tracer's ScopeManager#active is not nil, no explicit references
# are provided, and `ignore_active_scope` is false, then an inferred
# References#CHILD_OF reference is created to the ScopeManager#active's
# SpanContext when start_active is invoked.
#
# @param operation_name [String] The operation name for the Span
# @param child_of [SpanContext, Span] SpanContext that acts as a parent to
# the newly-started Span. If a Span instance is provided, its
# context is automatically substituted. See [Reference] for more
# information.
#
# If specified, the `references` parameter must be omitted.
# @param references [Array<Reference>] An array of reference
# objects that identify one or more parent SpanContexts.
# @param start_time [Time] When the Span started, if not now
# @param tags [Hash] Tags to assign to the Span at start time
# @param ignore_active_scope [Boolean] whether to create an implicit
# References#CHILD_OF reference to the ScopeManager#active.
# @param finish_on_close [Boolean] whether span should automatically be
# finished when Scope#close is called
# @yield [Scope] If an optional block is passed to start_active it will
# yield the newly-started Scope. If `finish_on_close` is true then the
# Span will be finished automatically after the block is executed.
# @return [Scope] The newly-started and activated Scope
def start_active_span(operation_name,
child_of: nil,
references: nil,
start_time: Time.now,
tags: {},
ignore_active_scope: false,
finish_on_close: true,
**)
span = start_span(
operation_name,
child_of: child_of,
references: references,
start_time: start_time,
tags: tags,
ignore_active_scope: ignore_active_scope
)
scope = @scope_manager.activate(span, finish_on_close: finish_on_close)
if block_given?
begin
yield scope
ensure
scope.close
end
end
scope
end
# Inject a SpanContext into the given carrier
#
# @param span_context [SpanContext]
# @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK]
# @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
def inject(span_context, format, carrier)
@injectors.fetch(format).each do |injector|
injector.inject(span_context, carrier)
end
end
# Extract a SpanContext in the given format from the given carrier.
#
# @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK]
# @param carrier [Carrier] A carrier object of the type dictated by the specified `format`
# @return [SpanContext] the extracted SpanContext or nil if none could be found
def extract(format, carrier)
@extractors
.fetch(format)
.lazy
.map { |extractor| extractor.extract(carrier) }
.reject(&:nil?)
.first
end
private
def prepare_span_context(operation_name:, child_of:, references:, ignore_active_scope:)
context =
context_from_child_of(child_of) ||
context_from_references(references) ||
context_from_active_scope(ignore_active_scope)
if context
[SpanContext.create_from_parent_context(context), {}]
else
trace_id = TraceId.generate
is_sampled, tags = @sampler.sample?(
trace_id: trace_id,
operation_name: operation_name
)
span_context = SpanContext.new(
trace_id: trace_id,
span_id: trace_id,
flags: is_sampled ? SpanContext::Flags::SAMPLED : SpanContext::Flags::NONE
)
[span_context, tags]
end
end
def context_from_child_of(child_of)
return nil unless child_of
child_of.respond_to?(:context) ? child_of.context : child_of
end
def context_from_references(references)
return nil if !references || references.none?
# Prefer CHILD_OF reference if present
ref = references.detect do |reference|
reference.type == OpenTracing::Reference::CHILD_OF
end
(ref || references[0]).context
end
def context_from_active_scope(ignore_active_scope)
return if ignore_active_scope
active_scope = @scope_manager.active
active_scope.span.context if active_scope
end
end
end

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
require_relative './udp_sender/transport'
require 'socket'
module Jaeger
class UdpSender
def initialize(host:, port:, encoder:, logger:)
@encoder = encoder
@logger = logger
transport = Transport.new(host, port)
protocol = ::Thrift::CompactProtocol.new(transport)
@client = Jaeger::Thrift::Agent::Client.new(protocol)
end
def send_spans(spans)
batch = @encoder.encode(spans)
@client.emitBatch(batch)
rescue StandardError => error
@logger.error("Failure while sending a batch of spans: #{error}")
end
end
end

View file

@ -0,0 +1,40 @@
# frozen_string_literal: true
module Jaeger
class UdpSender
class Transport
FLAGS = 0
def initialize(host, port)
@socket = UDPSocket.new
@host = host
@port = port
@buffer = ::Thrift::MemoryBufferTransport.new
end
def write(str)
@buffer.write(str)
end
def flush
data = @buffer.read(@buffer.available)
send_bytes(data)
end
def open; end
def close; end
private
def send_bytes(bytes)
@socket.send(bytes, FLAGS, @host, @port)
@socket.flush
rescue Errno::ECONNREFUSED
warn 'Unable to connect to Jaeger Agent'
rescue StandardError => e
warn "Unable to send spans: #{e.message}"
end
end
end
end

View file

@ -0,0 +1,51 @@
#!/usr/bin/env ruby
require 'bundler'
Bundler.setup
require 'jaeger/client'
host = ENV['JAEGER_HOST'] || '127.0.0.1'
port = ENV['JAEGER_HOST'] || 6831
tracer1 = Jaeger::Client.build(host: host, port: port.to_i, service_name: 'test-service', flush_interval: 1)
tracer2 = Jaeger::Client.build(host: host, port: port.to_i, service_name: 'downstream-service', flush_interval: 1)
rpc_span = tracer1.start_span(
'receive request',
tags: { 'span.kind' => 'server' }
)
sleep 0.1
rpc_span.log_kv(event: 'woop di doop', count: 5)
sleep 1
async_request_span = tracer1.start_span(
'request async action',
references: [
OpenTracing::Reference.child_of(rpc_span.context)
],
tags: { 'span.kind' => 'producer' }
)
sleep 0.1
async_request_span.finish
rpc_span.finish
sleep 0.5
async_span = tracer2.start_span(
'async span started after rpc span',
references: [
OpenTracing::Reference.follows_from(async_request_span.context)
],
tags: {
'span.kind' => 'consumer',
'peer.service' => 'downstream-service'
}
)
sleep 0.3 # emulate network delay
async_span.finish
sleep 2
puts 'Finished'

View file

@ -0,0 +1,52 @@
#!/usr/bin/env ruby
require 'bundler'
Bundler.setup
require 'jaeger/client'
host = ENV['JAEGER_HOST'] || '127.0.0.1'
port = ENV['JAEGER_HOST'] || 6831
tracer1 = Jaeger::Client.build(host: host, port: port.to_i, service_name: 'test-service', flush_interval: 1)
tracer2 = Jaeger::Client.build(host: host, port: port.to_i, service_name: 'downstream-service', flush_interval: 1)
outer_span = tracer1.start_span(
'receive request',
tags: { 'span.kind' => 'server' }
)
sleep 0.1
outer_span.log_kv(event: 'woop di doop', count: 5)
sleep 1
inner_span = tracer1.start_span(
'fetch info from downstream',
child_of: outer_span,
tags: {
'span.kind' => 'client',
'peer.service' => 'downstream-service',
'peer.ipv4' => '6.6.6.6',
'peer.port' => 443
}
)
inner_span.set_tag('error', false)
sleep 0.3 # emulate network delay
downstream_span = tracer2.start_span(
'downstream operation',
child_of: inner_span,
tags: { 'span.kind' => 'server' }
)
sleep 0.5
downstream_span.finish
sleep 0.2 # emulate network delay
inner_span.finish
sleep 0.1 # doing something with fetched info
outer_span.finish
sleep 2
puts 'Finished'

View file

@ -0,0 +1,32 @@
# The MIT License (MIT)
#
# Copyright (c) 2016 Uber Technologies, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
include "jaeger.thrift"
include "zipkincore.thrift"
namespace java com.uber.jaeger.agent.thrift
namespace rb Jaeger.Thrift
service Agent {
oneway void emitZipkinBatch(1: list<zipkincore.Span> spans)
oneway void emitBatch(1: jaeger.Batch batch)
}

View file

@ -0,0 +1,116 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
require 'jaeger/thrift/agent_types'
module Jaeger
module Thrift
module Agent
class Client
include ::Thrift::Client
def emitZipkinBatch(spans)
send_emitZipkinBatch(spans)
end
def send_emitZipkinBatch(spans)
send_oneway_message('emitZipkinBatch', EmitZipkinBatch_args, :spans => spans)
end
def emitBatch(batch)
send_emitBatch(batch)
end
def send_emitBatch(batch)
send_oneway_message('emitBatch', EmitBatch_args, :batch => batch)
end
end
class Processor
include ::Thrift::Processor
def process_emitZipkinBatch(seqid, iprot, oprot)
args = read_args(iprot, EmitZipkinBatch_args)
@handler.emitZipkinBatch(args.spans)
return
end
def process_emitBatch(seqid, iprot, oprot)
args = read_args(iprot, EmitBatch_args)
@handler.emitBatch(args.batch)
return
end
end
# HELPER FUNCTIONS AND STRUCTURES
class EmitZipkinBatch_args
include ::Thrift::Struct, ::Thrift::Struct_Union
SPANS = 1
FIELDS = {
SPANS => {:type => ::Thrift::Types::LIST, :name => 'spans', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::Span}}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
class EmitZipkinBatch_result
include ::Thrift::Struct, ::Thrift::Struct_Union
FIELDS = {
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
class EmitBatch_args
include ::Thrift::Struct, ::Thrift::Struct_Union
BATCH = 1
FIELDS = {
BATCH => {:type => ::Thrift::Types::STRUCT, :name => 'batch', :class => ::Jaeger::Thrift::Batch}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
class EmitBatch_result
include ::Thrift::Struct, ::Thrift::Struct_Union
FIELDS = {
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
end
end
end

View file

@ -0,0 +1,118 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
require 'jaeger/thrift/agent/agent_types'
module Jaeger
module Thrift
module Agent
module Agent
class Client
include ::Thrift::Client
def emitZipkinBatch(spans)
send_emitZipkinBatch(spans)
end
def send_emitZipkinBatch(spans)
send_oneway_message('emitZipkinBatch', EmitZipkinBatch_args, :spans => spans)
end
def emitBatch(batch)
send_emitBatch(batch)
end
def send_emitBatch(batch)
send_oneway_message('emitBatch', EmitBatch_args, :batch => batch)
end
end
class Processor
include ::Thrift::Processor
def process_emitZipkinBatch(seqid, iprot, oprot)
args = read_args(iprot, EmitZipkinBatch_args)
@handler.emitZipkinBatch(args.spans)
return
end
def process_emitBatch(seqid, iprot, oprot)
args = read_args(iprot, EmitBatch_args)
@handler.emitBatch(args.batch)
return
end
end
# HELPER FUNCTIONS AND STRUCTURES
class EmitZipkinBatch_args
include ::Thrift::Struct, ::Thrift::Struct_Union
SPANS = 1
FIELDS = {
SPANS => {:type => ::Thrift::Types::LIST, :name => 'spans', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::Span}}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
class EmitZipkinBatch_result
include ::Thrift::Struct, ::Thrift::Struct_Union
FIELDS = {
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
class EmitBatch_args
include ::Thrift::Struct, ::Thrift::Struct_Union
BATCH = 1
FIELDS = {
BATCH => {:type => ::Thrift::Types::STRUCT, :name => 'batch', :class => ::Jaeger::Thrift::Batch}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
class EmitBatch_result
include ::Thrift::Struct, ::Thrift::Struct_Union
FIELDS = {
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
end
end
end
end

View file

@ -0,0 +1,15 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
require 'jaeger/thrift/agent/agent_types'
module Jaeger
module Thrift
module Agent
end
end
end

View file

@ -0,0 +1,17 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
require 'jaeger/thrift/jaeger_types'
require 'jaeger/thrift/zipkin/zipkincore_types'
module Jaeger
module Thrift
module Agent
end
end
end

View file

@ -0,0 +1,13 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
require 'jaeger/thrift/agent_types'
module Jaeger
module Thrift
end
end

View file

@ -0,0 +1,15 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
require 'jaeger/thrift/jaeger_types'
require 'jaeger/thrift/zipkin/zipkincore_types'
module Jaeger
module Thrift
end
end

View file

@ -0,0 +1,82 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
require 'jaeger/thrift/jaeger_types'
module Jaeger
module Thrift
module Collector
class Client
include ::Thrift::Client
def submitBatches(batches)
send_submitBatches(batches)
return recv_submitBatches()
end
def send_submitBatches(batches)
send_message('submitBatches', SubmitBatches_args, :batches => batches)
end
def recv_submitBatches()
result = receive_message(SubmitBatches_result)
return result.success unless result.success.nil?
raise ::Thrift::ApplicationException.new(::Thrift::ApplicationException::MISSING_RESULT, 'submitBatches failed: unknown result')
end
end
class Processor
include ::Thrift::Processor
def process_submitBatches(seqid, iprot, oprot)
args = read_args(iprot, SubmitBatches_args)
result = SubmitBatches_result.new()
result.success = @handler.submitBatches(args.batches)
write_result(result, oprot, 'submitBatches', seqid)
end
end
# HELPER FUNCTIONS AND STRUCTURES
class SubmitBatches_args
include ::Thrift::Struct, ::Thrift::Struct_Union
BATCHES = 1
FIELDS = {
BATCHES => {:type => ::Thrift::Types::LIST, :name => 'batches', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Batch}}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
class SubmitBatches_result
include ::Thrift::Struct, ::Thrift::Struct_Union
SUCCESS = 0
FIELDS = {
SUCCESS => {:type => ::Thrift::Types::LIST, :name => 'success', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::BatchSubmitResponse}}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
end
end
end

View file

@ -0,0 +1,13 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
require 'jaeger/thrift/jaeger_types'
module Jaeger
module Thrift
end
end

View file

@ -0,0 +1,211 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
module Jaeger
module Thrift
module TagType
STRING = 0
DOUBLE = 1
BOOL = 2
LONG = 3
BINARY = 4
VALUE_MAP = {0 => "STRING", 1 => "DOUBLE", 2 => "BOOL", 3 => "LONG", 4 => "BINARY"}
VALID_VALUES = Set.new([STRING, DOUBLE, BOOL, LONG, BINARY]).freeze
end
module SpanRefType
CHILD_OF = 0
FOLLOWS_FROM = 1
VALUE_MAP = {0 => "CHILD_OF", 1 => "FOLLOWS_FROM"}
VALID_VALUES = Set.new([CHILD_OF, FOLLOWS_FROM]).freeze
end
class Tag
include ::Thrift::Struct, ::Thrift::Struct_Union
KEY = 1
VTYPE = 2
VSTR = 3
VDOUBLE = 4
VBOOL = 5
VLONG = 6
VBINARY = 7
FIELDS = {
KEY => {:type => ::Thrift::Types::STRING, :name => 'key'},
VTYPE => {:type => ::Thrift::Types::I32, :name => 'vType', :enum_class => ::Jaeger::Thrift::TagType},
VSTR => {:type => ::Thrift::Types::STRING, :name => 'vStr', :optional => true},
VDOUBLE => {:type => ::Thrift::Types::DOUBLE, :name => 'vDouble', :optional => true},
VBOOL => {:type => ::Thrift::Types::BOOL, :name => 'vBool', :optional => true},
VLONG => {:type => ::Thrift::Types::I64, :name => 'vLong', :optional => true},
VBINARY => {:type => ::Thrift::Types::STRING, :name => 'vBinary', :binary => true, :optional => true}
}
def struct_fields; FIELDS; end
def validate
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field key is unset!') unless @key
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field vType is unset!') unless @vType
unless @vType.nil? || ::Jaeger::Thrift::TagType::VALID_VALUES.include?(@vType)
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Invalid value of field vType!')
end
end
::Thrift::Struct.generate_accessors self
end
class Log
include ::Thrift::Struct, ::Thrift::Struct_Union
TIMESTAMP = 1
LOG_FIELDS = 2
FIELDS = {
TIMESTAMP => {:type => ::Thrift::Types::I64, :name => 'timestamp'},
LOG_FIELDS => {:type => ::Thrift::Types::LIST, :name => 'fields', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Tag}}
}
def struct_fields; FIELDS; end
def validate
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field timestamp is unset!') unless @timestamp
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field fields is unset!') unless @fields
end
::Thrift::Struct.generate_accessors self
end
class SpanRef
include ::Thrift::Struct, ::Thrift::Struct_Union
REFTYPE = 1
TRACEIDLOW = 2
TRACEIDHIGH = 3
SPANID = 4
FIELDS = {
REFTYPE => {:type => ::Thrift::Types::I32, :name => 'refType', :enum_class => ::Jaeger::Thrift::SpanRefType},
TRACEIDLOW => {:type => ::Thrift::Types::I64, :name => 'traceIdLow'},
TRACEIDHIGH => {:type => ::Thrift::Types::I64, :name => 'traceIdHigh'},
SPANID => {:type => ::Thrift::Types::I64, :name => 'spanId'}
}
def struct_fields; FIELDS; end
def validate
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field refType is unset!') unless @refType
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field traceIdLow is unset!') unless @traceIdLow
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field traceIdHigh is unset!') unless @traceIdHigh
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field spanId is unset!') unless @spanId
unless @refType.nil? || ::Jaeger::Thrift::SpanRefType::VALID_VALUES.include?(@refType)
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Invalid value of field refType!')
end
end
::Thrift::Struct.generate_accessors self
end
class Span
include ::Thrift::Struct, ::Thrift::Struct_Union
TRACEIDLOW = 1
TRACEIDHIGH = 2
SPANID = 3
PARENTSPANID = 4
OPERATIONNAME = 5
REFERENCES = 6
FLAGS = 7
STARTTIME = 8
DURATION = 9
TAGS = 10
LOGS = 11
FIELDS = {
TRACEIDLOW => {:type => ::Thrift::Types::I64, :name => 'traceIdLow'},
TRACEIDHIGH => {:type => ::Thrift::Types::I64, :name => 'traceIdHigh'},
SPANID => {:type => ::Thrift::Types::I64, :name => 'spanId'},
PARENTSPANID => {:type => ::Thrift::Types::I64, :name => 'parentSpanId'},
OPERATIONNAME => {:type => ::Thrift::Types::STRING, :name => 'operationName'},
REFERENCES => {:type => ::Thrift::Types::LIST, :name => 'references', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::SpanRef}, :optional => true},
FLAGS => {:type => ::Thrift::Types::I32, :name => 'flags'},
STARTTIME => {:type => ::Thrift::Types::I64, :name => 'startTime'},
DURATION => {:type => ::Thrift::Types::I64, :name => 'duration'},
TAGS => {:type => ::Thrift::Types::LIST, :name => 'tags', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Tag}, :optional => true},
LOGS => {:type => ::Thrift::Types::LIST, :name => 'logs', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Log}, :optional => true}
}
def struct_fields; FIELDS; end
def validate
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field traceIdLow is unset!') unless @traceIdLow
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field traceIdHigh is unset!') unless @traceIdHigh
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field spanId is unset!') unless @spanId
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field parentSpanId is unset!') unless @parentSpanId
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field operationName is unset!') unless @operationName
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field flags is unset!') unless @flags
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field startTime is unset!') unless @startTime
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field duration is unset!') unless @duration
end
::Thrift::Struct.generate_accessors self
end
class Process
include ::Thrift::Struct, ::Thrift::Struct_Union
SERVICENAME = 1
TAGS = 2
FIELDS = {
SERVICENAME => {:type => ::Thrift::Types::STRING, :name => 'serviceName'},
TAGS => {:type => ::Thrift::Types::LIST, :name => 'tags', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Tag}, :optional => true}
}
def struct_fields; FIELDS; end
def validate
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field serviceName is unset!') unless @serviceName
end
::Thrift::Struct.generate_accessors self
end
class Batch
include ::Thrift::Struct, ::Thrift::Struct_Union
PROCESS = 1
SPANS = 2
FIELDS = {
PROCESS => {:type => ::Thrift::Types::STRUCT, :name => 'process', :class => ::Jaeger::Thrift::Process},
SPANS => {:type => ::Thrift::Types::LIST, :name => 'spans', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Span}}
}
def struct_fields; FIELDS; end
def validate
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field process is unset!') unless @process
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field spans is unset!') unless @spans
end
::Thrift::Struct.generate_accessors self
end
class BatchSubmitResponse
include ::Thrift::Struct, ::Thrift::Struct_Union
OK = 1
FIELDS = {
OK => {:type => ::Thrift::Types::BOOL, :name => 'ok'}
}
def struct_fields; FIELDS; end
def validate
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field ok is unset!') if @ok.nil?
end
::Thrift::Struct.generate_accessors self
end
end
end

View file

@ -0,0 +1,84 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
require 'jaeger/thrift/zipkin/zipkincore_types'
module Jaeger
module Thrift
module Zipkin
module ZipkinCollector
class Client
include ::Thrift::Client
def submitZipkinBatch(spans)
send_submitZipkinBatch(spans)
return recv_submitZipkinBatch()
end
def send_submitZipkinBatch(spans)
send_message('submitZipkinBatch', SubmitZipkinBatch_args, :spans => spans)
end
def recv_submitZipkinBatch()
result = receive_message(SubmitZipkinBatch_result)
return result.success unless result.success.nil?
raise ::Thrift::ApplicationException.new(::Thrift::ApplicationException::MISSING_RESULT, 'submitZipkinBatch failed: unknown result')
end
end
class Processor
include ::Thrift::Processor
def process_submitZipkinBatch(seqid, iprot, oprot)
args = read_args(iprot, SubmitZipkinBatch_args)
result = SubmitZipkinBatch_result.new()
result.success = @handler.submitZipkinBatch(args.spans)
write_result(result, oprot, 'submitZipkinBatch', seqid)
end
end
# HELPER FUNCTIONS AND STRUCTURES
class SubmitZipkinBatch_args
include ::Thrift::Struct, ::Thrift::Struct_Union
SPANS = 1
FIELDS = {
SPANS => {:type => ::Thrift::Types::LIST, :name => 'spans', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::Span}}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
class SubmitZipkinBatch_result
include ::Thrift::Struct, ::Thrift::Struct_Union
SUCCESS = 0
FIELDS = {
SUCCESS => {:type => ::Thrift::Types::LIST, :name => 'success', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::Response}}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
end
end
end
end

View file

@ -0,0 +1,41 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
require 'jaeger/thrift/zipkin/zipkincore_types'
module Jaeger
module Thrift
module Zipkin
CLIENT_SEND = %q"cs"
CLIENT_RECV = %q"cr"
SERVER_SEND = %q"ss"
SERVER_RECV = %q"sr"
WIRE_SEND = %q"ws"
WIRE_RECV = %q"wr"
CLIENT_SEND_FRAGMENT = %q"csf"
CLIENT_RECV_FRAGMENT = %q"crf"
SERVER_SEND_FRAGMENT = %q"ssf"
SERVER_RECV_FRAGMENT = %q"srf"
LOCAL_COMPONENT = %q"lc"
CLIENT_ADDR = %q"ca"
SERVER_ADDR = %q"sa"
end
end
end

View file

@ -0,0 +1,220 @@
#
# Autogenerated by Thrift Compiler (0.10.0)
#
# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
#
require 'thrift'
module Jaeger
module Thrift
module Zipkin
module AnnotationType
BOOL = 0
BYTES = 1
I16 = 2
I32 = 3
I64 = 4
DOUBLE = 5
STRING = 6
VALUE_MAP = {0 => "BOOL", 1 => "BYTES", 2 => "I16", 3 => "I32", 4 => "I64", 5 => "DOUBLE", 6 => "STRING"}
VALID_VALUES = Set.new([BOOL, BYTES, I16, I32, I64, DOUBLE, STRING]).freeze
end
# Indicates the network context of a service recording an annotation with two
# exceptions.
#
# When a BinaryAnnotation, and key is CLIENT_ADDR or SERVER_ADDR,
# the endpoint indicates the source or destination of an RPC. This exception
# allows zipkin to display network context of uninstrumented services, or
# clients such as web browsers.
class Endpoint
include ::Thrift::Struct, ::Thrift::Struct_Union
IPV4 = 1
PORT = 2
SERVICE_NAME = 3
FIELDS = {
# IPv4 host address packed into 4 bytes.
#
# Ex for the ip 1.2.3.4, it would be (1 << 24) | (2 << 16) | (3 << 8) | 4
IPV4 => {:type => ::Thrift::Types::I32, :name => 'ipv4'},
# IPv4 port
#
# Note: this is to be treated as an unsigned integer, so watch for negatives.
#
# Conventionally, when the port isn't known, port = 0.
PORT => {:type => ::Thrift::Types::I16, :name => 'port'},
# Service name in lowercase, such as "memcache" or "zipkin-web"
#
# Conventionally, when the service name isn't known, service_name = "unknown".
SERVICE_NAME => {:type => ::Thrift::Types::STRING, :name => 'service_name'}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
# An annotation is similar to a log statement. It includes a host field which
# allows these events to be attributed properly, and also aggregatable.
class Annotation
include ::Thrift::Struct, ::Thrift::Struct_Union
TIMESTAMP = 1
VALUE = 2
HOST = 3
FIELDS = {
# Microseconds from epoch.
#
# This value should use the most precise value possible. For example,
# gettimeofday or syncing nanoTime against a tick of currentTimeMillis.
TIMESTAMP => {:type => ::Thrift::Types::I64, :name => 'timestamp'},
VALUE => {:type => ::Thrift::Types::STRING, :name => 'value'},
# Always the host that recorded the event. By specifying the host you allow
# rollup of all events (such as client requests to a service) by IP address.
HOST => {:type => ::Thrift::Types::STRUCT, :name => 'host', :class => ::Jaeger::Thrift::Zipkin::Endpoint, :optional => true}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
# Binary annotations are tags applied to a Span to give it context. For
# example, a binary annotation of "http.uri" could the path to a resource in a
# RPC call.
#
# Binary annotations of type STRING are always queryable, though more a
# historical implementation detail than a structural concern.
#
# Binary annotations can repeat, and vary on the host. Similar to Annotation,
# the host indicates who logged the event. This allows you to tell the
# difference between the client and server side of the same key. For example,
# the key "http.uri" might be different on the client and server side due to
# rewriting, like "/api/v1/myresource" vs "/myresource. Via the host field,
# you can see the different points of view, which often help in debugging.
class BinaryAnnotation
include ::Thrift::Struct, ::Thrift::Struct_Union
KEY = 1
VALUE = 2
ANNOTATION_TYPE = 3
HOST = 4
FIELDS = {
KEY => {:type => ::Thrift::Types::STRING, :name => 'key'},
VALUE => {:type => ::Thrift::Types::STRING, :name => 'value', :binary => true},
ANNOTATION_TYPE => {:type => ::Thrift::Types::I32, :name => 'annotation_type', :enum_class => ::Jaeger::Thrift::Zipkin::AnnotationType},
# The host that recorded tag, which allows you to differentiate between
# multiple tags with the same key. There are two exceptions to this.
#
# When the key is CLIENT_ADDR or SERVER_ADDR, host indicates the source or
# destination of an RPC. This exception allows zipkin to display network
# context of uninstrumented services, or clients such as web browsers.
HOST => {:type => ::Thrift::Types::STRUCT, :name => 'host', :class => ::Jaeger::Thrift::Zipkin::Endpoint, :optional => true}
}
def struct_fields; FIELDS; end
def validate
unless @annotation_type.nil? || ::Jaeger::Thrift::Zipkin::AnnotationType::VALID_VALUES.include?(@annotation_type)
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Invalid value of field annotation_type!')
end
end
::Thrift::Struct.generate_accessors self
end
# A trace is a series of spans (often RPC calls) which form a latency tree.
#
# The root span is where trace_id = id and parent_id = Nil. The root span is
# usually the longest interval in the trace, starting with a SERVER_RECV
# annotation and ending with a SERVER_SEND.
class Span
include ::Thrift::Struct, ::Thrift::Struct_Union
TRACE_ID = 1
NAME = 3
ID = 4
PARENT_ID = 5
ANNOTATIONS = 6
BINARY_ANNOTATIONS = 8
DEBUG = 9
TIMESTAMP = 10
DURATION = 11
FIELDS = {
TRACE_ID => {:type => ::Thrift::Types::I64, :name => 'trace_id'},
# Span name in lowercase, rpc method for example
#
# Conventionally, when the span name isn't known, name = "unknown".
NAME => {:type => ::Thrift::Types::STRING, :name => 'name'},
ID => {:type => ::Thrift::Types::I64, :name => 'id'},
PARENT_ID => {:type => ::Thrift::Types::I64, :name => 'parent_id', :optional => true},
ANNOTATIONS => {:type => ::Thrift::Types::LIST, :name => 'annotations', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::Annotation}},
BINARY_ANNOTATIONS => {:type => ::Thrift::Types::LIST, :name => 'binary_annotations', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::BinaryAnnotation}},
DEBUG => {:type => ::Thrift::Types::BOOL, :name => 'debug', :default => false, :optional => true},
# Microseconds from epoch of the creation of this span.
#
# This value should be set directly by instrumentation, using the most
# precise value possible. For example, gettimeofday or syncing nanoTime
# against a tick of currentTimeMillis.
#
# For compatibilty with instrumentation that precede this field, collectors
# or span stores can derive this via Annotation.timestamp.
# For example, SERVER_RECV.timestamp or CLIENT_SEND.timestamp.
#
# This field is optional for compatibility with old data: first-party span
# stores are expected to support this at time of introduction.
TIMESTAMP => {:type => ::Thrift::Types::I64, :name => 'timestamp', :optional => true},
# Measurement of duration in microseconds, used to support queries.
#
# This value should be set directly, where possible. Doing so encourages
# precise measurement decoupled from problems of clocks, such as skew or NTP
# updates causing time to move backwards.
#
# For compatibilty with instrumentation that precede this field, collectors
# or span stores can derive this by subtracting Annotation.timestamp.
# For example, SERVER_SEND.timestamp - SERVER_RECV.timestamp.
#
# If this field is persisted as unset, zipkin will continue to work, except
# duration query support will be implementation-specific. Similarly, setting
# this field non-atomically is implementation-specific.
#
# This field is i64 vs i32 to support spans longer than 35 minutes.
DURATION => {:type => ::Thrift::Types::I64, :name => 'duration', :optional => true}
}
def struct_fields; FIELDS; end
def validate
end
::Thrift::Struct.generate_accessors self
end
class Response
include ::Thrift::Struct, ::Thrift::Struct_Union
OK = 1
FIELDS = {
OK => {:type => ::Thrift::Types::BOOL, :name => 'ok'}
}
def struct_fields; FIELDS; end
def validate
raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field ok is unset!') if @ok.nil?
end
::Thrift::Struct.generate_accessors self
end
end
end
end

View file

@ -0,0 +1,88 @@
# Copyright (c) 2016 Uber Technologies, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
namespace java com.uber.jaeger.thriftjava
namespace rb Jaeger.Thrift
# TagType denotes the type of a Tag's value.
enum TagType { STRING, DOUBLE, BOOL, LONG, BINARY }
# Tag is a basic strongly typed key/value pair. It has been flattened to reduce the use of pointers in golang
struct Tag {
1: required string key
2: required TagType vType
3: optional string vStr
4: optional double vDouble
5: optional bool vBool
6: optional i64 vLong
7: optional binary vBinary
}
# Log is a timed even with an arbitrary set of tags.
struct Log {
1: required i64 timestamp
2: required list<Tag> fields
}
enum SpanRefType { CHILD_OF, FOLLOWS_FROM }
# SpanRef describes causal relationship of the current span to another span (e.g. 'child-of')
struct SpanRef {
1: required SpanRefType refType
2: required i64 traceIdLow
3: required i64 traceIdHigh
4: required i64 spanId
}
# Span represents a named unit of work performed by a service.
struct Span {
1: required i64 traceIdLow # the least significant 64 bits of a traceID
2: required i64 traceIdHigh # the most significant 64 bits of a traceID; 0 when only 64bit IDs are used
3: required i64 spanId # unique span id (only unique within a given trace)
4: required i64 parentSpanId # since nearly all spans will have parents spans, CHILD_OF refs do not have to be explicit
5: required string operationName
6: optional list<SpanRef> references # causal references to other spans
7: required i32 flags # tbd
8: required i64 startTime
9: required i64 duration
10: optional list<Tag> tags
11: optional list<Log> logs
}
# Process describes the traced process/service that emits spans.
struct Process {
1: required string serviceName
2: optional list<Tag> tags
}
# Batch is a collection of spans reported out of process.
struct Batch {
1: required Process process
2: required list<Span> spans
}
# BatchSubmitResponse is the response on submitting a batch.
struct BatchSubmitResponse {
1: required bool ok # The Collector's client is expected to only log (or emit a counter) when not ok equals false
}
service Collector {
list<BatchSubmitResponse> submitBatches(1: list<Batch> batches)
}

View file

@ -0,0 +1,300 @@
# Copyright 2012 Twitter Inc.
#
# Licensed 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.
namespace java com.twitter.zipkin.thriftjava
#@namespace scala com.twitter.zipkin.thriftscala
namespace rb Jaeger.Thrift.Zipkin
#************** Annotation.value **************
/**
* The client sent ("cs") a request to a server. There is only one send per
* span. For example, if there's a transport error, each attempt can be logged
* as a WIRE_SEND annotation.
*
* If chunking is involved, each chunk could be logged as a separate
* CLIENT_SEND_FRAGMENT in the same span.
*
* Annotation.host is not the server. It is the host which logged the send
* event, almost always the client. When logging CLIENT_SEND, instrumentation
* should also log the SERVER_ADDR.
*/
const string CLIENT_SEND = "cs"
/**
* The client received ("cr") a response from a server. There is only one
* receive per span. For example, if duplicate responses were received, each
* can be logged as a WIRE_RECV annotation.
*
* If chunking is involved, each chunk could be logged as a separate
* CLIENT_RECV_FRAGMENT in the same span.
*
* Annotation.host is not the server. It is the host which logged the receive
* event, almost always the client. The actual endpoint of the server is
* recorded separately as SERVER_ADDR when CLIENT_SEND is logged.
*/
const string CLIENT_RECV = "cr"
/**
* The server sent ("ss") a response to a client. There is only one response
* per span. If there's a transport error, each attempt can be logged as a
* WIRE_SEND annotation.
*
* Typically, a trace ends with a server send, so the last timestamp of a trace
* is often the timestamp of the root span's server send.
*
* If chunking is involved, each chunk could be logged as a separate
* SERVER_SEND_FRAGMENT in the same span.
*
* Annotation.host is not the client. It is the host which logged the send
* event, almost always the server. The actual endpoint of the client is
* recorded separately as CLIENT_ADDR when SERVER_RECV is logged.
*/
const string SERVER_SEND = "ss"
/**
* The server received ("sr") a request from a client. There is only one
* request per span. For example, if duplicate responses were received, each
* can be logged as a WIRE_RECV annotation.
*
* Typically, a trace starts with a server receive, so the first timestamp of a
* trace is often the timestamp of the root span's server receive.
*
* If chunking is involved, each chunk could be logged as a separate
* SERVER_RECV_FRAGMENT in the same span.
*
* Annotation.host is not the client. It is the host which logged the receive
* event, almost always the server. When logging SERVER_RECV, instrumentation
* should also log the CLIENT_ADDR.
*/
const string SERVER_RECV = "sr"
/**
* Optionally logs an attempt to send a message on the wire. Multiple wire send
* events could indicate network retries. A lag between client or server send
* and wire send might indicate queuing or processing delay.
*/
const string WIRE_SEND = "ws"
/**
* Optionally logs an attempt to receive a message from the wire. Multiple wire
* receive events could indicate network retries. A lag between wire receive
* and client or server receive might indicate queuing or processing delay.
*/
const string WIRE_RECV = "wr"
/**
* Optionally logs progress of a (CLIENT_SEND, WIRE_SEND). For example, this
* could be one chunk in a chunked request.
*/
const string CLIENT_SEND_FRAGMENT = "csf"
/**
* Optionally logs progress of a (CLIENT_RECV, WIRE_RECV). For example, this
* could be one chunk in a chunked response.
*/
const string CLIENT_RECV_FRAGMENT = "crf"
/**
* Optionally logs progress of a (SERVER_SEND, WIRE_SEND). For example, this
* could be one chunk in a chunked response.
*/
const string SERVER_SEND_FRAGMENT = "ssf"
/**
* Optionally logs progress of a (SERVER_RECV, WIRE_RECV). For example, this
* could be one chunk in a chunked request.
*/
const string SERVER_RECV_FRAGMENT = "srf"
#***** BinaryAnnotation.key ******
/**
* The value of "lc" is the component or namespace of a local span.
*
* BinaryAnnotation.host adds service context needed to support queries.
*
* Local Component("lc") supports three key features: flagging, query by
* service and filtering Span.name by namespace.
*
* While structurally the same, local spans are fundamentally different than
* RPC spans in how they should be interpreted. For example, zipkin v1 tools
* center on RPC latency and service graphs. Root local-spans are neither
* indicative of critical path RPC latency, nor have impact on the shape of a
* service graph. By flagging with "lc", tools can special-case local spans.
*
* Zipkin v1 Spans are unqueryable unless they can be indexed by service name.
* The only path to a service name is by (Binary)?Annotation.host.serviceName.
* By logging "lc", a local span can be queried even if no other annotations
* are logged.
*
* The value of "lc" is the namespace of Span.name. For example, it might be
* "finatra2", for a span named "bootstrap". "lc" allows you to resolves
* conflicts for the same Span.name, for example "finatra/bootstrap" vs
* "finch/bootstrap". Using local component, you'd search for spans named
* "bootstrap" where "lc=finch"
*/
const string LOCAL_COMPONENT = "lc"
#***** BinaryAnnotation.key where value = [1] and annotation_type = BOOL ******
/**
* Indicates a client address ("ca") in a span. Most likely, there's only one.
* Multiple addresses are possible when a client changes its ip or port within
* a span.
*/
const string CLIENT_ADDR = "ca"
/**
* Indicates a server address ("sa") in a span. Most likely, there's only one.
* Multiple addresses are possible when a client is redirected, or fails to a
* different server ip or port.
*/
const string SERVER_ADDR = "sa"
/**
* Indicates the network context of a service recording an annotation with two
* exceptions.
*
* When a BinaryAnnotation, and key is CLIENT_ADDR or SERVER_ADDR,
* the endpoint indicates the source or destination of an RPC. This exception
* allows zipkin to display network context of uninstrumented services, or
* clients such as web browsers.
*/
struct Endpoint {
/**
* IPv4 host address packed into 4 bytes.
*
* Ex for the ip 1.2.3.4, it would be (1 << 24) | (2 << 16) | (3 << 8) | 4
*/
1: i32 ipv4
/**
* IPv4 port
*
* Note: this is to be treated as an unsigned integer, so watch for negatives.
*
* Conventionally, when the port isn't known, port = 0.
*/
2: i16 port
/**
* Service name in lowercase, such as "memcache" or "zipkin-web"
*
* Conventionally, when the service name isn't known, service_name = "unknown".
*/
3: string service_name
}
/**
* An annotation is similar to a log statement. It includes a host field which
* allows these events to be attributed properly, and also aggregatable.
*/
struct Annotation {
/**
* Microseconds from epoch.
*
* This value should use the most precise value possible. For example,
* gettimeofday or syncing nanoTime against a tick of currentTimeMillis.
*/
1: i64 timestamp
2: string value // what happened at the timestamp?
/**
* Always the host that recorded the event. By specifying the host you allow
* rollup of all events (such as client requests to a service) by IP address.
*/
3: optional Endpoint host
// don't reuse 4: optional i32 OBSOLETE_duration // how long did the operation take? microseconds
}
enum AnnotationType { BOOL, BYTES, I16, I32, I64, DOUBLE, STRING }
/**
* Binary annotations are tags applied to a Span to give it context. For
* example, a binary annotation of "http.uri" could the path to a resource in a
* RPC call.
*
* Binary annotations of type STRING are always queryable, though more a
* historical implementation detail than a structural concern.
*
* Binary annotations can repeat, and vary on the host. Similar to Annotation,
* the host indicates who logged the event. This allows you to tell the
* difference between the client and server side of the same key. For example,
* the key "http.uri" might be different on the client and server side due to
* rewriting, like "/api/v1/myresource" vs "/myresource. Via the host field,
* you can see the different points of view, which often help in debugging.
*/
struct BinaryAnnotation {
1: string key,
2: binary value,
3: AnnotationType annotation_type,
/**
* The host that recorded tag, which allows you to differentiate between
* multiple tags with the same key. There are two exceptions to this.
*
* When the key is CLIENT_ADDR or SERVER_ADDR, host indicates the source or
* destination of an RPC. This exception allows zipkin to display network
* context of uninstrumented services, or clients such as web browsers.
*/
4: optional Endpoint host
}
/**
* A trace is a series of spans (often RPC calls) which form a latency tree.
*
* The root span is where trace_id = id and parent_id = Nil. The root span is
* usually the longest interval in the trace, starting with a SERVER_RECV
* annotation and ending with a SERVER_SEND.
*/
struct Span {
1: i64 trace_id # unique trace id, use for all spans in trace
/**
* Span name in lowercase, rpc method for example
*
* Conventionally, when the span name isn't known, name = "unknown".
*/
3: string name,
4: i64 id, # unique span id, only used for this span
5: optional i64 parent_id, # parent span id
6: list<Annotation> annotations, # all annotations/events that occured, sorted by timestamp
8: list<BinaryAnnotation> binary_annotations # any binary annotations
9: optional bool debug = 0 # if true, we DEMAND that this span passes all samplers
/**
* Microseconds from epoch of the creation of this span.
*
* This value should be set directly by instrumentation, using the most
* precise value possible. For example, gettimeofday or syncing nanoTime
* against a tick of currentTimeMillis.
*
* For compatibilty with instrumentation that precede this field, collectors
* or span stores can derive this via Annotation.timestamp.
* For example, SERVER_RECV.timestamp or CLIENT_SEND.timestamp.
*
* This field is optional for compatibility with old data: first-party span
* stores are expected to support this at time of introduction.
*/
10: optional i64 timestamp,
/**
* Measurement of duration in microseconds, used to support queries.
*
* This value should be set directly, where possible. Doing so encourages
* precise measurement decoupled from problems of clocks, such as skew or NTP
* updates causing time to move backwards.
*
* For compatibilty with instrumentation that precede this field, collectors
* or span stores can derive this by subtracting Annotation.timestamp.
* For example, SERVER_SEND.timestamp - SERVER_RECV.timestamp.
*
* If this field is persisted as unset, zipkin will continue to work, except
* duration query support will be implementation-specific. Similarly, setting
* this field non-atomically is implementation-specific.
*
* This field is i64 vs i32 to support spans longer than 35 minutes.
*/
11: optional i64 duration
}
# define TChannel service
struct Response {
1: required bool ok
}
service ZipkinCollector {
list<Response> submitZipkinBatch(1: list<Span> spans)
}