├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE.txt ├── Makefile ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── crossdock ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── docker-compose.yml ├── jaeger-docker-compose.yml ├── rules.mk └── server ├── jaeger-client.gemspec ├── lib └── jaeger │ ├── client.rb │ ├── client │ └── version.rb │ ├── encoders │ └── thrift_encoder.rb │ ├── extractors.rb │ ├── http_sender.rb │ ├── injectors.rb │ ├── rate_limiter.rb │ ├── recurring_executor.rb │ ├── reporters.rb │ ├── reporters │ ├── composite_reporter.rb │ ├── in_memory_reporter.rb │ ├── logging_reporter.rb │ ├── null_reporter.rb │ ├── remote_reporter.rb │ └── remote_reporter │ │ └── buffer.rb │ ├── samplers.rb │ ├── samplers │ ├── const.rb │ ├── guaranteed_throughput_probabilistic.rb │ ├── per_operation.rb │ ├── probabilistic.rb │ ├── rate_limiting.rb │ ├── remote_controlled.rb │ └── remote_controlled │ │ └── instructions_fetcher.rb │ ├── scope.rb │ ├── scope_manager.rb │ ├── scope_manager │ ├── scope_identifier.rb │ └── scope_stack.rb │ ├── span.rb │ ├── span │ └── thrift_log_builder.rb │ ├── span_context.rb │ ├── thrift_tag_builder.rb │ ├── trace_id.rb │ ├── tracer.rb │ ├── udp_sender.rb │ └── udp_sender │ └── transport.rb ├── script ├── create_follows_from_trace └── create_trace ├── spec ├── jaeger │ ├── encoders │ │ └── thrift_encoder_spec.rb │ ├── extractors │ │ ├── b3_rack_codec_spec.rb │ │ ├── b3_text_map_codec_spec.rb │ │ ├── jaeger_rack_codec_spec.rb │ │ ├── jaeger_text_map_codec_spec.rb │ │ └── trace_context_rack_codec_spec.rb │ ├── injectors │ │ ├── b3_rack_codec_spec.rb │ │ ├── jaeger_rack_codec_spec.rb │ │ ├── jaeger_text_map_codec_spec.rb │ │ └── trace_context_rack_codec_spec.rb │ ├── rate_limiter_spec.rb │ ├── recurring_executor_spec.rb │ ├── reporters │ │ ├── composite_reporter_spec.rb │ │ ├── in_memory_reporter_spec.rb │ │ ├── logging_reporter_spec.rb │ │ ├── null_reporter_spec.rb │ │ └── remote_reporter_spec.rb │ ├── samplers │ │ ├── const_spec.rb │ │ ├── guaranteed_throughput_probabilistic_spec.rb │ │ ├── per_operation_spec.rb │ │ ├── probabilistic_spec.rb │ │ ├── rate_limiting_spec.rb │ │ ├── remote_controlled │ │ │ └── instructions_fetcher_spec.rb │ │ └── remote_controlled_spec.rb │ ├── scope_manager │ │ └── scope_identifier_spec.rb │ ├── scope_manager_spec.rb │ ├── scope_spec.rb │ ├── span_context_spec.rb │ ├── span_spec.rb │ ├── trace_id_spec.rb │ └── tracer_spec.rb └── spec_helper.rb └── thrift ├── agent.thrift ├── gen-rb └── jaeger │ └── thrift │ ├── agent.rb │ ├── agent │ ├── agent.rb │ ├── agent_constants.rb │ └── agent_types.rb │ ├── agent_constants.rb │ ├── agent_types.rb │ ├── collector.rb │ ├── jaeger_constants.rb │ ├── jaeger_types.rb │ └── zipkin │ ├── zipkin_collector.rb │ ├── zipkincore_constants.rb │ └── zipkincore_types.rb ├── jaeger.thrift └── zipkincore.thrift /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Ruby 2 | 3 | on: 4 | push: 5 | pull_request: 6 | branches: [ master ] 7 | schedule: 8 | # Runs at 00:00 UTC on the 1st of every month. 9 | - cron: '0 0 1 * *' 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | ruby-version: ['2.7.2', '3.0', '3.1'] 17 | 18 | services: 19 | postgres: 20 | image: docker 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | submodules: true 25 | - name: Set up Ruby 26 | uses: ruby/setup-ruby@v1 27 | with: 28 | ruby-version: ${{ matrix.ruby-version }} 29 | bundler-cache: true 30 | - name: Run tests 31 | run: bundle exec rake 32 | - name: Run crossdock 33 | run: make crossdock 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | 11 | # rspec failure tracking 12 | .rspec_status 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "idl"] 2 | path = idl 3 | url = https://github.com/jaegertracing/jaeger-idl.git 4 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | require: rubocop-rspec 4 | 5 | AllCops: 6 | Exclude: 7 | - 'thrift/**/*' 8 | - 'vendor/bundle/**/*' 9 | NewCops: enable 10 | TargetRubyVersion: '2.7' 11 | 12 | Style/Documentation: 13 | Enabled: no 14 | 15 | Style/IfUnlessModifier: 16 | Enabled: no 17 | 18 | RSpec/NestedGroups: 19 | Max: 4 20 | 21 | RSpec/ExampleLength: 22 | Enabled: no 23 | 24 | RSpec/MultipleExpectations: 25 | Enabled: no 26 | 27 | RSpec/MultipleMemoizedHelpers: 28 | Enabled: no 29 | 30 | RSpec/MessageSpies: 31 | Enabled: no 32 | 33 | Metrics/BlockLength: 34 | Enabled: no 35 | 36 | Metrics/MethodLength: 37 | Enabled: no 38 | 39 | Metrics/AbcSize: 40 | Enabled: no 41 | 42 | Metrics/ClassLength: 43 | Enabled: no 44 | 45 | Metrics/ParameterLists: 46 | Enabled: no 47 | 48 | Lint/UnusedMethodArgument: 49 | Enabled: no 50 | 51 | Style/FrozenStringLiteralComment: 52 | Enabled: yes 53 | EnforcedStyle: always 54 | Include: 55 | - 'lib/**/*' 56 | 57 | Layout/LineLength: 58 | Max: 120 59 | 60 | Style/SingleLineMethods: 61 | Enabled: false 62 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2022-01-25 17:49:13 UTC using RuboCop version 1.25.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # Cop supports --auto-correct. 11 | Style/CollectionCompact: 12 | Exclude: 13 | - 'lib/jaeger/tracer.rb' 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to jaeger-client-ruby 2 | 3 | We currently do not accept any contributions from external sources. 4 | 5 | The code is still open source and if you wish to add any changes, feel free to fork it and add changes to your fork. 6 | 7 | Issues for any security-related bugs found are still welcome, but we offer no guarantees on whether or when they will be acted upon. 8 | 9 | For any exceptional cases, please contact us at open-source@glia.com. 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in jaeger-client.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Indrek Juhkam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | -include crossdock/rules.mk 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Jaeger::Client 2 | ================ 3 | [![Gem Version](https://badge.fury.io/rb/jaeger-client.svg)](https://rubygems.org/gems/jaeger-client) 4 | [![Build Status](https://github.com/salemove/jaeger-client-ruby/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/salemove/jaeger-client-ruby/actions/workflows/ci.yml?query=branch%3Amaster) 5 | 6 | **This project is not actively maintained. Please consider using [OpenTelemetry](https://opentelemetry.io/)** 7 | 8 | OpenTracing Tracer implementation for Jaeger in Ruby 9 | 10 | ## Installation 11 | 12 | Add this line to your application's Gemfile: 13 | 14 | ```ruby 15 | gem 'jaeger-client' 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```ruby 21 | require 'jaeger/client' 22 | OpenTracing.global_tracer = Jaeger::Client.build(host: 'localhost', port: 6831, service_name: 'echo') 23 | 24 | OpenTracing.start_active_span('span name') do 25 | # do something 26 | 27 | OpenTracing.start_active_span('inner span name') do 28 | # do something else 29 | end 30 | end 31 | 32 | # don't kill the program too soon, allow time for the background reporter to send the traces 33 | sleep 2 34 | ``` 35 | 36 | See [opentracing-ruby](https://github.com/opentracing/opentracing-ruby) for more examples. 37 | 38 | ### Reporters 39 | 40 | #### RemoteReporter (default) 41 | 42 | RemoteReporter buffers spans in memory and sends them out of process using Sender. 43 | 44 | There are two senders: `UdpSender` (default) and `HttpSender`. 45 | 46 | To use `HttpSender`: 47 | 48 | ```ruby 49 | OpenTracing.global_tracer = Jaeger::Client.build( 50 | service_name: 'service_name', 51 | reporter: Jaeger::Reporters::RemoteReporter.new( 52 | sender: Jaeger::HttpSender.new( 53 | url: 'http://localhost:14268/api/traces', 54 | headers: { 'key' => 'value' }, # headers key is optional 55 | encoder: Jaeger::Encoders::ThriftEncoder.new(service_name: 'service_name') 56 | ), 57 | flush_interval: 10 58 | ) 59 | ) 60 | ``` 61 | 62 | #### NullReporter 63 | 64 | NullReporter ignores all spans. 65 | 66 | ```ruby 67 | OpenTracing.global_tracer = Jaeger::Client.build( 68 | service_name: 'service_name', 69 | reporter: Jaeger::Reporters::NullReporter.new 70 | ) 71 | ``` 72 | 73 | #### LoggingReporter 74 | 75 | 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. 76 | 77 | ```ruby 78 | OpenTracing.global_tracer = Jaeger::Client.build( 79 | service_name: 'service_name', 80 | reporter: Jaeger::Reporters::LoggingReporter.new 81 | ) 82 | ``` 83 | 84 | LoggingReporter can also use a custom logger. For this provide logger using `logger` keyword argument. 85 | 86 | ### Samplers 87 | 88 | #### Const sampler 89 | 90 | `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. 91 | 92 | #### Probabilistic sampler 93 | 94 | `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)` 95 | 96 | #### RateLimiting sampler 97 | 98 | `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. 99 | 100 | Set `sampler` to `Jaeger::Samplers::RateLimiting.new(max_traces_per_second: 100)` 101 | 102 | #### GuaranteedThroughputProbabilistic sampler 103 | 104 | `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. 105 | 106 | Set `sampler` to `Jaeger::Samplers::GuaranteedThroughputProbabilistic.new(lower_bound: 10, rate: 0.001)` 107 | 108 | #### PerOperation sampler 109 | 110 | `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. 111 | 112 | Set `sampler` to 113 | ```ruby 114 | Jaeger::Samplers::PerOperation.new( 115 | strategies: { 116 | per_operation_strategies: [ 117 | { operation: 'GET /articles', probabilistic_sampling: { sampling_rate: 0.5 } }, 118 | { operation: 'POST /articles', probabilistic_sampling: { sampling_rate: 1.0 } } 119 | ], 120 | default_sampling_probability: 0.001, 121 | default_lower_bound_traces_per_second: 1.0 / (10.0 * 60.0) 122 | }, 123 | max_operations: 1000 124 | ) 125 | ``` 126 | 127 | #### RemoteControlled sampler 128 | 129 | `RemoteControlled` sampler is a sampler that is controller by jaeger agent. It starts out with `Probabilistic` sampler. It polls the jaeger-agent and changes sampling strategy accordingly. Set `sampler` to `Jaeger::Client::Samplers::RemoteControlled.new(service_name: 'service_name')`. 130 | 131 | RemoteControlled sampler options: 132 | 133 | | Param | Required | Description | 134 | |-------------------|----------|-------------| 135 | | service_name | x | name of the current service / application, same as given to Tracer | 136 | | sampler | | initial sampler to use prior to retrieving strategies from Agent | 137 | | refresh_interval | | interval in seconds before sampling strategy refreshes (0 to not refresh, defaults to 60) | 138 | | host | | host for jaeger-agent (defaults to 'localhost') | 139 | | port | | port for jaeger-agent for SamplingManager endpoint (defaults to 5778) | 140 | | logger | | logger for communication between jaeger-agent (default to $stdout logger) | 141 | 142 | ### TraceContext compatible header propagation 143 | 144 | It is possible to use [W3C Trace Context](https://www.w3.org/TR/trace-context/#overview) headers to propagate the tracing information. 145 | 146 | To set it up you need to change FORMAT_RACK injector and extractor. 147 | 148 | ```ruby 149 | OpenTracing.global_tracer = Jaeger::Client.build( 150 | service_name: 'service_name', 151 | injectors: { 152 | OpenTracing::FORMAT_RACK => [Jaeger::Injectors::TraceContextRackCodec] 153 | }, 154 | extractors: { 155 | OpenTracing::FORMAT_RACK => [Jaeger::Extractors::TraceContextRackCodec] 156 | } 157 | ) 158 | ``` 159 | 160 | ### Zipkin HTTP B3 compatible header propagation 161 | 162 | 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. 163 | 164 | To set it up you need to change FORMAT_RACK injector and extractor. 165 | 166 | ```ruby 167 | OpenTracing.global_tracer = Jaeger::Client.build( 168 | service_name: 'service_name', 169 | injectors: { 170 | OpenTracing::FORMAT_RACK => [Jaeger::Injectors::B3RackCodec] 171 | }, 172 | extractors: { 173 | OpenTracing::FORMAT_RACK => [Jaeger::Extractors::B3RackCodec] 174 | } 175 | ) 176 | ``` 177 | 178 | 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. 179 | 180 | If multiple extractors is used then the span context from the first match will be returned. 181 | 182 | ### Process Tags 183 | 184 | Jaeger Tracer allows you to define process level tags. By default the tracer provides `jaeger.version`, `ip` and `hostname`. You may want to overwrite `ip` or `hostname` if the tracer cannot auto-detect them. 185 | 186 | ```ruby 187 | OpenTracing.global_tracer = Jaeger::Client.build( 188 | service_name: 'service_name', 189 | tags: { 190 | 'hostname' => 'custom-hostname', 191 | 'custom_tag' => 'custom-tag-value' 192 | } 193 | ) 194 | ``` 195 | 196 | ## Development 197 | 198 | 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. 199 | 200 | 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). 201 | 202 | ## Contributing 203 | 204 | Bug reports and pull requests are welcome on GitHub at https://github.com/salemove/jaeger-client-ruby 205 | 206 | 207 | ## License 208 | 209 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 210 | 211 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | require 'rubocop/rake_task' 4 | 5 | RSpec::Core::RakeTask.new(:spec) 6 | 7 | RuboCop::RakeTask.new(:rubocop) 8 | 9 | task default: %i[rubocop spec] 10 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler/setup' 4 | require 'jaeger/client' 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require 'irb' 14 | IRB.start(__FILE__) 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /crossdock/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.7-alpine 2 | 3 | ENV APP_HOME /app 4 | 5 | # git is required by bundler to run jaeger gem with local path 6 | RUN apk add --no-cache git 7 | 8 | # Add only files needed for installing gem dependencies. This allows us to 9 | # change other files without needing to install gems every time when building 10 | # the docker image. 11 | ADD Gemfile Gemfile.lock jaeger-client.gemspec $APP_HOME/ 12 | ADD lib/jaeger/client/version.rb $APP_HOME/lib/jaeger/client/ 13 | ADD crossdock/Gemfile crossdock/Gemfile.lock $APP_HOME/crossdock/ 14 | 15 | RUN gem install bundler -v $(tail -n 1 $APP_HOME/Gemfile.lock | awk '{$1=$1};1') 16 | 17 | RUN apk add --no-cache --virtual .app-builddeps build-base \ 18 | && cd $APP_HOME && bundle install \ 19 | && cd $APP_HOME/crossdock && bundle install \ 20 | && apk del .app-builddeps 21 | 22 | ADD . $APP_HOME 23 | 24 | RUN chown -R nobody:nogroup $APP_HOME 25 | USER nobody 26 | 27 | WORKDIR $APP_HOME/crossdock 28 | 29 | CMD ["bundle", "exec", "./server"] 30 | 31 | EXPOSE 8080-8082 32 | -------------------------------------------------------------------------------- /crossdock/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'jaeger-client', path: '../' 4 | gem 'rack' 5 | gem 'sinatra' 6 | gem 'webrick', '~> 1.7' 7 | -------------------------------------------------------------------------------- /crossdock/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: .. 3 | specs: 4 | jaeger-client (1.2.0) 5 | opentracing (~> 0.3) 6 | thrift 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | mustermann (1.1.1) 12 | ruby2_keywords (~> 0.0.1) 13 | opentracing (0.5.0) 14 | rack (2.2.3) 15 | rack-protection (2.1.0) 16 | rack 17 | ruby2_keywords (0.0.5) 18 | sinatra (2.1.0) 19 | mustermann (~> 1.0) 20 | rack (~> 2.2) 21 | rack-protection (= 2.1.0) 22 | tilt (~> 2.0) 23 | thrift (0.15.0) 24 | tilt (2.0.10) 25 | webrick (1.7.0) 26 | 27 | PLATFORMS 28 | ruby 29 | 30 | DEPENDENCIES 31 | jaeger-client! 32 | rack 33 | sinatra 34 | webrick (~> 1.7) 35 | 36 | BUNDLED WITH 37 | 2.3.5 38 | -------------------------------------------------------------------------------- /crossdock/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | crossdock: 5 | image: crossdock/crossdock 6 | links: 7 | - test_driver 8 | - go 9 | - python 10 | - java 11 | - ruby 12 | environment: 13 | - WAIT_FOR=test_driver,go,python,java,ruby 14 | - WAIT_FOR_TIMEOUT=60s 15 | 16 | - CALL_TIMEOUT=60s 17 | 18 | - AXIS_CLIENT=go 19 | 20 | - AXIS_S1NAME=go,python,java,ruby 21 | - AXIS_SAMPLED=true,false 22 | - AXIS_S2NAME=go,python,java,ruby 23 | - AXIS_S2TRANSPORT=http 24 | - AXIS_S3NAME=go,python,java,ruby 25 | - AXIS_S3TRANSPORT=http 26 | 27 | - BEHAVIOR_TRACE=client,s1name,sampled,s2name,s2transport,s3name,s3transport 28 | 29 | - AXIS_TESTDRIVER=test_driver 30 | - AXIS_SERVICES=ruby 31 | 32 | - BEHAVIOR_ENDTOEND=testdriver,services 33 | 34 | - REPORT=compact 35 | go: 36 | image: jaegertracing/xdock-go 37 | ports: 38 | - "8080-8082" 39 | 40 | java: 41 | image: jaegertracing/xdock-java 42 | depends_on: 43 | - jaeger-agent 44 | ports: 45 | - "8080-8082" 46 | 47 | python: 48 | image: jaegertracing/xdock-py 49 | depends_on: 50 | - jaeger-agent 51 | ports: 52 | - "8080-8082" 53 | 54 | ruby: 55 | build: 56 | context: ../. 57 | dockerfile: crossdock/Dockerfile 58 | ports: 59 | - "8080-8082" 60 | 61 | test_driver: 62 | image: jaegertracing/test-driver 63 | depends_on: 64 | - jaeger-query 65 | - jaeger-collector 66 | - jaeger-agent 67 | ports: 68 | - "8080" 69 | -------------------------------------------------------------------------------- /crossdock/jaeger-docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | jaeger-collector: 5 | image: jaegertracing/jaeger-collector 6 | command: ["--es.num-shards=1", "--es.num-replicas=0", "--es.server-urls=http://elasticsearch:9200", "--collector.zipkin.host-port=:9411"] 7 | ports: 8 | - "14269" 9 | - "14268:14268" 10 | - "14250" 11 | - "9411:9411" 12 | environment: 13 | - SPAN_STORAGE_TYPE=elasticsearch 14 | - LOG_LEVEL=debug 15 | restart: on-failure 16 | depends_on: 17 | - elasticsearch 18 | 19 | jaeger-query: 20 | image: jaegertracing/jaeger-query 21 | command: ["--es.num-shards=1", "--es.num-replicas=0", "--es.server-urls=http://elasticsearch:9200"] 22 | ports: 23 | - "16686:16686" 24 | - "16687" 25 | environment: 26 | - SPAN_STORAGE_TYPE=elasticsearch 27 | - LOG_LEVEL=debug 28 | restart: on-failure 29 | depends_on: 30 | - elasticsearch 31 | 32 | jaeger-agent: 33 | image: jaegertracing/jaeger-agent 34 | command: ["--reporter.grpc.host-port=jaeger-collector:14250", "--reporter.grpc.retry.max=1000"] 35 | ports: 36 | - "5775:5775/udp" 37 | - "6831:6831/udp" 38 | - "6832:6832/udp" 39 | - "5778:5778" 40 | environment: 41 | - LOG_LEVEL=debug 42 | restart: on-failure 43 | depends_on: 44 | - jaeger-collector 45 | 46 | elasticsearch: 47 | image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.8.3 48 | environment: 49 | - discovery.type=single-node 50 | ports: 51 | - "9200:9200/tcp" 52 | 53 | 54 | -------------------------------------------------------------------------------- /crossdock/rules.mk: -------------------------------------------------------------------------------- 1 | XDOCK_YAML=crossdock/docker-compose.yml 2 | TRACETEST_THRIFT=idl/thrift/crossdock/tracetest.thrift 3 | JAEGER_COMPOSE_URL=https://raw.githubusercontent.com/jaegertracing/jaeger/master/crossdock/jaeger-docker-compose.yml 4 | XDOCK_JAEGER_YAML=crossdock/jaeger-docker-compose.yml 5 | 6 | .PHONY: clean-compile 7 | clean-compile: 8 | find . -name '*.pyc' -exec rm {} \; 9 | 10 | .PHONY: docker 11 | docker: clean-compile crossdock-download-jaeger 12 | docker build -f crossdock/Dockerfile -t jaeger-client-ruby . 13 | 14 | .PHONY: crossdock 15 | crossdock: ${TRACETEST_THRIFT} crossdock-download-jaeger 16 | docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) kill ruby 17 | docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) rm -f ruby 18 | docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) build ruby 19 | docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) run crossdock 20 | 21 | .PHONY: crossdock-fresh 22 | crossdock-fresh: ${TRACETEST_THRIFT} crossdock-download-jaeger 23 | docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) kill 24 | docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) rm --force 25 | docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) pull 26 | docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) build 27 | docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) run crossdock 28 | 29 | .PHONY: crossdock-logs crossdock-download-jaeger 30 | crossdock-logs: 31 | docker-compose -f $(XDOCK_YAML) -f $(XDOCK_JAEGER_YAML) logs 32 | 33 | .PHONY: crossdock-download-jaeger 34 | crossdock-download-jaeger: 35 | curl -o $(XDOCK_JAEGER_YAML) $(JAEGER_COMPOSE_URL) 36 | -------------------------------------------------------------------------------- /crossdock/server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $stdout.sync = true 4 | 5 | require 'sinatra/base' 6 | require 'webrick' 7 | require 'jaeger/client' 8 | require 'net/http' 9 | require 'uri' 10 | 11 | class HealthServer < Sinatra::Application 12 | get '/' do 13 | status 200 14 | end 15 | end 16 | 17 | class HttpServer < Sinatra::Application 18 | post '/start_trace' do 19 | puts "Got request to start trace: #{trace_request}" 20 | 21 | parent_context = tracer.extract(OpenTracing::FORMAT_RACK, request.env) 22 | server_span = tracer.start_span('/start_trace', child_of: parent_context) 23 | 24 | server_span.set_baggage_item('crossdock-baggage-key', trace_request['baggage']) 25 | if trace_request.key?('sampled') 26 | server_span.set_tag('sampling.priority', trace_request['sampled'] ? 1 : 0) 27 | end 28 | 29 | response = { 30 | span: observe_span(server_span), 31 | notImplementedError: '' 32 | } 33 | 34 | if trace_request['downstream'] 35 | downstream = trace_request['downstream'] 36 | transport = downstream['transport'] 37 | 38 | response[:downstream] = 39 | case transport 40 | when 'HTTP' 41 | call_downstream_http(downstream, server_span) 42 | when 'DUMMY' 43 | { notImplementedError: 'Dummy has not been implemented' } 44 | else 45 | { notImplementedError: "Unrecognized transport received: #{transport}" } 46 | end 47 | end 48 | 49 | puts "Response: #{response}" 50 | 51 | server_span.finish 52 | body JSON.dump(response) 53 | end 54 | 55 | post '/join_trace' do 56 | puts 'Got request to join trace' \ 57 | "\n Params: #{trace_request}" \ 58 | "\n Headers: #{request_headers(request)}" 59 | 60 | parent_context = tracer.extract(OpenTracing::FORMAT_RACK, request.env) 61 | server_span = tracer.start_span('/join_trace', child_of: parent_context) 62 | 63 | response = { 64 | span: observe_span(server_span), 65 | notImplementedError: '' 66 | } 67 | 68 | if trace_request['downstream'] 69 | downstream = trace_request['downstream'] 70 | transport = downstream['transport'] 71 | 72 | response[:downstream] = 73 | case transport 74 | when 'HTTP' 75 | call_downstream_http(downstream, server_span) 76 | when 'DUMMY' 77 | { notImplementedError: 'Dummy has not been implemented' } 78 | else 79 | { notImplementedError: "Unrecognized transport received: #{transport}" } 80 | end 81 | end 82 | 83 | puts "Response: #{response}" 84 | 85 | server_span.finish 86 | body JSON.dump(response) 87 | end 88 | 89 | post '/create_traces' do 90 | puts "Got request to create traces: #{trace_request}" 91 | 92 | trace_request['count'].times do 93 | span = tracer.start_span(trace_request['operation'], tags: trace_request['tags']) 94 | span.finish 95 | end 96 | 97 | status 200 98 | end 99 | 100 | private 101 | 102 | def tracer 103 | @tracer ||= Jaeger::Client.build( 104 | service_name: 'crossdock-ruby', 105 | host: 'jaeger-agent', 106 | port: 6831, 107 | flush_interval: 1, 108 | sampler: Jaeger::Samplers::Const.new(true) 109 | ) 110 | end 111 | 112 | def trace_request 113 | @trace_request ||= begin 114 | request.body.rewind 115 | JSON.parse(request.body.read) 116 | end 117 | end 118 | 119 | def observe_span(span) 120 | if span 121 | { 122 | traceId: span.context.to_trace_id, 123 | sampled: span.context.sampled?, 124 | baggage: span.get_baggage_item('crossdock-baggage-key') 125 | } 126 | else 127 | { 128 | traceId: 'no span found', 129 | sampled: false, 130 | baggage: 'no span found' 131 | } 132 | end 133 | end 134 | 135 | def call_downstream_http(downstream, server_span) 136 | downstream_url = "http://#{downstream['host']}:#{downstream['port']}/join_trace" 137 | 138 | client_span = tracer.start_span('client-span', child_of: server_span) 139 | 140 | headers = { 'Content-Type' => 'application/json' } 141 | tracer.inject(client_span.context, OpenTracing::FORMAT_RACK, headers) 142 | 143 | response = Net::HTTP.post( 144 | URI(downstream_url), 145 | JSON.dump( 146 | serverRole: downstream['serverRole'], 147 | downstream: downstream['downstream'] 148 | ), 149 | headers 150 | ) 151 | 152 | client_span.finish 153 | 154 | if response.is_a?(Net::HTTPSuccess) 155 | JSON.parse(response.body) 156 | else 157 | { error: response.body } 158 | end 159 | end 160 | 161 | def request_headers(request) 162 | request.env.select do |key, _value| 163 | key.start_with?('HTTP_') 164 | end 165 | end 166 | end 167 | 168 | threads = [] 169 | threads << Thread.new do 170 | Rack::Handler::WEBrick.run(HealthServer, Port: 8080, Host: '0.0.0.0') 171 | end 172 | threads << Thread.new do 173 | Rack::Handler::WEBrick.run(HttpServer, Port: 8081, Host: '0.0.0.0') 174 | end 175 | threads.each(&:join) 176 | -------------------------------------------------------------------------------- /jaeger-client.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | 4 | require 'jaeger/client/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'jaeger-client' 8 | spec.version = Jaeger::Client::VERSION 9 | spec.authors = ['SaleMove TechMovers'] 10 | spec.email = ['techmovers@salemove.com'] 11 | spec.metadata['rubygems_mfa_required'] = 'true' 12 | 13 | spec.summary = 'OpenTracing Tracer implementation for Jaeger in Ruby' 14 | spec.description = '' 15 | spec.homepage = 'https://github.com/salemove/jaeger-client-ruby' 16 | spec.license = 'MIT' 17 | 18 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 19 | f.match(%r{^(test|spec|features)/}) 20 | end 21 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 22 | spec.require_paths = ['lib'] 23 | 24 | spec.required_ruby_version = ['>= 2.7', '< 3.2'] 25 | 26 | spec.add_development_dependency 'bundler' 27 | spec.add_development_dependency 'rake', '~> 13.0' 28 | spec.add_development_dependency 'rspec', '~> 3.10' 29 | spec.add_development_dependency 'rubocop', '~> 1.25' 30 | spec.add_development_dependency 'rubocop-rake', '~> 0.6' 31 | spec.add_development_dependency 'rubocop-rspec', '~> 2.8' 32 | spec.add_development_dependency 'timecop', '~> 0.9' 33 | spec.add_development_dependency 'webmock', '~> 3.14' 34 | 35 | spec.add_dependency 'opentracing', '~> 0.3' 36 | spec.add_dependency 'thrift' 37 | end 38 | -------------------------------------------------------------------------------- /lib/jaeger/client.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.push("#{File.dirname(__FILE__)}/../../thrift/gen-rb") 4 | 5 | require 'opentracing' 6 | require 'jaeger/thrift/agent' 7 | require 'logger' 8 | require 'time' 9 | require 'net/http' 10 | require 'cgi' 11 | require 'json' 12 | 13 | require_relative 'tracer' 14 | require_relative 'span' 15 | require_relative 'span_context' 16 | require_relative 'scope' 17 | require_relative 'scope_manager' 18 | require_relative 'trace_id' 19 | require_relative 'udp_sender' 20 | require_relative 'http_sender' 21 | require_relative 'reporters' 22 | require_relative 'client/version' 23 | require_relative 'samplers' 24 | require_relative 'encoders/thrift_encoder' 25 | require_relative 'injectors' 26 | require_relative 'extractors' 27 | require_relative 'rate_limiter' 28 | require_relative 'thrift_tag_builder' 29 | require_relative 'recurring_executor' 30 | 31 | module Jaeger 32 | module Client 33 | # We initially had everything under Jaeger::Client namespace. This however 34 | # was not very useful and was removed. These assignments are here for 35 | # backwards compatibility. Fine to remove in the next major version. 36 | UdpSender = Jaeger::UdpSender 37 | HttpSender = Jaeger::HttpSender 38 | Encoders = Jaeger::Encoders 39 | Samplers = Jaeger::Samplers 40 | Reporters = Jaeger::Reporters 41 | Injectors = Jaeger::Injectors 42 | Extractors = Jaeger::Extractors 43 | 44 | DEFAULT_FLUSH_INTERVAL = 10 45 | 46 | def self.build(service_name:, 47 | host: '127.0.0.1', 48 | port: 6831, 49 | flush_interval: DEFAULT_FLUSH_INTERVAL, 50 | sampler: Samplers::Const.new(true), 51 | logger: Logger.new($stdout), 52 | sender: nil, 53 | reporter: nil, 54 | injectors: {}, 55 | extractors: {}, 56 | tags: {}) 57 | encoder = Encoders::ThriftEncoder.new(service_name: service_name, tags: tags, logger: logger) 58 | 59 | if sender 60 | warn '[DEPRECATION] Passing `sender` directly to Jaeger::Client.build is deprecated.' \ 61 | 'Please use `reporter` instead.' 62 | end 63 | 64 | reporter ||= Reporters::RemoteReporter.new( 65 | sender: sender || UdpSender.new(host: host, port: port, encoder: encoder, logger: logger), 66 | flush_interval: flush_interval 67 | ) 68 | 69 | Tracer.new( 70 | reporter: reporter, 71 | sampler: sampler, 72 | injectors: Injectors.prepare(injectors), 73 | extractors: Extractors.prepare(extractors) 74 | ) 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/jaeger/client/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Client 5 | VERSION = '1.3.0' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/jaeger/encoders/thrift_encoder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Encoders 5 | class ThriftEncoder 6 | def initialize(service_name:, logger:, tags: {}) 7 | @service_name = service_name 8 | @logger = logger 9 | @tags = prepare_tags(tags) 10 | @process = Jaeger::Thrift::Process.new('serviceName' => @service_name, 'tags' => @tags) 11 | end 12 | 13 | def encode(spans) 14 | encode_batch(spans.map(&method(:encode_span))) 15 | end 16 | 17 | def encode_limited_size(spans, protocol_class, max_message_length) 18 | batches = [] 19 | current_batch = [] 20 | current_batch_size = 0 21 | 22 | max_spans_length = calculate_max_spans_length(protocol_class, max_message_length) 23 | 24 | spans.each do |span| 25 | encoded_span = encode_span(span) 26 | span_size = message_size(encoded_span, protocol_class) 27 | 28 | if span_size > max_spans_length 29 | @logger.warn("Skip span #{span.operation_name} with size #{span_size}") 30 | next 31 | end 32 | 33 | if (current_batch_size + span_size) > max_spans_length 34 | batches << encode_batch(current_batch) 35 | current_batch = [] 36 | current_batch_size = 0 37 | end 38 | 39 | current_batch << encoded_span 40 | current_batch_size += span_size 41 | end 42 | batches << encode_batch(current_batch) unless current_batch.empty? 43 | batches 44 | end 45 | 46 | private 47 | 48 | def encode_batch(encoded_spans) 49 | Jaeger::Thrift::Batch.new('process' => @process, 'spans' => encoded_spans) 50 | end 51 | 52 | def encode_span(span) 53 | context = span.context 54 | start_ts, duration = build_timestamps(span) 55 | 56 | Jaeger::Thrift::Span.new( 57 | 'traceIdLow' => TraceId.uint64_id_to_int64(trace_id_to_low(context.trace_id)), 58 | 'traceIdHigh' => TraceId.uint64_id_to_int64(trace_id_to_high(context.trace_id)), 59 | 'spanId' => TraceId.uint64_id_to_int64(context.span_id), 60 | 'parentSpanId' => TraceId.uint64_id_to_int64(context.parent_id), 61 | 'operationName' => span.operation_name, 62 | 'references' => build_references(span.references || []), 63 | 'flags' => context.flags, 64 | 'startTime' => start_ts, 65 | 'duration' => duration, 66 | 'tags' => span.tags, 67 | 'logs' => span.logs 68 | ) 69 | end 70 | 71 | def build_references(references) 72 | references.map do |ref| 73 | Jaeger::Thrift::SpanRef.new( 74 | 'refType' => span_ref_type(ref.type), 75 | 'traceIdLow' => TraceId.uint64_id_to_int64(trace_id_to_low(ref.context.trace_id)), 76 | 'traceIdHigh' => TraceId.uint64_id_to_int64(trace_id_to_high(ref.context.trace_id)), 77 | 'spanId' => TraceId.uint64_id_to_int64(ref.context.span_id) 78 | ) 79 | end 80 | end 81 | 82 | def build_timestamps(span) 83 | start_ts = (span.start_time.to_f * 1_000_000).to_i 84 | end_ts = (span.end_time.to_f * 1_000_000).to_i 85 | duration = end_ts - start_ts 86 | [start_ts, duration] 87 | end 88 | 89 | def span_ref_type(type) 90 | case type 91 | when OpenTracing::Reference::CHILD_OF 92 | Jaeger::Thrift::SpanRefType::CHILD_OF 93 | when OpenTracing::Reference::FOLLOWS_FROM 94 | Jaeger::Thrift::SpanRefType::FOLLOWS_FROM 95 | else 96 | warn "Jaeger::Client with format #{type} is not supported yet" 97 | nil 98 | end 99 | end 100 | 101 | def prepare_tags(tags) 102 | with_default_tags = tags.dup 103 | with_default_tags['jaeger.version'] = "Ruby-#{Jaeger::Client::VERSION}" 104 | with_default_tags['hostname'] ||= Socket.gethostname 105 | 106 | unless with_default_tags['ip'] 107 | ipv4 = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? } 108 | with_default_tags['ip'] = ipv4.ip_address unless ipv4.nil? 109 | end 110 | 111 | with_default_tags.map do |key, value| 112 | ThriftTagBuilder.build(key, value) 113 | end 114 | end 115 | 116 | # Returns the right most 64 bits of trace id 117 | def trace_id_to_low(trace_id) 118 | trace_id & TraceId::MAX_64BIT_UNSIGNED_INT 119 | end 120 | 121 | # Returns the left most 64 bits of trace id 122 | def trace_id_to_high(trace_id) 123 | (trace_id >> 64) & TraceId::MAX_64BIT_UNSIGNED_INT 124 | end 125 | 126 | class DummyTransport 127 | attr_accessor :size 128 | 129 | def initialize 130 | @size = 0 131 | end 132 | 133 | def write(buf) 134 | @size += buf.size 135 | end 136 | 137 | def flush 138 | @size = 0 139 | end 140 | 141 | def close; end 142 | end 143 | 144 | def message_size(message, protocol_class) 145 | transport = DummyTransport.new 146 | protocol = protocol_class.new(transport) 147 | message.write(protocol) 148 | transport.size 149 | end 150 | 151 | # Compact protocol have dynamic size of list header 152 | # https://erikvanoosten.github.io/thrift-missing-specification/#_list_and_set_2 153 | BATCH_SPANS_SIZE_WINDOW = 4 154 | 155 | def empty_batch_size_cache 156 | @empty_batch_size_cache ||= {} 157 | end 158 | 159 | def caclulate_empty_batch_size(protocol_class) 160 | empty_batch_size_cache[protocol_class] ||= 161 | message_size(encode_batch([]), protocol_class) + BATCH_SPANS_SIZE_WINDOW 162 | end 163 | 164 | def calculate_max_spans_length(protocol_class, max_message_length) 165 | empty_batch_size = caclulate_empty_batch_size(protocol_class) 166 | max_spans_length = max_message_length - empty_batch_size 167 | return max_spans_length if max_spans_length.positive? 168 | 169 | raise "Batch header have size #{empty_batch_size}, but limit #{max_message_length}" 170 | end 171 | end 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /lib/jaeger/extractors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Extractors 5 | class SerializedJaegerTrace 6 | def self.parse(trace) 7 | return nil if !trace || trace == '' 8 | 9 | trace_arguments = trace.split(':') 10 | return nil if trace_arguments.size != 4 11 | 12 | trace_id = TraceId.base16_hex_id_to_uint128(trace_arguments[0]) 13 | span_id, parent_id, flags = trace_arguments[1..3].map(&TraceId.method(:base16_hex_id_to_uint64)) 14 | 15 | return nil if trace_id.zero? || span_id.zero? 16 | 17 | SpanContext.new( 18 | trace_id: trace_id, 19 | parent_id: parent_id, 20 | span_id: span_id, 21 | flags: flags 22 | ) 23 | end 24 | end 25 | 26 | class JaegerTextMapCodec 27 | def self.extract(carrier) 28 | context = SerializedJaegerTrace.parse(carrier['uber-trace-id']) 29 | return nil unless context 30 | 31 | carrier.each do |key, value| 32 | baggage_match = key.match(/\Auberctx-([\w-]+)\Z/) 33 | if baggage_match 34 | context.set_baggage_item(baggage_match[1], value) 35 | end 36 | end 37 | 38 | context 39 | end 40 | end 41 | 42 | class JaegerRackCodec 43 | def self.extract(carrier) 44 | serialized_trace = carrier['HTTP_UBER_TRACE_ID'] 45 | serialized_trace = CGI.unescape(serialized_trace) if serialized_trace 46 | context = SerializedJaegerTrace.parse(serialized_trace) 47 | return nil unless context 48 | 49 | carrier.each do |key, value| 50 | baggage_match = key.match(/\AHTTP_UBERCTX_(\w+)\Z/) 51 | if baggage_match 52 | key = baggage_match[1].downcase.tr('_', '-') 53 | context.set_baggage_item(key, CGI.unescape(value)) 54 | end 55 | end 56 | 57 | context 58 | end 59 | end 60 | 61 | class JaegerBinaryCodec 62 | def self.extract(_carrier) 63 | warn 'Jaeger::Client with binary format is not supported yet' 64 | end 65 | end 66 | 67 | class B3RackCodec 68 | class Keys 69 | TRACE_ID = 'HTTP_X_B3_TRACEID' 70 | SPAN_ID = 'HTTP_X_B3_SPANID' 71 | PARENT_SPAN_ID = 'HTTP_X_B3_PARENTSPANID' 72 | FLAGS = 'HTTP_X_B3_FLAGS' 73 | SAMPLED = 'HTTP_X_B3_SAMPLED' 74 | end.freeze 75 | 76 | def self.extract(carrier) 77 | B3CodecCommon.extract(carrier, Keys) 78 | end 79 | end 80 | 81 | class B3TextMapCodec 82 | class Keys 83 | TRACE_ID = 'x-b3-traceid' 84 | SPAN_ID = 'x-b3-spanid' 85 | PARENT_SPAN_ID = 'x-b3-parentspanid' 86 | FLAGS = 'x-b3-flags' 87 | SAMPLED = 'x-b3-sampled' 88 | end.freeze 89 | 90 | def self.extract(carrier) 91 | B3CodecCommon.extract(carrier, Keys) 92 | end 93 | end 94 | 95 | class B3CodecCommon 96 | def self.extract(carrier, keys) 97 | return nil if carrier[keys::TRACE_ID].nil? || carrier[keys::SPAN_ID].nil? 98 | 99 | trace_id = if carrier[keys::TRACE_ID].length <= 16 100 | TraceId.base16_hex_id_to_uint64(carrier[keys::TRACE_ID]) 101 | else 102 | TraceId.base16_hex_id_to_uint128(carrier[keys::TRACE_ID]) 103 | end 104 | 105 | span_id = TraceId.base16_hex_id_to_uint64(carrier[keys::SPAN_ID]) 106 | parent_id = TraceId.base16_hex_id_to_uint64(carrier[keys::PARENT_SPAN_ID]) 107 | flags = parse_flags(carrier[keys::FLAGS], carrier[keys::SAMPLED]) 108 | 109 | return nil if span_id.zero? || trace_id.zero? 110 | 111 | SpanContext.new( 112 | trace_id: trace_id, 113 | parent_id: parent_id, 114 | span_id: span_id, 115 | flags: flags 116 | ) 117 | end 118 | 119 | # if the flags header is '1' then the sampled header should not be present 120 | def self.parse_flags(flags_header, sampled_header) 121 | if flags_header == '1' 122 | Jaeger::SpanContext::Flags::DEBUG 123 | else 124 | TraceId.base16_hex_id_to_uint64(sampled_header) 125 | end 126 | end 127 | private_class_method :parse_flags 128 | end 129 | 130 | class TraceContextRackCodec 131 | # Internal regex used to identify the TraceContext version 132 | VERSION_PATTERN = /^([0-9a-fA-F]{2})-(.+)$/.freeze 133 | 134 | # Internal regex used to parse fields in version 0 135 | HEADER_V0_PATTERN = /^([0-9a-fA-F]{32})-([0-9a-fA-F]{16})(-([0-9a-fA-F]{2}))?$/.freeze 136 | 137 | def self.extract(carrier) 138 | header_value = carrier['HTTP_TRACEPARENT'] 139 | 140 | version_match = VERSION_PATTERN.match(header_value) 141 | return nil unless version_match 142 | 143 | # We currently only support version 0 144 | return nil if version_match[1].to_i(16) != 0 145 | 146 | match = HEADER_V0_PATTERN.match(version_match[2]) 147 | return nil unless match 148 | 149 | trace_id = TraceId.base16_hex_id_to_uint128(match[1]) 150 | span_id = TraceId.base16_hex_id_to_uint64(match[2]) 151 | flags = TraceId.base16_hex_id_to_uint64(match[4]) 152 | return nil if trace_id.zero? || span_id.zero? 153 | 154 | SpanContext.new(trace_id: trace_id, span_id: span_id, flags: flags) 155 | end 156 | end 157 | 158 | DEFAULT_EXTRACTORS = { 159 | OpenTracing::FORMAT_TEXT_MAP => JaegerTextMapCodec, 160 | OpenTracing::FORMAT_BINARY => JaegerBinaryCodec, 161 | OpenTracing::FORMAT_RACK => JaegerRackCodec 162 | }.freeze 163 | 164 | def self.prepare(extractors) 165 | DEFAULT_EXTRACTORS.reduce(extractors) do |acc, (format, default)| 166 | provided_extractors = Array(extractors[format]) 167 | provided_extractors += [default] if provided_extractors.empty? 168 | 169 | acc.merge(format => provided_extractors) 170 | end 171 | end 172 | end 173 | end 174 | -------------------------------------------------------------------------------- /lib/jaeger/http_sender.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'logger' 4 | 5 | module Jaeger 6 | class HttpSender 7 | def initialize(url:, encoder:, headers: {}, logger: Logger.new($stdout)) 8 | @encoder = encoder 9 | @logger = logger 10 | 11 | @uri = URI(url) 12 | @uri.query = 'format=jaeger.thrift' 13 | 14 | @transport = ::Thrift::HTTPClientTransport.new(@uri.to_s) 15 | @transport.add_headers(headers) 16 | 17 | @serializer = ::Thrift::Serializer.new 18 | end 19 | 20 | def send_spans(spans) 21 | batch = @encoder.encode(spans) 22 | @transport.write(@serializer.serialize(batch)) 23 | @transport.flush 24 | rescue StandardError => e 25 | @logger.error("Failure while sending a batch of spans: #{e}") 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/jaeger/injectors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Injectors 5 | def self.context_as_jaeger_string(span_context) 6 | [ 7 | span_context.trace_id.to_s(16), 8 | span_context.span_id.to_s(16), 9 | span_context.parent_id.to_s(16), 10 | span_context.flags.to_s(16) 11 | ].join(':') 12 | end 13 | 14 | class JaegerTextMapCodec 15 | def self.inject(span_context, carrier) 16 | carrier['uber-trace-id'] = Injectors.context_as_jaeger_string(span_context) 17 | span_context.baggage.each do |key, value| 18 | carrier["uberctx-#{key}"] = value 19 | end 20 | end 21 | end 22 | 23 | class JaegerRackCodec 24 | def self.inject(span_context, carrier) 25 | carrier['uber-trace-id'] = 26 | CGI.escape(Injectors.context_as_jaeger_string(span_context)) 27 | span_context.baggage.each do |key, value| 28 | carrier["uberctx-#{key}"] = CGI.escape(value) 29 | end 30 | end 31 | end 32 | 33 | class JaegerBinaryCodec 34 | def self.inject(_span_context, _carrier) 35 | warn 'Jaeger::Client with binary format is not supported yet' 36 | end 37 | end 38 | 39 | class B3RackCodec 40 | def self.inject(span_context, carrier) 41 | carrier['x-b3-traceid'] = TraceId.to_hex(span_context.trace_id) 42 | carrier['x-b3-spanid'] = TraceId.to_hex(span_context.span_id) 43 | carrier['x-b3-parentspanid'] = TraceId.to_hex(span_context.parent_id) 44 | 45 | # flags (for debug) and sampled headers are mutually exclusive 46 | if span_context.flags == Jaeger::SpanContext::Flags::DEBUG 47 | carrier['x-b3-flags'] = '1' 48 | else 49 | carrier['x-b3-sampled'] = span_context.flags.to_s(16) 50 | end 51 | end 52 | end 53 | 54 | class TraceContextRackCodec 55 | def self.inject(span_context, carrier) 56 | flags = span_context.sampled? || span_context.debug? ? 1 : 0 57 | 58 | carrier['traceparent'] = format( 59 | '%s-%s-%s-%s', 60 | version: '00', 61 | trace_id: span_context.trace_id.to_s(16).rjust(32, '0'), 62 | span_id: span_context.span_id.to_s(16).rjust(16, '0'), 63 | flags: flags.to_s(16).rjust(2, '0') 64 | ) 65 | end 66 | end 67 | 68 | DEFAULT_INJECTORS = { 69 | OpenTracing::FORMAT_TEXT_MAP => JaegerTextMapCodec, 70 | OpenTracing::FORMAT_BINARY => JaegerBinaryCodec, 71 | OpenTracing::FORMAT_RACK => JaegerRackCodec 72 | }.freeze 73 | 74 | def self.prepare(injectors) 75 | DEFAULT_INJECTORS.reduce(injectors) do |acc, (format, default)| 76 | provided_injectors = Array(injectors[format]) 77 | provided_injectors += [default] if provided_injectors.empty? 78 | 79 | acc.merge(format => provided_injectors) 80 | end 81 | end 82 | end 83 | end 84 | -------------------------------------------------------------------------------- /lib/jaeger/rate_limiter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | # RateLimiter is based on leaky bucket algorithm, formulated in terms of a 5 | # credits balance that is replenished every time check_credit() method is 6 | # called (tick) by the amount proportional to the time elapsed since the 7 | # last tick, up to the max_balance. A call to check_credit() takes a cost 8 | # of an item we want to pay with the balance. If the balance exceeds the 9 | # cost of the item, the item is "purchased" and the balance reduced, 10 | # indicated by returned value of true. Otherwise the balance is unchanged 11 | # and return false. 12 | # 13 | # This can be used to limit a rate of messages emitted by a service by 14 | # instantiating the Rate Limiter with the max number of messages a service 15 | # is allowed to emit per second, and calling check_credit(1.0) for each 16 | # message to determine if the message is within the rate limit. 17 | # 18 | # It can also be used to limit the rate of traffic in bytes, by setting 19 | # credits_per_second to desired throughput as bytes/second, and calling 20 | # check_credit() with the actual message size. 21 | class RateLimiter 22 | def initialize(credits_per_second:, max_balance:) 23 | @credits_per_second = credits_per_second 24 | @max_balance = max_balance 25 | @balance = max_balance 26 | @last_tick = Time.now 27 | end 28 | 29 | def check_credit(item_cost) 30 | update_balance 31 | 32 | return false if @balance < item_cost 33 | 34 | @balance -= item_cost 35 | true 36 | end 37 | 38 | def update(credits_per_second:, max_balance:) 39 | update_balance 40 | 41 | @credits_per_second = credits_per_second 42 | 43 | # The new balance should be proportional to the old balance 44 | @balance = max_balance * @balance / @max_balance 45 | @max_balance = max_balance 46 | end 47 | 48 | private 49 | 50 | def update_balance 51 | current_time = Time.now 52 | elapsed_time = current_time - @last_tick 53 | @last_tick = current_time 54 | 55 | @balance += elapsed_time * @credits_per_second 56 | return if @balance <= @max_balance 57 | 58 | @balance = @max_balance 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/jaeger/recurring_executor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | # Executes a given block periodically. The block will be executed only once 5 | # when interval is set to 0. 6 | class RecurringExecutor 7 | def initialize(interval:) 8 | @interval = interval 9 | end 10 | 11 | def start(&block) 12 | raise 'Already running' if @thread 13 | 14 | @thread = Thread.new do 15 | if @interval <= 0 16 | yield 17 | else 18 | loop do 19 | yield 20 | sleep @interval 21 | end 22 | end 23 | end 24 | end 25 | 26 | def running? 27 | @thread&.alive? 28 | end 29 | 30 | def stop 31 | @thread.kill 32 | @thread = nil 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/jaeger/reporters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'reporters/composite_reporter' 4 | require_relative 'reporters/in_memory_reporter' 5 | require_relative 'reporters/logging_reporter' 6 | require_relative 'reporters/null_reporter' 7 | require_relative 'reporters/remote_reporter' 8 | -------------------------------------------------------------------------------- /lib/jaeger/reporters/composite_reporter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Reporters 5 | class CompositeReporter 6 | def initialize(reporters:) 7 | @reporters = reporters 8 | end 9 | 10 | def report(span) 11 | @reporters.each do |reporter| 12 | reporter.report(span) 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/jaeger/reporters/in_memory_reporter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Reporters 5 | class InMemoryReporter 6 | def initialize 7 | @spans = [] 8 | @mutex = Mutex.new 9 | end 10 | 11 | def report(span) 12 | @mutex.synchronize do 13 | @spans << span 14 | end 15 | end 16 | 17 | def spans 18 | @mutex.synchronize do 19 | @spans 20 | end 21 | end 22 | 23 | def clear 24 | @mutex.synchronize do 25 | @spans.clear 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/jaeger/reporters/logging_reporter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Reporters 5 | class LoggingReporter 6 | def initialize(logger: Logger.new($stdout)) 7 | @logger = logger 8 | end 9 | 10 | def report(span) 11 | span_info = { 12 | operation_name: span.operation_name, 13 | start_time: span.start_time.iso8601, 14 | end_time: span.end_time.iso8601, 15 | trace_id: span.context.to_trace_id, 16 | span_id: span.context.to_span_id 17 | } 18 | @logger.info "Span reported: #{span_info}" 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/jaeger/reporters/null_reporter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Reporters 5 | class NullReporter 6 | def report(_span) 7 | # no-op 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/jaeger/reporters/remote_reporter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative './remote_reporter/buffer' 4 | 5 | module Jaeger 6 | module Reporters 7 | class RemoteReporter 8 | def initialize(sender:, flush_interval:) 9 | @sender = sender 10 | @flush_interval = flush_interval 11 | @buffer = Buffer.new 12 | end 13 | 14 | def flush 15 | spans = @buffer.retrieve 16 | @sender.send_spans(spans) if spans.any? 17 | spans 18 | end 19 | 20 | def report(span) 21 | return if !span.context.sampled? && !span.context.debug? 22 | 23 | init_reporter_thread 24 | @buffer << span 25 | end 26 | 27 | private 28 | 29 | def init_reporter_thread 30 | return if @initializer_pid == Process.pid 31 | 32 | @initializer_pid = Process.pid 33 | Thread.new do 34 | loop do 35 | flush 36 | sleep(@flush_interval) 37 | end 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/jaeger/reporters/remote_reporter/buffer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Reporters 5 | class RemoteReporter 6 | class Buffer 7 | def initialize 8 | @buffer = [] 9 | @mutex = Mutex.new 10 | end 11 | 12 | def <<(element) 13 | @mutex.synchronize do 14 | @buffer << element 15 | true 16 | end 17 | end 18 | 19 | def retrieve 20 | @mutex.synchronize do 21 | elements = @buffer.dup 22 | @buffer.clear 23 | elements 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/jaeger/samplers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'samplers/const' 4 | require_relative 'samplers/guaranteed_throughput_probabilistic' 5 | require_relative 'samplers/per_operation' 6 | require_relative 'samplers/probabilistic' 7 | require_relative 'samplers/rate_limiting' 8 | require_relative 'samplers/remote_controlled' 9 | -------------------------------------------------------------------------------- /lib/jaeger/samplers/const.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Samplers 5 | # Const sampler 6 | # 7 | # A sampler that always makes the same decision for new traces depending 8 | # on the initialization value. Use `Jaeger::Samplers::Const.new(true)` 9 | # to mark all new traces as sampled. 10 | class Const 11 | def initialize(decision) 12 | @decision = decision 13 | @tags = { 14 | 'sampler.type' => 'const', 15 | 'sampler.param' => @decision ? 1 : 0 16 | } 17 | end 18 | 19 | def sample(*) 20 | [@decision, @tags] 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/jaeger/samplers/guaranteed_throughput_probabilistic.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Samplers 5 | # A sampler that leverages both Probabilistic sampler and RateLimiting 6 | # sampler. The RateLimiting is used as a guaranteed lower bound sampler 7 | # such that every operation is sampled at least once in a time interval 8 | # defined by the lower_bound. ie a lower_bound of 1.0 / (60 * 10) will 9 | # sample an operation at least once every 10 minutes. 10 | # 11 | # The Probabilistic sampler is given higher priority when tags are 12 | # emitted, ie. if is_sampled() for both samplers return true, the tags 13 | # for Probabilistic sampler will be used. 14 | class GuaranteedThroughputProbabilistic 15 | attr_reader :tags, :probabilistic_sampler, :lower_bound_sampler 16 | 17 | def initialize(lower_bound:, rate:, lower_bound_sampler: nil) 18 | @probabilistic_sampler = Probabilistic.new(rate: rate) 19 | @lower_bound_sampler = lower_bound_sampler || RateLimiting.new(max_traces_per_second: lower_bound) 20 | @lower_bound_tags = { 21 | 'sampler.type' => 'lowerbound', 22 | 'sampler.param' => rate 23 | } 24 | end 25 | 26 | def update(lower_bound:, rate:) 27 | is_updated = @probabilistic_sampler.update(rate: rate) 28 | is_updated = @lower_bound_sampler.update(max_traces_per_second: lower_bound) || is_updated 29 | @lower_bound_tags['sampler.param'] = rate 30 | is_updated 31 | end 32 | 33 | def sample(...) 34 | is_sampled, probabilistic_tags = @probabilistic_sampler.sample(...) 35 | if is_sampled 36 | # We still call lower_bound_sampler to update the rate limiter budget 37 | @lower_bound_sampler.sample(...) 38 | 39 | return [is_sampled, probabilistic_tags] 40 | end 41 | 42 | is_sampled, _tags = @lower_bound_sampler.sample(...) 43 | [is_sampled, @lower_bound_tags] 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/jaeger/samplers/per_operation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Samplers 5 | # A sampler that leverages both Probabilistic sampler and RateLimiting 6 | # sampler via the GuaranteedThroughputProbabilistic sampler. This sampler 7 | # keeps track of all operations and delegates calls the the respective 8 | # GuaranteedThroughputProbabilistic sampler. 9 | class PerOperation 10 | DEFAULT_SAMPLING_PROBABILITY = 0.001 11 | DEFAULT_LOWER_BOUND = 1.0 / (10.0 * 60.0) # sample once every 10 minutes' 12 | 13 | attr_reader :default_sampling_probability, :lower_bound, :samplers 14 | 15 | def initialize(strategies:, max_operations:) 16 | @max_operations = max_operations 17 | @samplers = {} 18 | update(strategies: strategies) 19 | end 20 | 21 | def update(strategies:) 22 | is_updated = false 23 | 24 | @default_sampling_probability = 25 | strategies[:default_sampling_probability] || DEFAULT_SAMPLING_PROBABILITY 26 | @lower_bound = 27 | strategies[:default_lower_bound_traces_per_second] || DEFAULT_LOWER_BOUND 28 | 29 | if @default_sampler 30 | is_updated = @default_sampler.update(rate: @default_sampling_probability) 31 | else 32 | @default_sampler = Probabilistic.new(rate: @default_sampling_probability) 33 | end 34 | 35 | update_operation_strategies(strategies) || is_updated 36 | end 37 | 38 | def sample(opts) 39 | operation_name = opts.fetch(:operation_name) 40 | sampler = @samplers[operation_name] 41 | return sampler.sample(opts) if sampler 42 | 43 | return @default_sampler.sample(opts) if @samplers.length >= @max_operations 44 | 45 | sampler = GuaranteedThroughputProbabilistic.new( 46 | lower_bound: @lower_bound, 47 | rate: @default_sampling_probability 48 | ) 49 | @samplers[operation_name] = sampler 50 | sampler.sample(opts) 51 | end 52 | 53 | private 54 | 55 | def update_operation_strategies(strategies) 56 | is_updated = false 57 | 58 | (strategies[:per_operation_strategies] || []).each do |strategy| 59 | operation = strategy.fetch(:operation) 60 | rate = strategy.fetch(:probabilistic_sampling).fetch(:sampling_rate) 61 | 62 | if (sampler = @samplers[operation]) 63 | is_updated = sampler.update(lower_bound: @lower_bound, rate: rate) || is_updated 64 | else 65 | @samplers[operation] = GuaranteedThroughputProbabilistic.new( 66 | lower_bound: @lower_bound, 67 | rate: rate 68 | ) 69 | is_updated = true 70 | end 71 | end 72 | 73 | is_updated 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/jaeger/samplers/probabilistic.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Samplers 5 | # Probabilistic sampler 6 | # 7 | # Sample a portion of traces using trace_id as the random decision 8 | class Probabilistic 9 | attr_reader :rate 10 | 11 | def initialize(rate: 0.001) 12 | update(rate: rate) 13 | end 14 | 15 | def update(rate:) 16 | if rate < 0.0 || rate > 1.0 17 | raise "Sampling rate must be between 0.0 and 1.0, got #{rate.inspect}" 18 | end 19 | 20 | new_boundary = TraceId::TRACE_ID_UPPER_BOUND * rate 21 | return false if @boundary == new_boundary 22 | 23 | @rate = rate 24 | @boundary = TraceId::TRACE_ID_UPPER_BOUND * rate 25 | @tags = { 26 | 'sampler.type' => 'probabilistic', 27 | 'sampler.param' => rate 28 | } 29 | 30 | true 31 | end 32 | 33 | def sample(opts) 34 | trace_id = opts.fetch(:trace_id) 35 | 36 | [@boundary >= trace_id, @tags] 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/jaeger/samplers/rate_limiting.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Samplers 5 | # Samples at most max_traces_per_second. The distribution of sampled 6 | # traces follows burstiness of the service, i.e. a service with uniformly 7 | # distributed requests will have those requests sampled uniformly as 8 | # well, but if requests are bursty, especially sub-second, then a number 9 | # of sequential requests can be sampled each second. 10 | class RateLimiting 11 | attr_reader :tags, :max_traces_per_second 12 | 13 | def initialize(max_traces_per_second: 10) 14 | update(max_traces_per_second: max_traces_per_second) 15 | end 16 | 17 | def update(max_traces_per_second:) 18 | if max_traces_per_second < 0.0 19 | raise "max_traces_per_second must not be negative, got #{max_traces_per_second}" 20 | end 21 | 22 | return false if max_traces_per_second == @max_traces_per_second 23 | 24 | @tags = { 25 | 'sampler.type' => 'ratelimiting', 26 | 'sampler.param' => max_traces_per_second 27 | } 28 | @max_traces_per_second = max_traces_per_second 29 | max_balance = [max_traces_per_second, 1.0].max 30 | 31 | if @rate_limiter 32 | @rate_limiter.update( 33 | credits_per_second: max_traces_per_second, 34 | max_balance: max_balance 35 | ) 36 | else 37 | @rate_limiter = RateLimiter.new( 38 | credits_per_second: max_traces_per_second, 39 | max_balance: [max_traces_per_second, 1.0].max 40 | ) 41 | end 42 | 43 | true 44 | end 45 | 46 | def sample(*) 47 | [@rate_limiter.check_credit(1.0), @tags] 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/jaeger/samplers/remote_controlled.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'remote_controlled/instructions_fetcher' 4 | 5 | module Jaeger 6 | module Samplers 7 | class RemoteControlled 8 | DEFAULT_REFRESH_INTERVAL = 60 9 | DEFAULT_SAMPLING_HOST = 'localhost' 10 | DEFAULT_SAMPLING_PORT = 5778 11 | 12 | attr_reader :sampler 13 | 14 | def initialize(opts = {}) 15 | @sampler = opts.fetch(:sampler, Probabilistic.new) 16 | @logger = opts.fetch(:logger, Logger.new($stdout)) 17 | 18 | @poll_executor = opts[:poll_executor] || begin 19 | refresh_interval = opts.fetch(:refresh_interval, DEFAULT_REFRESH_INTERVAL) 20 | RecurringExecutor.new(interval: refresh_interval) 21 | end 22 | 23 | @instructions_fetcher = opts[:instructions_fetcher] || begin 24 | service_name = opts.fetch(:service_name) 25 | host = opts.fetch(:host, DEFAULT_SAMPLING_HOST) 26 | port = opts.fetch(:port, DEFAULT_SAMPLING_PORT) 27 | InstructionsFetcher.new(host: host, port: port, service_name: service_name) 28 | end 29 | end 30 | 31 | def sample(*args) 32 | @poll_executor.start(&method(:poll)) unless @poll_executor.running? 33 | 34 | @sampler.sample(*args) 35 | end 36 | 37 | def poll 38 | @logger.debug 'Fetching sampling strategy' 39 | 40 | instructions = @instructions_fetcher.fetch 41 | handle_instructions(instructions) 42 | rescue InstructionsFetcher::FetchFailed => e 43 | @logger.warn "Fetching sampling strategy failed: #{e.message}" 44 | end 45 | 46 | private 47 | 48 | def handle_instructions(instructions) 49 | if instructions['operationSampling'] 50 | update_per_operation_sampler(instructions['operationSampling']) 51 | else 52 | update_rate_limiting_or_probabilistic_sampler(instructions['strategyType'], instructions) 53 | end 54 | end 55 | 56 | def update_per_operation_sampler(instructions) 57 | strategies = normalize(instructions) 58 | 59 | if @sampler.is_a?(PerOperation) 60 | @sampler.update(strategies: strategies) 61 | else 62 | @sampler = PerOperation.new(strategies: strategies, max_operations: 2000) 63 | end 64 | end 65 | 66 | def normalize(instructions) 67 | { 68 | default_sampling_probability: instructions['defaultSamplingProbability'], 69 | default_lower_bound_traces_per_second: instructions['defaultLowerBoundTracesPerSecond'], 70 | per_operation_strategies: instructions['perOperationStrategies'].map do |strategy| 71 | { 72 | operation: strategy['operation'], 73 | probabilistic_sampling: { 74 | sampling_rate: strategy['probabilisticSampling']['samplingRate'] 75 | } 76 | } 77 | end 78 | } 79 | end 80 | 81 | def update_rate_limiting_or_probabilistic_sampler(strategy, instructions) 82 | case strategy 83 | when 'PROBABILISTIC' 84 | update_probabilistic_strategy(instructions['probabilisticSampling']) 85 | when 'RATE_LIMITING' 86 | update_rate_limiting_strategy(instructions['rateLimitingSampling']) 87 | else 88 | @logger.warn "Unknown sampling strategy #{strategy}" 89 | end 90 | end 91 | 92 | def update_probabilistic_strategy(instructions) 93 | rate = instructions['samplingRate'] 94 | return unless rate 95 | 96 | if @sampler.is_a?(Probabilistic) 97 | @sampler.update(rate: rate) 98 | @logger.info "Updated Probabilistic sampler (rate=#{rate})" 99 | else 100 | @sampler = Probabilistic.new(rate: rate) 101 | @logger.info "Updated sampler to Probabilistic (rate=#{rate})" 102 | end 103 | end 104 | 105 | def update_rate_limiting_strategy(instructions) 106 | max_traces_per_second = instructions['maxTracesPerSecond'] 107 | return unless max_traces_per_second 108 | 109 | if @sampler.is_a?(RateLimiting) 110 | @sampler.update(max_traces_per_second: max_traces_per_second) 111 | @logger.info "Updated Ratelimiting sampler (max_traces_per_second=#{max_traces_per_second})" 112 | else 113 | @sampler = RateLimiting.new(max_traces_per_second: max_traces_per_second) 114 | @logger.info "Updated sampler to Ratelimiting (max_traces_per_second=#{max_traces_per_second})" 115 | end 116 | end 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/jaeger/samplers/remote_controlled/instructions_fetcher.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module Samplers 5 | class RemoteControlled 6 | class InstructionsFetcher 7 | FetchFailed = Class.new(StandardError) 8 | 9 | def initialize(host:, port:, service_name:) 10 | @host = host 11 | @port = port 12 | @service_name = service_name 13 | end 14 | 15 | def fetch 16 | http = Net::HTTP.new(@host, @port) 17 | path = "/sampling?service=#{CGI.escape(@service_name)}" 18 | response = 19 | begin 20 | http.request(Net::HTTP::Get.new(path)) 21 | rescue StandardError => e 22 | raise FetchFailed, e.inspect 23 | end 24 | 25 | unless response.is_a?(Net::HTTPSuccess) 26 | raise FetchFailed, "Unsuccessful response (code=#{response.code})" 27 | end 28 | 29 | JSON.parse(response.body) 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/jaeger/scope.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | # Scope represents an OpenTracing Scope 5 | # 6 | # See http://www.opentracing.io for more information. 7 | class Scope 8 | def initialize(span, scope_stack, finish_on_close:) 9 | @span = span 10 | @scope_stack = scope_stack 11 | @finish_on_close = finish_on_close 12 | @closed = false 13 | end 14 | 15 | # Return the Span scoped by this Scope 16 | # 17 | # @return [Span] 18 | attr_reader :span 19 | 20 | # Close scope 21 | # 22 | # Mark the end of the active period for the current thread and Scope, 23 | # updating the ScopeManager#active in the process. 24 | def close 25 | raise "Tried to close already closed span: #{inspect}" if @closed 26 | 27 | @closed = true 28 | 29 | @span.finish if @finish_on_close 30 | removed_scope = @scope_stack.pop 31 | 32 | if removed_scope != self # rubocop:disable Style/GuardClause 33 | raise 'Removed non-active scope, ' \ 34 | "removed: #{removed_scope.inspect}, "\ 35 | "expected: #{inspect}" 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/jaeger/scope_manager.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'scope_manager/scope_stack' 4 | require_relative 'scope_manager/scope_identifier' 5 | 6 | module Jaeger 7 | # ScopeManager represents an OpenTracing ScopeManager 8 | # 9 | # See http://www.opentracing.io for more information. 10 | # 11 | # The ScopeManager interface abstracts both the activation of Span instances 12 | # via ScopeManager#activate and access to an active Span/Scope via 13 | # ScopeManager#active 14 | # 15 | class ScopeManager 16 | def initialize 17 | @scope_stack = ScopeStack.new 18 | end 19 | 20 | # Make a span instance active 21 | # 22 | # @param span [Span] the Span that should become active 23 | # @param finish_on_close [Boolean] whether the Span should automatically be 24 | # finished when Scope#close is called 25 | # @return [Scope] instance to control the end of the active period for the 26 | # Span. It is a programming error to neglect to call Scope#close on the 27 | # returned instance. 28 | def activate(span, finish_on_close: true) 29 | return active if active && active.span == span 30 | 31 | scope = Scope.new(span, @scope_stack, finish_on_close: finish_on_close) 32 | @scope_stack.push(scope) 33 | scope 34 | end 35 | 36 | # Return active scope 37 | # 38 | # If there is a non-null Scope, its wrapped Span becomes an implicit parent 39 | # (as Reference#CHILD_OF) of any newly-created Span at 40 | # Tracer#start_active_span or Tracer#start_span time. 41 | # 42 | # @return [Scope] the currently active Scope which can be used to access the 43 | # currently active Span. 44 | def active 45 | @scope_stack.peek 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/jaeger/scope_manager/scope_identifier.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | class ScopeManager 5 | # @api private 6 | class ScopeIdentifier 7 | def self.generate 8 | # 65..90.chr are characters between A and Z 9 | "opentracing_#{(0...8).map { rand(65..90).chr }.join}".to_sym 10 | end 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/jaeger/scope_manager/scope_stack.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | class ScopeManager 5 | # @api private 6 | class ScopeStack 7 | def initialize 8 | # Generate a random identifier to use as the Thread.current key. This is 9 | # needed so that it would be possible to create multiple tracers in one 10 | # thread (mostly useful for testing purposes) 11 | @scope_identifier = ScopeIdentifier.generate 12 | end 13 | 14 | def push(scope) 15 | store << scope 16 | end 17 | 18 | def pop 19 | store.pop 20 | end 21 | 22 | def peek 23 | store.last 24 | end 25 | 26 | private 27 | 28 | def store 29 | Thread.current[@scope_identifier] ||= [] 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/jaeger/span.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'span/thrift_log_builder' 4 | 5 | module Jaeger 6 | class Span 7 | attr_accessor :operation_name 8 | 9 | attr_reader :context, :start_time, :end_time, :references, :tags, :logs 10 | 11 | # Creates a new {Span} 12 | # 13 | # @param context [SpanContext] the context of the span 14 | # @param operation_name [String] the operation name 15 | # @param reporter [#report] span reporter 16 | # 17 | # @return [Span] a new Span 18 | def initialize(context, operation_name, reporter, start_time: Time.now, references: [], tags: {}) 19 | @context = context 20 | @operation_name = operation_name 21 | @reporter = reporter 22 | @start_time = start_time 23 | @references = references 24 | @tags = [] 25 | @logs = [] 26 | 27 | tags.each { |key, value| set_tag(key, value) } 28 | end 29 | 30 | # Set a tag value on this span 31 | # 32 | # @param key [String] the key of the tag 33 | # @param value [String, Numeric, Boolean] the value of the tag. If it's not 34 | # a String, Numeric, or Boolean it will be encoded with to_s 35 | def set_tag(key, value) 36 | if key == 'sampling.priority' 37 | if value.to_i.positive? 38 | return self if @context.debug? 39 | 40 | @context.flags = @context.flags | SpanContext::Flags::SAMPLED | SpanContext::Flags::DEBUG 41 | else 42 | @context.flags = @context.flags & ~SpanContext::Flags::SAMPLED 43 | end 44 | return self 45 | end 46 | 47 | # Using Thrift::Tag to avoid unnecessary memory allocations 48 | @tags << ThriftTagBuilder.build(key, value) 49 | 50 | self 51 | end 52 | 53 | # Set a baggage item on the span 54 | # 55 | # @param key [String] the key of the baggage item 56 | # @param value [String] the value of the baggage item 57 | def set_baggage_item(key, value) 58 | @context.set_baggage_item(key, value) 59 | self 60 | end 61 | 62 | # Get a baggage item 63 | # 64 | # @param key [String] the key of the baggage item 65 | # 66 | # @return Value of the baggage item 67 | def get_baggage_item(key) 68 | @context.get_baggage_item(key) 69 | end 70 | 71 | # Add a log entry to this span 72 | # 73 | # @deprecated Use {#log_kv} instead. 74 | def log(...) 75 | warn 'Span#log is deprecated. Please use Span#log_kv instead.' 76 | log_kv(...) 77 | end 78 | 79 | # Add a log entry to this span 80 | # 81 | # @param timestamp [Time] time of the log 82 | # @param fields [Hash] Additional information to log 83 | def log_kv(timestamp: Time.now, **fields) 84 | # Using Thrift::Log to avoid unnecessary memory allocations 85 | @logs << ThriftLogBuilder.build(timestamp, fields) 86 | nil 87 | end 88 | 89 | # Finish the {Span} 90 | # 91 | # @param end_time [Time] custom end time, if not now 92 | def finish(end_time: Time.now) 93 | @end_time = end_time 94 | @reporter.report(self) 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/jaeger/span/thrift_log_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | class Span 5 | class ThriftLogBuilder 6 | FIELDS = Jaeger::Thrift::Log::FIELDS 7 | TIMESTAMP = FIELDS[Jaeger::Thrift::Log::TIMESTAMP].fetch(:name) 8 | LOG_FIELDS = FIELDS[Jaeger::Thrift::Log::LOG_FIELDS].fetch(:name) 9 | 10 | def self.build(timestamp, fields) 11 | Jaeger::Thrift::Log.new( 12 | TIMESTAMP => (timestamp.to_f * 1_000_000).to_i, 13 | LOG_FIELDS => fields.map { |key, value| ThriftTagBuilder.build(key, value) } 14 | ) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/jaeger/span_context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | # SpanContext holds the data for a span that gets inherited to child spans 5 | class SpanContext 6 | module Flags 7 | NONE = 0x00 8 | SAMPLED = 0x01 9 | DEBUG = 0x02 10 | end 11 | 12 | def self.create_from_parent_context(span_context) 13 | new( 14 | trace_id: span_context.trace_id, 15 | parent_id: span_context.span_id, 16 | span_id: TraceId.generate, 17 | flags: span_context.flags, 18 | baggage: span_context.baggage.dup 19 | ) 20 | end 21 | 22 | attr_accessor :flags 23 | attr_reader :span_id, :parent_id, :trace_id, :baggage 24 | 25 | def initialize(span_id:, trace_id:, flags:, parent_id: 0, baggage: {}) 26 | @span_id = span_id 27 | @parent_id = parent_id 28 | @trace_id = trace_id 29 | @baggage = baggage 30 | @flags = flags 31 | end 32 | 33 | def sampled? 34 | @flags & Flags::SAMPLED == Flags::SAMPLED 35 | end 36 | 37 | def debug? 38 | @flags & Flags::DEBUG == Flags::DEBUG 39 | end 40 | 41 | def to_trace_id 42 | @to_trace_id ||= @trace_id.to_s(16) 43 | end 44 | 45 | def to_span_id 46 | @to_span_id ||= @span_id.to_s(16) 47 | end 48 | 49 | def set_baggage_item(key, value) 50 | @baggage[key.to_s] = value.to_s 51 | end 52 | 53 | def get_baggage_item(key) 54 | @baggage[key.to_s] 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/jaeger/thrift_tag_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | class ThriftTagBuilder 5 | FIELDS = Jaeger::Thrift::Tag::FIELDS 6 | KEY = FIELDS[Jaeger::Thrift::Tag::KEY].fetch(:name) 7 | VTYPE = FIELDS[Jaeger::Thrift::Tag::VTYPE].fetch(:name) 8 | VLONG = FIELDS[Jaeger::Thrift::Tag::VLONG].fetch(:name) 9 | VDOUBLE = FIELDS[Jaeger::Thrift::Tag::VDOUBLE].fetch(:name) 10 | VBOOL = FIELDS[Jaeger::Thrift::Tag::VBOOL].fetch(:name) 11 | VSTR = FIELDS[Jaeger::Thrift::Tag::VSTR].fetch(:name) 12 | 13 | def self.build(key, value) 14 | case value 15 | when Integer 16 | Jaeger::Thrift::Tag.new( 17 | KEY => key.to_s, 18 | VTYPE => Jaeger::Thrift::TagType::LONG, 19 | VLONG => value 20 | ) 21 | when Float 22 | Jaeger::Thrift::Tag.new( 23 | KEY => key.to_s, 24 | VTYPE => Jaeger::Thrift::TagType::DOUBLE, 25 | VDOUBLE => value 26 | ) 27 | when TrueClass, FalseClass 28 | Jaeger::Thrift::Tag.new( 29 | KEY => key.to_s, 30 | VTYPE => Jaeger::Thrift::TagType::BOOL, 31 | VBOOL => value 32 | ) 33 | else 34 | Jaeger::Thrift::Tag.new( 35 | KEY => key.to_s, 36 | VTYPE => Jaeger::Thrift::TagType::STRING, 37 | VSTR => value.to_s 38 | ) 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/jaeger/trace_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | module TraceId 5 | MAX_64BIT_SIGNED_INT = (1 << 63) - 1 6 | MAX_64BIT_UNSIGNED_INT = (1 << 64) - 1 7 | MAX_128BIT_UNSIGNED_INT = (1 << 128) - 1 8 | TRACE_ID_UPPER_BOUND = MAX_64BIT_UNSIGNED_INT + 1 9 | 10 | def self.generate 11 | rand(TRACE_ID_UPPER_BOUND) 12 | end 13 | 14 | def self.base16_hex_id_to_uint64(id) 15 | return nil unless id 16 | 17 | value = id.to_i(16) 18 | value > MAX_64BIT_UNSIGNED_INT || value.negative? ? 0 : value 19 | end 20 | 21 | def self.base16_hex_id_to_uint128(id) 22 | return nil unless id 23 | 24 | value = id.to_i(16) 25 | value > MAX_128BIT_UNSIGNED_INT || value.negative? ? 0 : value 26 | end 27 | 28 | # Thrift defines ID fields as i64, which is signed, therefore we convert 29 | # large IDs (> 2^63) to negative longs 30 | def self.uint64_id_to_int64(id) 31 | id > MAX_64BIT_SIGNED_INT ? id - MAX_64BIT_UNSIGNED_INT - 1 : id 32 | end 33 | 34 | # Convert an integer id into a 0 padded hex string. 35 | # If the string is shorter than 16 characters, it will be padded to 16. 36 | # If it is longer than 16 characters, it is padded to 32. 37 | def self.to_hex(id) 38 | hex_str = id.to_s(16) 39 | 40 | # pad the string with '0's to 16 or 32 characters 41 | if hex_str.length > 16 42 | hex_str.rjust(32, '0') 43 | else 44 | hex_str.rjust(16, '0') 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/jaeger/tracer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | class Tracer 5 | def initialize(reporter:, sampler:, injectors:, extractors:) 6 | @reporter = reporter 7 | @sampler = sampler 8 | @injectors = injectors 9 | @extractors = extractors 10 | @scope_manager = ScopeManager.new 11 | end 12 | 13 | # @return [ScopeManager] the current ScopeManager, which may be a no-op 14 | # but may not be nil. 15 | attr_reader :scope_manager 16 | 17 | # @return [Span, nil] the active span. This is a shorthand for 18 | # `scope_manager.active.span`, and nil will be returned if 19 | # Scope#active is nil. 20 | def active_span 21 | scope = scope_manager.active 22 | scope&.span 23 | end 24 | 25 | # Starts a new span. 26 | # 27 | # This is similar to #start_active_span, but the returned Span will not 28 | # be registered via the ScopeManager. 29 | # 30 | # @param operation_name [String] The operation name for the Span 31 | # @param child_of [SpanContext, Span] SpanContext that acts as a parent to 32 | # the newly-started Span. If a Span instance is provided, its 33 | # context is automatically substituted. See [Reference] for more 34 | # information. 35 | # 36 | # If specified, the `references` parameter must be omitted. 37 | # @param references [Array] An array of reference 38 | # objects that identify one or more parent SpanContexts. 39 | # @param start_time [Time] When the Span started, if not now 40 | # @param tags [Hash] Tags to assign to the Span at start time 41 | # @param ignore_active_scope [Boolean] whether to create an implicit 42 | # References#CHILD_OF reference to the ScopeManager#active. 43 | # 44 | # @yield [Span] If passed an optional block, start_span will yield the 45 | # newly-created span to the block. The span will be finished automatically 46 | # after the block is executed. 47 | # @return [Span, Object] If passed an optional block, start_span will return 48 | # the block's return value, otherwise it returns the newly-started Span 49 | # instance, which has not been automatically registered via the 50 | # ScopeManager 51 | def start_span(operation_name, 52 | child_of: nil, 53 | references: nil, 54 | start_time: Time.now, 55 | tags: {}, 56 | ignore_active_scope: false, 57 | **) 58 | context, sampler_tags = prepare_span_context( 59 | operation_name: operation_name, 60 | child_of: child_of, 61 | references: references, 62 | ignore_active_scope: ignore_active_scope 63 | ) 64 | span = Span.new( 65 | context, 66 | operation_name, 67 | @reporter, 68 | start_time: start_time, 69 | references: references, 70 | tags: tags.merge(sampler_tags) 71 | ) 72 | 73 | if block_given? 74 | begin 75 | yield(span) 76 | ensure 77 | span.finish 78 | end 79 | else 80 | span 81 | end 82 | end 83 | 84 | # Creates a newly started and activated Scope 85 | # 86 | # If the Tracer's ScopeManager#active is not nil, no explicit references 87 | # are provided, and `ignore_active_scope` is false, then an inferred 88 | # References#CHILD_OF reference is created to the ScopeManager#active's 89 | # SpanContext when start_active is invoked. 90 | # 91 | # @param operation_name [String] The operation name for the Span 92 | # @param child_of [SpanContext, Span] SpanContext that acts as a parent to 93 | # the newly-started Span. If a Span instance is provided, its 94 | # context is automatically substituted. See [Reference] for more 95 | # information. 96 | # 97 | # If specified, the `references` parameter must be omitted. 98 | # @param references [Array] An array of reference 99 | # objects that identify one or more parent SpanContexts. 100 | # @param start_time [Time] When the Span started, if not now 101 | # @param tags [Hash] Tags to assign to the Span at start time 102 | # @param ignore_active_scope [Boolean] whether to create an implicit 103 | # References#CHILD_OF reference to the ScopeManager#active. 104 | # @param finish_on_close [Boolean] whether span should automatically be 105 | # finished when Scope#close is called 106 | # @yield [Scope] If an optional block is passed to start_active_span it 107 | # will yield the newly-started Scope. If `finish_on_close` is true then the 108 | # Span will be finished automatically after the block is executed. 109 | # @return [Scope, Object] If passed an optional block, start_active_span 110 | # returns the block's return value, otherwise it returns the newly-started 111 | # and activated Scope 112 | def start_active_span(operation_name, 113 | child_of: nil, 114 | references: nil, 115 | start_time: Time.now, 116 | tags: {}, 117 | ignore_active_scope: false, 118 | finish_on_close: true, 119 | **) 120 | span = start_span( 121 | operation_name, 122 | child_of: child_of, 123 | references: references, 124 | start_time: start_time, 125 | tags: tags, 126 | ignore_active_scope: ignore_active_scope 127 | ) 128 | scope = @scope_manager.activate(span, finish_on_close: finish_on_close) 129 | 130 | if block_given? 131 | begin 132 | yield scope 133 | ensure 134 | scope.close 135 | end 136 | else 137 | scope 138 | end 139 | end 140 | 141 | # Inject a SpanContext into the given carrier 142 | # 143 | # @param span_context [SpanContext] 144 | # @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK] 145 | # @param carrier [Carrier] A carrier object of the type dictated by the specified `format` 146 | def inject(span_context, format, carrier) 147 | @injectors.fetch(format).each do |injector| 148 | injector.inject(span_context, carrier) 149 | end 150 | end 151 | 152 | # Extract a SpanContext in the given format from the given carrier. 153 | # 154 | # @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK] 155 | # @param carrier [Carrier] A carrier object of the type dictated by the specified `format` 156 | # @return [SpanContext] the extracted SpanContext or nil if none could be found 157 | def extract(format, carrier) 158 | @extractors 159 | .fetch(format) 160 | .lazy 161 | .map { |extractor| extractor.extract(carrier) } 162 | .reject(&:nil?) 163 | .first 164 | end 165 | 166 | private 167 | 168 | def prepare_span_context(operation_name:, child_of:, references:, ignore_active_scope:) 169 | context = 170 | context_from_child_of(child_of) || 171 | context_from_references(references) || 172 | context_from_active_scope(ignore_active_scope) 173 | 174 | if context 175 | [SpanContext.create_from_parent_context(context), {}] 176 | else 177 | trace_id = TraceId.generate 178 | is_sampled, tags = @sampler.sample( 179 | trace_id: trace_id, 180 | operation_name: operation_name 181 | ) 182 | span_context = SpanContext.new( 183 | trace_id: trace_id, 184 | span_id: trace_id, 185 | flags: is_sampled ? SpanContext::Flags::SAMPLED : SpanContext::Flags::NONE 186 | ) 187 | [span_context, tags] 188 | end 189 | end 190 | 191 | def context_from_child_of(child_of) 192 | return nil unless child_of 193 | 194 | child_of.respond_to?(:context) ? child_of.context : child_of 195 | end 196 | 197 | def context_from_references(references) 198 | return nil if !references || references.none? 199 | 200 | # Prefer CHILD_OF reference if present 201 | ref = references.detect do |reference| 202 | reference.type == OpenTracing::Reference::CHILD_OF 203 | end 204 | (ref || references[0]).context 205 | end 206 | 207 | def context_from_active_scope(ignore_active_scope) 208 | return if ignore_active_scope 209 | 210 | active_scope = @scope_manager.active 211 | active_scope&.span&.context 212 | end 213 | end 214 | end 215 | -------------------------------------------------------------------------------- /lib/jaeger/udp_sender.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative './udp_sender/transport' 4 | require 'socket' 5 | 6 | module Jaeger 7 | class UdpSender 8 | def initialize(host:, port:, encoder:, logger:, max_packet_size: 65_000) 9 | @encoder = encoder 10 | @logger = logger 11 | 12 | transport = Transport.new(host, port, logger: logger) 13 | @protocol_class = ::Thrift::CompactProtocol 14 | protocol = @protocol_class.new(transport) 15 | @client = Jaeger::Thrift::Agent::Client.new(protocol) 16 | @max_packet_size = max_packet_size 17 | end 18 | 19 | def send_spans(spans) 20 | batches = @encoder.encode_limited_size(spans, @protocol_class, @max_packet_size) 21 | batches.each { |batch| @client.emitBatch(batch) } 22 | rescue StandardError => e 23 | @logger.error("Failure while sending a batch of spans: #{e}") 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/jaeger/udp_sender/transport.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Jaeger 4 | class UdpSender 5 | class Transport 6 | FLAGS = 0 7 | 8 | def initialize(host, port, logger:) 9 | @socket = UDPSocket.new 10 | @host = host 11 | @port = port 12 | @logger = logger 13 | @buffer = ::Thrift::MemoryBufferTransport.new 14 | end 15 | 16 | def write(str) 17 | @buffer.write(str) 18 | end 19 | 20 | def flush 21 | data = @buffer.read(@buffer.available) 22 | send_bytes(data) 23 | end 24 | 25 | def open; end 26 | 27 | def close; end 28 | 29 | private 30 | 31 | def send_bytes(bytes) 32 | @socket.send(bytes, FLAGS, @host, @port) 33 | @socket.flush 34 | rescue Errno::ECONNREFUSED 35 | @logger.warn 'Unable to connect to Jaeger Agent' 36 | rescue StandardError => e 37 | @logger.warn "Unable to send spans: #{e.message}" 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /script/create_follows_from_trace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler' 4 | Bundler.setup 5 | 6 | require 'jaeger/client' 7 | 8 | host = ENV['JAEGER_HOST'] || '127.0.0.1' 9 | port = ENV['JAEGER_HOST'] || 6831 10 | 11 | tracer1 = Jaeger::Client.build(host: host, port: port.to_i, service_name: 'test-service', flush_interval: 1) 12 | tracer2 = Jaeger::Client.build(host: host, port: port.to_i, service_name: 'downstream-service', flush_interval: 1) 13 | 14 | rpc_span = tracer1.start_span( 15 | 'receive request', 16 | tags: { 'span.kind' => 'server' } 17 | ) 18 | sleep 0.1 19 | rpc_span.log_kv(event: 'woop di doop', count: 5) 20 | sleep 1 21 | 22 | async_request_span = tracer1.start_span( 23 | 'request async action', 24 | references: [ 25 | OpenTracing::Reference.child_of(rpc_span.context) 26 | ], 27 | tags: { 'span.kind' => 'producer' } 28 | ) 29 | sleep 0.1 30 | 31 | async_request_span.finish 32 | rpc_span.finish 33 | 34 | sleep 0.5 35 | 36 | async_span = tracer2.start_span( 37 | 'async span started after rpc span', 38 | references: [ 39 | OpenTracing::Reference.follows_from(async_request_span.context) 40 | ], 41 | tags: { 42 | 'span.kind' => 'consumer', 43 | 'peer.service' => 'downstream-service' 44 | } 45 | ) 46 | sleep 0.3 # emulate network delay 47 | async_span.finish 48 | 49 | sleep 2 50 | 51 | puts 'Finished' 52 | -------------------------------------------------------------------------------- /script/create_trace: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'bundler' 4 | Bundler.setup 5 | 6 | require 'jaeger/client' 7 | 8 | host = ENV['JAEGER_HOST'] || '127.0.0.1' 9 | port = ENV['JAEGER_HOST'] || 6831 10 | 11 | tracer1 = Jaeger::Client.build(host: host, port: port.to_i, service_name: 'test-service', flush_interval: 1) 12 | tracer2 = Jaeger::Client.build(host: host, port: port.to_i, service_name: 'downstream-service', flush_interval: 1) 13 | 14 | outer_span = tracer1.start_span( 15 | 'receive request', 16 | tags: { 'span.kind' => 'server' } 17 | ) 18 | sleep 0.1 19 | outer_span.log_kv(event: 'woop di doop', count: 5) 20 | sleep 1 21 | 22 | inner_span = tracer1.start_span( 23 | 'fetch info from downstream', 24 | child_of: outer_span, 25 | tags: { 26 | 'span.kind' => 'client', 27 | 'peer.service' => 'downstream-service', 28 | 'peer.ipv4' => '6.6.6.6', 29 | 'peer.port' => 443 30 | } 31 | ) 32 | inner_span.set_tag('error', false) 33 | sleep 0.3 # emulate network delay 34 | 35 | downstream_span = tracer2.start_span( 36 | 'downstream operation', 37 | child_of: inner_span, 38 | tags: { 'span.kind' => 'server' } 39 | ) 40 | sleep 0.5 41 | downstream_span.finish 42 | 43 | sleep 0.2 # emulate network delay 44 | 45 | inner_span.finish 46 | 47 | sleep 0.1 # doing something with fetched info 48 | outer_span.finish 49 | 50 | sleep 2 51 | 52 | puts 'Finished' 53 | -------------------------------------------------------------------------------- /spec/jaeger/encoders/thrift_encoder_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec::Matchers.define :be_a_valid_thrift_span do |_| 4 | match do |actual| 5 | actual.instance_of?(::Jaeger::Thrift::Span) && 6 | !actual.instance_variable_get(:@traceIdLow).nil? && 7 | !actual.instance_variable_get(:@traceIdHigh).nil? && 8 | !actual.instance_variable_get(:@spanId).nil? && 9 | !actual.instance_variable_get(:@parentSpanId).nil? && 10 | !actual.instance_variable_get(:@operationName).nil? && 11 | !actual.instance_variable_get(:@flags).nil? && 12 | !actual.instance_variable_get(:@startTime).nil? && 13 | !actual.instance_variable_get(:@duration).nil? && 14 | !actual.instance_variable_get(:@tags).nil? 15 | end 16 | end 17 | 18 | RSpec.describe Jaeger::Encoders::ThriftEncoder do 19 | let(:logger) { instance_spy(Logger) } 20 | let(:encoder) { described_class.new(service_name: service_name, logger: logger, tags: tags) } 21 | let(:service_name) { 'service-name' } 22 | let(:tags) { {} } 23 | 24 | context 'without custom tags' do 25 | it 'has jaeger.version' do 26 | tags = encoder.encode([]).process.tags 27 | version_tag = tags.detect { |tag| tag.key == 'jaeger.version' } 28 | expect(version_tag.vStr).to match(/Ruby-/) 29 | end 30 | 31 | it 'has hostname' do 32 | tags = encoder.encode([]).process.tags 33 | hostname_tag = tags.detect { |tag| tag.key == 'hostname' } 34 | expect(hostname_tag.vStr).to be_a(String) 35 | end 36 | 37 | it 'has ip' do 38 | tags = encoder.encode([]).process.tags 39 | ip_tag = tags.detect { |tag| tag.key == 'ip' } 40 | expect(ip_tag.vStr).to be_a(String) 41 | end 42 | end 43 | 44 | context 'when hostname is provided' do 45 | let(:tags) { { 'hostname' => hostname } } 46 | let(:hostname) { 'custom-hostname' } 47 | 48 | it 'uses provided hostname in the process tags' do 49 | tags = encoder.encode([]).process.tags 50 | hostname_tag = tags.detect { |tag| tag.key == 'hostname' } 51 | expect(hostname_tag.vStr).to eq(hostname) 52 | end 53 | end 54 | 55 | context 'when ip is provided' do 56 | let(:tags) { { 'ip' => ip } } 57 | let(:ip) { 'custom-ip' } 58 | 59 | it 'uses provided ip in the process tags' do 60 | tags = encoder.encode([]).process.tags 61 | ip_tag = tags.detect { |tag| tag.key == 'ip' } 62 | expect(ip_tag.vStr).to eq(ip) 63 | end 64 | end 65 | 66 | context 'when spans are encoded without limit' do 67 | let(:context) do 68 | Jaeger::SpanContext.new( 69 | trace_id: Jaeger::TraceId.generate, 70 | span_id: Jaeger::TraceId.generate, 71 | flags: Jaeger::SpanContext::Flags::DEBUG 72 | ) 73 | end 74 | let(:example_span) { Jaeger::Span.new(context, 'example_op', nil) } 75 | 76 | it 'encodes spans into one batch' do 77 | encoded_batch = encoder.encode([example_span]) 78 | expect(encoded_batch.spans.first).to be_a_valid_thrift_span 79 | end 80 | end 81 | 82 | context 'when spans are encoded with limits' do 83 | let(:context) do 84 | Jaeger::SpanContext.new( 85 | trace_id: Jaeger::TraceId.generate, 86 | span_id: Jaeger::TraceId.generate, 87 | flags: Jaeger::SpanContext::Flags::DEBUG 88 | ) 89 | end 90 | let(:example_span) { Jaeger::Span.new(context, 'example_op', nil) } 91 | # example span have size of 63, so let's make an array of 100 span 92 | # and set the limit at 5000, so it should return a batch of 2 93 | let(:example_spans) { Array.new(100, example_span) } 94 | 95 | it 'encodes spans into multiple batches' do 96 | encoded_batches = encoder.encode_limited_size(example_spans, ::Thrift::CompactProtocol, 5_000) 97 | expect(encoded_batches.length).to be(2) 98 | expect(encoded_batches.first.spans.first).to be_a_valid_thrift_span 99 | expect(encoded_batches.first.spans.last).to be_a_valid_thrift_span 100 | expect(encoded_batches.last.spans.first).to be_a_valid_thrift_span 101 | expect(encoded_batches.last.spans.last).to be_a_valid_thrift_span 102 | end 103 | end 104 | 105 | context 'when span have reference' do 106 | let(:context) do 107 | Jaeger::SpanContext.new( 108 | trace_id: Jaeger::TraceId.generate, 109 | span_id: Jaeger::TraceId.generate, 110 | flags: Jaeger::SpanContext::Flags::DEBUG 111 | ) 112 | end 113 | let(:reference_context) do 114 | Jaeger::SpanContext.new( 115 | trace_id: rand(Jaeger::TraceId::MAX_128BIT_UNSIGNED_INT), 116 | span_id: Jaeger::TraceId.generate, 117 | flags: Jaeger::SpanContext::Flags::DEBUG 118 | ) 119 | end 120 | let(:example_references) { [OpenTracing::Reference.follows_from(reference_context)] } 121 | let(:example_span) { Jaeger::Span.new(context, 'example_op', nil, references: example_references) } 122 | let(:example_spans) { [example_span] } 123 | 124 | it 'encode span with references' do 125 | batch = encoder.encode(example_spans) 126 | span = batch.spans.first 127 | reference = span.references.first 128 | 129 | trace_id_half_range = [-Jaeger::TraceId::MAX_64BIT_SIGNED_INT, Jaeger::TraceId::MAX_64BIT_SIGNED_INT] 130 | expect(reference.traceIdLow).to be_between(*trace_id_half_range) 131 | expect(reference.traceIdHigh).to be_between(*trace_id_half_range) 132 | expect(reference.traceIdLow).not_to eq 0 133 | expect(reference.traceIdHigh).not_to eq 0 134 | end 135 | end 136 | 137 | context 'when process have additional tags' do 138 | let(:context) do 139 | Jaeger::SpanContext.new( 140 | trace_id: Jaeger::TraceId.generate, 141 | span_id: Jaeger::TraceId.generate, 142 | flags: Jaeger::SpanContext::Flags::DEBUG 143 | ) 144 | end 145 | let(:example_span) { Jaeger::Span.new(context, 'example_op', nil) } 146 | let(:example_spans) { Array.new(150, example_span) } 147 | 148 | let(:tags) { Array.new(50) { |index| ["key#{index}", "value#{index}"] }.to_h } 149 | let(:max_length) { 5_000 } 150 | 151 | it 'size of every batch not exceed limit with compact protocol' do 152 | encoded_batches = encoder.encode_limited_size(example_spans, ::Thrift::CompactProtocol, max_length) 153 | expect(encoded_batches.count).to be > 2 154 | encoded_batches.each do |encoded_batch| 155 | transport = ::Thrift::MemoryBufferTransport.new 156 | protocol = ::Thrift::CompactProtocol.new(transport) 157 | encoded_batch.write(protocol) 158 | expect(transport.available).to be < max_length 159 | end 160 | end 161 | 162 | it 'size of every batch not exceed limit with binary protocol' do 163 | encoded_batches = encoder.encode_limited_size(example_spans, ::Thrift::BinaryProtocol, max_length) 164 | expect(encoded_batches.count).to be > 2 165 | encoded_batches.each do |encoded_batch| 166 | transport = ::Thrift::MemoryBufferTransport.new 167 | protocol = ::Thrift::CompactProtocol.new(transport) 168 | encoded_batch.write(protocol) 169 | expect(transport.available).to be < max_length 170 | end 171 | end 172 | 173 | context 'when limit to low' do 174 | let(:max_length) { 500 } 175 | 176 | it 'raise error' do 177 | expect { encoder.encode_limited_size(example_spans, ::Thrift::CompactProtocol, max_length) } 178 | .to raise_error(StandardError, /Batch header have size \d+, but limit #{max_length}/) 179 | end 180 | end 181 | end 182 | 183 | context 'when one span exceed max length' do 184 | let(:context) do 185 | Jaeger::SpanContext.new( 186 | trace_id: Jaeger::TraceId.generate, 187 | span_id: Jaeger::TraceId.generate, 188 | flags: Jaeger::SpanContext::Flags::DEBUG 189 | ) 190 | end 191 | let(:valid_span_before) { Jaeger::Span.new(context, 'first_span', nil) } 192 | let(:invalid_span_tags) { { 'key' => '0' * 3_000 } } 193 | let(:invalid_span) { Jaeger::Span.new(context, 'invalid_span', nil, tags: invalid_span_tags) } 194 | let(:valid_span_after) { Jaeger::Span.new(context, 'after_span', nil) } 195 | let(:example_spans) { [valid_span_before, invalid_span, valid_span_after] } 196 | let(:max_length) { 2_500 } 197 | 198 | it 'skip invalid span' do 199 | encoded_batches = encoder.encode_limited_size(example_spans, ::Thrift::BinaryProtocol, max_length) 200 | 201 | expect(encoded_batches.size).to eq 1 202 | expect(encoded_batches.first.spans.size).to eq 2 203 | expect(encoded_batches.first.spans.first.operationName).to eq valid_span_before.operation_name 204 | expect(encoded_batches.first.spans.last.operationName).to eq valid_span_after.operation_name 205 | end 206 | 207 | it 'log invalid span name' do 208 | encoder.encode_limited_size(example_spans, ::Thrift::BinaryProtocol, max_length) 209 | expect(logger).to have_received(:warn).with(/Skip span invalid_span with size \d+/) 210 | end 211 | 212 | it 'size of every batch not exceed limit with compact protocol' do 213 | encoded_batches = encoder.encode_limited_size(example_spans, ::Thrift::CompactProtocol, max_length) 214 | expect(encoded_batches.count).to eq 1 215 | encoded_batches.each do |encoded_batch| 216 | transport = ::Thrift::MemoryBufferTransport.new 217 | protocol = ::Thrift::CompactProtocol.new(transport) 218 | encoded_batch.write(protocol) 219 | expect(transport.available).to be < max_length 220 | end 221 | end 222 | 223 | it 'size of every batch not exceed limit with binary protocol' do 224 | encoded_batches = encoder.encode_limited_size(example_spans, ::Thrift::BinaryProtocol, max_length) 225 | expect(encoded_batches.count).to eq 1 226 | encoded_batches.each do |encoded_batch| 227 | transport = ::Thrift::MemoryBufferTransport.new 228 | protocol = ::Thrift::CompactProtocol.new(transport) 229 | encoded_batch.write(protocol) 230 | expect(transport.available).to be < max_length 231 | end 232 | end 233 | end 234 | end 235 | -------------------------------------------------------------------------------- /spec/jaeger/extractors/b3_rack_codec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::Extractors::B3RackCodec do 4 | let(:span_context) { described_class.extract(carrier) } 5 | 6 | let(:operation_name) { 'operator-name' } 7 | let(:trace_id) { '58a515c97fd61fd7' } 8 | let(:parent_id) { '8e5a8c5509c8dcc1' } 9 | let(:span_id) { 'aba8be8d019abed2' } 10 | let(:flags) { '1' } 11 | let(:hexa_max_uint64) { 'f' * 16 } 12 | let(:hexa_max_uint128) { 'f' * 32 } 13 | let(:max_uint64) { (2**64) - 1 } 14 | let(:max_uint128) { (2**128) - 1 } 15 | 16 | context 'when header HTTP_X_B3_SAMPLED is present' do 17 | let(:carrier) do 18 | { 'HTTP_X_B3_TRACEID' => trace_id, 19 | 'HTTP_X_B3_SPANID' => span_id, 20 | 'HTTP_X_B3_PARENTSPANID' => parent_id, 21 | 'HTTP_X_B3_SAMPLED' => flags } 22 | end 23 | 24 | it 'has flags' do 25 | expect(span_context.flags).to eq(flags.to_i(16)) 26 | end 27 | 28 | context 'when trace-id is a max uint64' do 29 | let(:trace_id) { hexa_max_uint64 } 30 | 31 | it 'interprets it correctly' do 32 | expect(span_context.trace_id).to eq(max_uint64) 33 | end 34 | end 35 | 36 | context 'when trace-id is a max uint128' do 37 | let(:trace_id) { hexa_max_uint128 } 38 | 39 | it 'interprets it correctly' do 40 | expect(span_context.trace_id).to eq(max_uint128) 41 | end 42 | end 43 | 44 | context 'when parent-id is a max uint64' do 45 | let(:parent_id) { hexa_max_uint64 } 46 | 47 | it 'interprets it correctly' do 48 | expect(span_context.parent_id).to eq(max_uint64) 49 | end 50 | end 51 | 52 | context 'when span-id is a max uint64' do 53 | let(:span_id) { hexa_max_uint64 } 54 | 55 | it 'interprets it correctly' do 56 | expect(span_context.span_id).to eq(max_uint64) 57 | end 58 | end 59 | 60 | context 'when parent-id is 0' do 61 | let(:parent_id) { '0' } 62 | 63 | it 'sets parent_id to 0' do 64 | expect(span_context.parent_id).to eq(0) 65 | end 66 | end 67 | 68 | context 'when trace-id is missing' do 69 | let(:trace_id) { nil } 70 | 71 | it 'returns nil' do 72 | expect(span_context).to eq(nil) 73 | end 74 | end 75 | 76 | context 'when span-id is missing' do 77 | let(:span_id) { nil } 78 | 79 | it 'returns nil' do 80 | expect(span_context).to eq(nil) 81 | end 82 | end 83 | end 84 | 85 | context 'when header HTTP_X_B3_FLAGS is present' do 86 | let(:carrier) do 87 | { 'HTTP_X_B3_TRACEID' => trace_id, 88 | 'HTTP_X_B3_SPANID' => span_id, 89 | 'HTTP_X_B3_PARENTSPANID' => parent_id, 90 | 'HTTP_X_B3_FLAGS' => '1' } 91 | end 92 | 93 | it 'sets the DEBUG flag' do 94 | expect(span_context.flags).to eq(0x02) 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /spec/jaeger/extractors/b3_text_map_codec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::Extractors::B3TextMapCodec do 4 | let(:span_context) { described_class.extract(carrier) } 5 | 6 | let(:operation_name) { 'operator-name' } 7 | let(:trace_id) { '58a515c97fd61fd7' } 8 | let(:parent_id) { '8e5a8c5509c8dcc1' } 9 | let(:span_id) { 'aba8be8d019abed2' } 10 | let(:flags) { '1' } 11 | let(:hexa_max_uint64) { 'f' * 16 } 12 | let(:hexa_max_uint128) { 'f' * 32 } 13 | let(:max_uint64) { (2**64) - 1 } 14 | let(:max_uint128) { (2**128) - 1 } 15 | 16 | context 'when header x-b3-sampled is present' do 17 | let(:carrier) do 18 | { 'x-b3-traceid' => trace_id, 19 | 'x-b3-spanid' => span_id, 20 | 'x-b3-parentspanid' => parent_id, 21 | 'x-b3-sampled' => flags } 22 | end 23 | 24 | it 'has flags' do 25 | expect(span_context.flags).to eq(flags.to_i(16)) 26 | end 27 | 28 | context 'when trace-id is a max uint64' do 29 | let(:trace_id) { hexa_max_uint64 } 30 | 31 | it 'interprets it correctly' do 32 | expect(span_context.trace_id).to eq(max_uint64) 33 | end 34 | end 35 | 36 | context 'when trace-id is a max uint128' do 37 | let(:trace_id) { hexa_max_uint128 } 38 | 39 | it 'interprets it correctly' do 40 | expect(span_context.trace_id).to eq(max_uint128) 41 | end 42 | end 43 | 44 | context 'when parent-id is a max uint64' do 45 | let(:parent_id) { hexa_max_uint64 } 46 | 47 | it 'interprets it correctly' do 48 | expect(span_context.parent_id).to eq(max_uint64) 49 | end 50 | end 51 | 52 | context 'when span-id is a max uint64' do 53 | let(:span_id) { hexa_max_uint64 } 54 | 55 | it 'interprets it correctly' do 56 | expect(span_context.span_id).to eq(max_uint64) 57 | end 58 | end 59 | 60 | context 'when parent-id is 0' do 61 | let(:parent_id) { '0' } 62 | 63 | it 'sets parent_id to 0' do 64 | expect(span_context.parent_id).to eq(0) 65 | end 66 | end 67 | 68 | context 'when trace-id is missing' do 69 | let(:trace_id) { nil } 70 | 71 | it 'returns nil' do 72 | expect(span_context).to eq(nil) 73 | end 74 | end 75 | 76 | context 'when span-id is missing' do 77 | let(:span_id) { nil } 78 | 79 | it 'returns nil' do 80 | expect(span_context).to eq(nil) 81 | end 82 | end 83 | end 84 | 85 | context 'when header x-b3-flags is present' do 86 | let(:carrier) do 87 | { 'x-b3-traceid' => trace_id, 88 | 'x-b3-spanid' => span_id, 89 | 'x-b3-parentspanid' => parent_id, 90 | 'x-b3-flags' => '1' } 91 | end 92 | 93 | it 'sets the DEBUG flag' do 94 | expect(span_context.flags).to eq(0x02) 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /spec/jaeger/extractors/jaeger_rack_codec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::Extractors::JaegerRackCodec do 4 | let(:span_context) { described_class.extract(carrier) } 5 | 6 | let(:carrier) do 7 | { 8 | 'HTTP_UBER_TRACE_ID' => "#{trace_id}:#{span_id}:#{parent_id}:#{flags}", 9 | 'HTTP_UBERCTX_FOO_BAR' => 'baz' 10 | } 11 | end 12 | let(:trace_id) { '58a515c97fd61fd7' } 13 | let(:parent_id) { '8e5a8c5509c8dcc1' } 14 | let(:span_id) { 'aba8be8d019abed2' } 15 | let(:flags) { '1' } 16 | let(:hexa_max_uint64) { 'ff' * 8 } 17 | let(:max_uint64) { (2**64) - 1 } 18 | 19 | shared_examples 'a valid trace' do 20 | it 'has flags' do 21 | expect(span_context.flags).to eq(flags.to_i(16)) 22 | end 23 | 24 | it 'has baggage' do 25 | expect(span_context.get_baggage_item('foo-bar')).to eq('baz') 26 | end 27 | 28 | context 'when trace-id is a max uint64' do 29 | let(:trace_id) { hexa_max_uint64 } 30 | 31 | it 'interprets it correctly' do 32 | expect(span_context.trace_id).to eq(max_uint64) 33 | end 34 | end 35 | 36 | context 'when parent-id is a max uint64' do 37 | let(:parent_id) { hexa_max_uint64 } 38 | 39 | it 'interprets it correctly' do 40 | expect(span_context.parent_id).to eq(max_uint64) 41 | end 42 | end 43 | 44 | context 'when span-id is a max uint64' do 45 | let(:span_id) { hexa_max_uint64 } 46 | 47 | it 'interprets it correctly' do 48 | expect(span_context.span_id).to eq(max_uint64) 49 | end 50 | end 51 | 52 | context 'when parent-id is 0' do 53 | let(:parent_id) { '0' } 54 | 55 | it 'sets parent_id to 0' do 56 | expect(span_context.parent_id).to eq(0) 57 | end 58 | end 59 | 60 | context 'when trace-id missing' do 61 | let(:trace_id) { nil } 62 | 63 | it 'returns nil' do 64 | expect(span_context).to eq(nil) 65 | end 66 | end 67 | 68 | context 'when span-id missing' do 69 | let(:span_id) { nil } 70 | 71 | it 'returns nil' do 72 | expect(span_context).to eq(nil) 73 | end 74 | end 75 | end 76 | 77 | context 'when serialized trace is not encoded' do 78 | it_behaves_like 'a valid trace' 79 | end 80 | 81 | context 'when serialized trace is encoded' do 82 | before do 83 | carrier['HTTP_UBER_TRACE_ID'] = CGI.escape(carrier['HTTP_UBER_TRACE_ID']) 84 | end 85 | 86 | it_behaves_like 'a valid trace' 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/jaeger/extractors/jaeger_text_map_codec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::Extractors::JaegerTextMapCodec do 4 | let(:span_context) { described_class.extract(carrier) } 5 | 6 | let(:carrier) do 7 | { 8 | 'uber-trace-id' => "#{trace_id}:#{span_id}:#{parent_id}:#{flags}", 9 | 'uberctx-foo-bar' => 'baz' 10 | } 11 | end 12 | let(:trace_id) { '58a515c97fd61fd7' } 13 | let(:parent_id) { '8e5a8c5509c8dcc1' } 14 | let(:span_id) { 'aba8be8d019abed2' } 15 | let(:flags) { '1' } 16 | let(:hexa_max_uint64) { 'ff' * 8 } 17 | let(:max_uint64) { (2**64) - 1 } 18 | 19 | it 'has flags' do 20 | expect(span_context.flags).to eq(flags.to_i(16)) 21 | end 22 | 23 | it 'has baggage' do 24 | expect(span_context.get_baggage_item('foo-bar')).to eq('baz') 25 | end 26 | 27 | context 'when trace-id is a max uint64' do 28 | let(:trace_id) { hexa_max_uint64 } 29 | 30 | it 'interprets it correctly' do 31 | expect(span_context.trace_id).to eq(max_uint64) 32 | end 33 | end 34 | 35 | context 'when parent-id is a max uint64' do 36 | let(:parent_id) { hexa_max_uint64 } 37 | 38 | it 'interprets it correctly' do 39 | expect(span_context.parent_id).to eq(max_uint64) 40 | end 41 | end 42 | 43 | context 'when span-id is a max uint64' do 44 | let(:span_id) { hexa_max_uint64 } 45 | 46 | it 'interprets it correctly' do 47 | expect(span_context.span_id).to eq(max_uint64) 48 | end 49 | end 50 | 51 | context 'when parent-id is 0' do 52 | let(:parent_id) { '0' } 53 | 54 | it 'sets parent_id to 0' do 55 | expect(span_context.parent_id).to eq(0) 56 | end 57 | end 58 | 59 | context 'when trace-id missing' do 60 | let(:trace_id) { nil } 61 | 62 | it 'returns nil' do 63 | expect(span_context).to eq(nil) 64 | end 65 | end 66 | 67 | context 'when span-id missing' do 68 | let(:span_id) { nil } 69 | 70 | it 'returns nil' do 71 | expect(span_context).to eq(nil) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/jaeger/extractors/trace_context_rack_codec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::Extractors::TraceContextRackCodec do 4 | it 'parses valid sampled v0 traceparent' do 5 | carrier = { 'HTTP_TRACEPARENT' => '00-00000000030c22224787fd223b027d8d-000f5cf3018c0a93-01' } 6 | span_context = described_class.extract(carrier) 7 | 8 | expect(span_context.trace_id).to eq(943_123_332_103_493_452_342_394_253) 9 | expect(span_context.span_id).to eq(4_324_323_423_423_123) 10 | expect(span_context.sampled?).to eq(true) 11 | end 12 | 13 | it 'parses valid non-sampled v0 traceparent' do 14 | carrier = { 'HTTP_TRACEPARENT' => '00-00000000030c22224787fd223b027d8d-000f5cf3018c0a93-00' } 15 | span_context = described_class.extract(carrier) 16 | 17 | expect(span_context.trace_id).to eq(943_123_332_103_493_452_342_394_253) 18 | expect(span_context.span_id).to eq(4_324_323_423_423_123) 19 | expect(span_context.sampled?).to eq(false) 20 | end 21 | 22 | it 'parses valid traceparent with largest trace id and span id' do 23 | carrier = { 'HTTP_TRACEPARENT' => '00-ffffffffffffffffffffffffffffffff-ffffffffffffffff-01' } 24 | span_context = described_class.extract(carrier) 25 | 26 | expect(span_context.trace_id).to eq((2**128) - 1) 27 | expect(span_context.span_id).to eq((2**64) - 1) 28 | expect(span_context.sampled?).to eq(true) 29 | end 30 | 31 | it 'returns nil when unhandled version' do 32 | carrier = { 'HTTP_TRACEPARENT' => '01-00000000030c22224787fd223b027d8d-000f5cf3018c0a93-01' } 33 | span_context = described_class.extract(carrier) 34 | 35 | expect(span_context).to eq(nil) 36 | end 37 | 38 | it 'returns nil when trace id is 0' do 39 | carrier = { 'HTTP_TRACEPARENT' => '00-00000000000000000000000000000000-000f5cf3018c0a93-01' } 40 | span_context = described_class.extract(carrier) 41 | 42 | expect(span_context).to eq(nil) 43 | end 44 | 45 | it 'returns nil when span id is 0' do 46 | carrier = { 'HTTP_TRACEPARENT' => '00-00000000030c22224787fd223b027d8d-0000000000000000-01' } 47 | span_context = described_class.extract(carrier) 48 | 49 | expect(span_context).to eq(nil) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /spec/jaeger/injectors/b3_rack_codec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::Injectors::B3RackCodec do 4 | let(:inject) { described_class.inject(span_context, carrier) } 5 | 6 | let(:span_context) { build_span_context } 7 | let(:carrier) { {} } 8 | 9 | it 'sets trace information' do 10 | inject 11 | expect(carrier['x-b3-traceid']).to eq(span_context.trace_id.to_s(16).rjust(16, '0')) 12 | expect(carrier['x-b3-spanid']).to eq(span_context.span_id.to_s(16).rjust(16, '0')) 13 | expect(carrier['x-b3-parentspanid']).to eq(span_context.parent_id.to_s(16).rjust(16, '0')) 14 | expect(carrier['x-b3-sampled']).to eq(span_context.flags.to_s(16)) 15 | end 16 | 17 | context 'when sampler flag is DEBUG' do 18 | let(:span_context) do 19 | Jaeger::SpanContext.new( 20 | span_id: Jaeger::TraceId.generate, 21 | trace_id: Jaeger::TraceId.generate, 22 | flags: 0x02 23 | ) 24 | end 25 | 26 | it 'sets the x-b3-flags header' do 27 | inject 28 | expect(carrier).to have_key 'x-b3-flags' 29 | expect(carrier['x-b3-flags']).to eq '1' 30 | end 31 | 32 | it 'does not set the x-b3-sampled header' do 33 | inject 34 | expect(carrier).not_to have_key 'x-b3-sampled' 35 | end 36 | end 37 | 38 | context 'when span context IDs are longer than 16 characters' do 39 | let(:span_context) do 40 | Jaeger::SpanContext.new( 41 | span_id: 0xFFFFFFFFFFFFFFFFF, 42 | parent_id: 0xFFFFFFFFFFFFFFFFF, 43 | trace_id: 0xFFFFFFFFFFFFFFFFF, 44 | flags: 0 45 | ) 46 | end 47 | 48 | it 'pads the hex id strings to 32 characters' do 49 | inject 50 | expect(carrier['x-b3-traceid'].length).to eq 32 51 | expect(carrier['x-b3-spanid'].length).to eq 32 52 | expect(carrier['x-b3-parentspanid'].length).to eq 32 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /spec/jaeger/injectors/jaeger_rack_codec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::Injectors::JaegerRackCodec do 4 | let(:tracer) do 5 | Jaeger::Tracer.new( 6 | reporter: instance_spy(Jaeger::Reporters::RemoteReporter), 7 | sampler: Jaeger::Samplers::Const.new(true), 8 | injectors: Jaeger::Injectors.prepare({}), 9 | extractors: Jaeger::Extractors.prepare({}) 10 | ) 11 | end 12 | let(:span) { tracer.start_span('test') } 13 | 14 | it 'sets trace information' do 15 | carrier = {} 16 | inject(span, carrier) 17 | 18 | expect(carrier['uber-trace-id']).to eq( 19 | [ 20 | span.context.trace_id.to_s(16), 21 | span.context.span_id.to_s(16), 22 | span.context.parent_id.to_s(16), 23 | span.context.flags.to_s(16) 24 | ].join('%3A') 25 | ) 26 | end 27 | 28 | it 'sets baggage' do 29 | span.set_baggage_item('foo', 'bar') 30 | span.set_baggage_item('x', 'y') 31 | carrier = {} 32 | inject(span, carrier) 33 | 34 | expect(carrier['uberctx-foo']).to eq('bar') 35 | expect(carrier['uberctx-x']).to eq('y') 36 | end 37 | 38 | def inject(span, carrier) 39 | described_class.inject(span.context, carrier) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/jaeger/injectors/jaeger_text_map_codec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::Injectors::JaegerTextMapCodec do 4 | let(:tracer) do 5 | Jaeger::Tracer.new( 6 | reporter: instance_spy(Jaeger::Reporters::RemoteReporter), 7 | sampler: Jaeger::Samplers::Const.new(true), 8 | injectors: Jaeger::Injectors.prepare({}), 9 | extractors: Jaeger::Extractors.prepare({}) 10 | ) 11 | end 12 | let(:span) { tracer.start_span('test') } 13 | 14 | it 'sets trace information' do 15 | carrier = {} 16 | inject(span, carrier) 17 | 18 | expect(carrier['uber-trace-id']).to eq( 19 | [ 20 | span.context.trace_id.to_s(16), 21 | span.context.span_id.to_s(16), 22 | span.context.parent_id.to_s(16), 23 | span.context.flags.to_s(16) 24 | ].join(':') 25 | ) 26 | end 27 | 28 | it 'sets baggage' do 29 | span.set_baggage_item('foo', 'bar') 30 | span.set_baggage_item('x', 'y') 31 | carrier = {} 32 | inject(span, carrier) 33 | 34 | expect(carrier['uberctx-foo']).to eq('bar') 35 | expect(carrier['uberctx-x']).to eq('y') 36 | end 37 | 38 | def inject(span, carrier) 39 | described_class.inject(span.context, carrier) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /spec/jaeger/injectors/trace_context_rack_codec_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::Injectors::TraceContextRackCodec do 4 | let(:tracer) do 5 | Jaeger::Tracer.new( 6 | reporter: instance_spy(Jaeger::Reporters::RemoteReporter), 7 | sampler: Jaeger::Samplers::Const.new(true), 8 | injectors: Jaeger::Injectors.prepare({}), 9 | extractors: Jaeger::Extractors.prepare({}) 10 | ) 11 | end 12 | let(:span) { tracer.start_span('test') } 13 | 14 | it 'sets traceparent' do 15 | span_context = build_span_context( 16 | trace_id: 943_123_332_103_493_452_342_394_253, 17 | span_id: 4_324_323_423_423_123, 18 | flags: Jaeger::SpanContext::Flags::SAMPLED 19 | ) 20 | 21 | carrier = {} 22 | described_class.inject(span_context, carrier) 23 | 24 | expect(carrier['traceparent']).to eq('00-00000000030c22224787fd223b027d8d-000f5cf3018c0a93-01') 25 | end 26 | 27 | it 'sets traceparent with largest trace id and span id' do 28 | span_context = build_span_context( 29 | trace_id: (2**128) - 1, 30 | span_id: (2**64) - 1, 31 | flags: Jaeger::SpanContext::Flags::SAMPLED 32 | ) 33 | 34 | carrier = {} 35 | described_class.inject(span_context, carrier) 36 | 37 | expect(carrier['traceparent']).to eq('00-ffffffffffffffffffffffffffffffff-ffffffffffffffff-01') 38 | end 39 | 40 | def inject(span, carrier) 41 | described_class.inject(span.context, carrier) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/jaeger/rate_limiter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::RateLimiter do 4 | let(:start_time) { Time.now } 5 | 6 | before { Timecop.freeze(start_time) } 7 | 8 | after { Timecop.return } 9 | 10 | describe '#check_credit' do 11 | it 'returns false if item cost is higher than balance' do 12 | limiter = build_limiter(credits_per_second: 5) 13 | expect(limiter.check_credit(6)).to eq(false) 14 | end 15 | 16 | it 'returns true until there is credit left' do 17 | limiter = build_limiter(credits_per_second: 2) 18 | expect(limiter.check_credit(1.0)).to eq(true) 19 | expect(limiter.check_credit(1.0)).to eq(true) 20 | expect(limiter.check_credit(1.0)).to eq(false) 21 | end 22 | 23 | it 'returns true when there is enough credit' do 24 | limiter = build_limiter(credits_per_second: 2) 25 | 26 | # use all credit 27 | expect(limiter.check_credit(1.0)).to eq(true) 28 | expect(limiter.check_credit(1.0)).to eq(true) 29 | expect(limiter.check_credit(1.0)).to eq(false) 30 | 31 | # move time 250ms forward, not enough credits to pay for one sample 32 | Timecop.travel(start_time + 0.25) 33 | expect(limiter.check_credit(1.0)).to eq(false) 34 | 35 | # move time 250ms forward, now enough credits to pay for one sample 36 | Timecop.travel(start_time + 0.5) 37 | expect(limiter.check_credit(1.0)).to eq(true) 38 | expect(limiter.check_credit(1.0)).to eq(false) 39 | 40 | # move time 5s forward, enough to accumulate credits for 10 samples, 41 | # but it should still be capped at 2 42 | Timecop.travel(start_time + 5.5) 43 | expect(limiter.check_credit(1.0)).to eq(true) 44 | expect(limiter.check_credit(1.0)).to eq(true) 45 | expect(limiter.check_credit(1.0)).to eq(false) 46 | end 47 | end 48 | 49 | describe '#update' do 50 | context 'when balance was full before the update' do 51 | it 'keeps the new balance full' do 52 | limiter = build_limiter(credits_per_second: 1) 53 | expect(limiter.check_credit(1.0)).to eq(true) 54 | 55 | limiter.update(credits_per_second: 2, max_balance: 2) 56 | expect(limiter.check_credit(1.0)).to eq(false) 57 | end 58 | end 59 | 60 | context 'when balance was half full before the update' do 61 | it 'marks the new balance half full' do 62 | limiter = build_limiter(credits_per_second: 2) 63 | expect(limiter.check_credit(1.0)).to eq(true) 64 | 65 | limiter.update(credits_per_second: 4, max_balance: 4) 66 | expect(limiter.check_credit(1.0)).to eq(true) 67 | expect(limiter.check_credit(1.0)).to eq(true) 68 | expect(limiter.check_credit(1.0)).to eq(false) 69 | end 70 | end 71 | end 72 | 73 | def build_limiter(credits_per_second:, **opts) 74 | described_class.new(**{ 75 | credits_per_second: credits_per_second, 76 | max_balance: credits_per_second 77 | }.merge(opts)) 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /spec/jaeger/recurring_executor_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::RecurringExecutor do 4 | let(:executor) { described_class.new(interval: interval) } 5 | let(:small_delay) { 0.05 } 6 | 7 | after { executor.stop } 8 | 9 | context 'when interval is set to 0' do 10 | let(:interval) { 0 } 11 | 12 | it 'executes block only once' do 13 | count = 0 14 | executor.start { count += 1 } 15 | 16 | sleep(small_delay) 17 | expect(count).to eq(1) 18 | end 19 | end 20 | 21 | context 'when interval is above 0' do 22 | let(:interval) { 3 } 23 | 24 | it 'executes block periodically' do 25 | count = 0 26 | 27 | allow(executor).to receive(:sleep).with(interval) do 28 | sleep(interval) if count >= 4 29 | end 30 | 31 | executor.start { count += 1 } 32 | 33 | sleep(small_delay) 34 | 35 | expect(count).to eq(4) 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/jaeger/reporters/composite_reporter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Reporters::CompositeReporter do 4 | let(:reporter) { described_class.new(reporters: [reporter1, reporter2]) } 5 | let(:reporter1) { instance_spy(Jaeger::Reporters::InMemoryReporter) } 6 | let(:reporter2) { instance_spy(Jaeger::Reporters::RemoteReporter) } 7 | 8 | describe '#report' do 9 | it 'forwards span to all reporters' do 10 | span = build_span 11 | reporter.report(span) 12 | 13 | expect(reporter1).to have_received(:report).with(span) 14 | expect(reporter2).to have_received(:report).with(span) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/jaeger/reporters/in_memory_reporter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Reporters::InMemoryReporter do 4 | let(:reporter) { described_class.new } 5 | 6 | describe '#report' do 7 | it 'adds span to in memory spans list' do 8 | span1 = build_span 9 | span2 = build_span 10 | 11 | reporter.report(span1) 12 | reporter.report(span2) 13 | 14 | expect(reporter.spans).to eq([span1, span2]) 15 | end 16 | end 17 | 18 | describe '#clear' do 19 | it 'clears spans from the reporter' do 20 | span1 = build_span 21 | reporter.report(span1) 22 | 23 | reporter.clear 24 | 25 | span2 = build_span 26 | reporter.report(span2) 27 | 28 | expect(reporter.spans).to eq([span2]) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /spec/jaeger/reporters/logging_reporter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Reporters::LoggingReporter do 4 | let(:reporter) { described_class.new(logger: logger) } 5 | let(:logger) { instance_spy(Logger) } 6 | 7 | describe '#report' do 8 | it 'logs out span information' do 9 | operation_name = 'my-op-name' 10 | start_time = Time.utc(2018, 11, 10, 15, 24, 30) 11 | end_time = Time.utc(2018, 11, 10, 15, 24, 33) 12 | 13 | span = build_span(operation_name: operation_name, start_time: start_time) 14 | span.finish(end_time: end_time) 15 | reporter.report(span) 16 | 17 | expect(logger).to have_received(:info).with( 18 | <<-STR.gsub(/\s+/, ' ').strip 19 | Span reported: {:operation_name=>"#{operation_name}", 20 | :start_time=>"#{start_time.iso8601}", 21 | :end_time=>"#{end_time.iso8601}", 22 | :trace_id=>"#{span.context.to_trace_id}", 23 | :span_id=>"#{span.context.to_span_id}"} 24 | STR 25 | ) 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/jaeger/reporters/null_reporter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Reporters::NullReporter do 4 | describe '#report' do 5 | it 'does nothing' do 6 | span = instance_double(Jaeger::Span) 7 | expect { described_class.new.report(span) }.not_to raise_error 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/jaeger/reporters/remote_reporter_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Reporters::RemoteReporter do 4 | let(:reporter) { described_class.new(sender: sender, flush_interval: 1) } 5 | let(:sender) { spy } 6 | let(:operation_name) { 'op-name' } 7 | 8 | before { allow(Thread).to receive(:new) } 9 | 10 | describe '#report' do 11 | let(:context) do 12 | Jaeger::SpanContext.new( 13 | trace_id: Jaeger::TraceId.generate, 14 | span_id: Jaeger::TraceId.generate, 15 | flags: flags 16 | ) 17 | end 18 | let(:span) { Jaeger::Span.new(context, operation_name, reporter) } 19 | 20 | context 'when span has debug mode enabled' do 21 | let(:flags) { Jaeger::SpanContext::Flags::DEBUG } 22 | 23 | it 'buffers the span' do 24 | reporter.report(span) 25 | reporter.flush 26 | expect(sender).to have_received(:send_spans).once 27 | end 28 | end 29 | 30 | context 'when span is sampled' do 31 | let(:flags) { Jaeger::SpanContext::Flags::SAMPLED } 32 | 33 | it 'buffers the span' do 34 | reporter.report(span) 35 | reporter.flush 36 | expect(sender).to have_received(:send_spans).once 37 | end 38 | end 39 | 40 | context 'when span does not have debug mode nor is sampled' do 41 | let(:flags) { Jaeger::SpanContext::Flags::NONE } 42 | 43 | it 'does not buffer the span' do 44 | reporter.report(span) 45 | reporter.flush 46 | expect(sender).not_to have_received(:send_spans) 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /spec/jaeger/samplers/const_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Samplers::Const do 4 | let(:sampler) { described_class.new(decision) } 5 | let(:sample_args) { { trace_id: Jaeger::TraceId.generate } } 6 | let(:sample_result) { sampler.sample(sample_args) } 7 | let(:is_sampled) { sample_result[0] } 8 | let(:tags) { sample_result[1] } 9 | 10 | context 'when decision is set to true' do 11 | let(:decision) { true } 12 | 13 | it 'sets sampling to always true' do 14 | expect(is_sampled).to eq(true) 15 | end 16 | 17 | it 'returns tags with param 1' do 18 | expect(tags).to eq( 19 | 'sampler.type' => 'const', 20 | 'sampler.param' => 1 21 | ) 22 | end 23 | end 24 | 25 | context 'when decision is set to false' do 26 | let(:decision) { false } 27 | 28 | it 'sets sampling to always false' do 29 | expect(is_sampled).to eq(false) 30 | end 31 | 32 | it 'returns tags with param 0' do 33 | expect(tags).to eq( 34 | 'sampler.type' => 'const', 35 | 'sampler.param' => 0 36 | ) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/jaeger/samplers/guaranteed_throughput_probabilistic_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Samplers::GuaranteedThroughputProbabilistic do 4 | let(:sampler) do 5 | described_class.new( 6 | lower_bound: lower_bound, 7 | rate: rate, 8 | lower_bound_sampler: lower_bound_sampler 9 | ) 10 | end 11 | let(:lower_bound) { 5 } 12 | let(:rate) { 0.5 } 13 | let(:lower_bound_sampler) { instance_double(Jaeger::Samplers::RateLimiting) } 14 | 15 | let(:max_traces_per_second) { 10 } 16 | let(:sample_args) { { trace_id: trace_id } } 17 | let(:sample_result) { sampler.sample(**sample_args) } 18 | let(:is_sampled) { sample_result[0] } 19 | let(:tags) { sample_result[1] } 20 | 21 | context 'when rate is set to 0' do 22 | let(:rate) { 0 } 23 | let(:trace_id) { Jaeger::TraceId.generate } 24 | 25 | context 'when lower bound return false' do 26 | before do 27 | allow(lower_bound_sampler).to receive(:sample) 28 | .and_return([false, {}]) 29 | end 30 | 31 | it 'returns false for every trace' do 32 | expect(is_sampled).to eq(false) 33 | end 34 | 35 | it 'returns tags with param 0' do 36 | expect(tags).to eq( 37 | 'sampler.type' => 'lowerbound', 38 | 'sampler.param' => rate 39 | ) 40 | end 41 | end 42 | 43 | context 'when lower bound sampler returns true' do 44 | before do 45 | allow(lower_bound_sampler).to receive(:sample) 46 | .and_return([true, {}]) 47 | end 48 | 49 | it 'returns true' do 50 | expect(is_sampled).to eq(true) 51 | end 52 | 53 | it 'returns tags with lower bound param' do 54 | expect(tags).to eq( 55 | 'sampler.type' => 'lowerbound', 56 | 'sampler.param' => rate 57 | ) 58 | end 59 | end 60 | end 61 | 62 | context 'when rate is set to 1' do 63 | let(:rate) { 1 } 64 | let(:trace_id) { Jaeger::TraceId.generate } 65 | 66 | before do 67 | allow(lower_bound_sampler).to receive(:sample) 68 | end 69 | 70 | it 'returns true for every trace' do 71 | expect(is_sampled).to eq(true) 72 | end 73 | 74 | it 'returns tags with param 1' do 75 | expect(tags).to eq( 76 | 'sampler.type' => 'probabilistic', 77 | 'sampler.param' => rate 78 | ) 79 | end 80 | 81 | it 'calls lower bound sampler' do 82 | expect(lower_bound_sampler).to receive(:sample).with(sample_args) 83 | is_sampled 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /spec/jaeger/samplers/per_operation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Samplers::PerOperation do 4 | let(:sampler) { described_class.new(strategies: strategies, max_operations: max_operations) } 5 | let(:max_operations) { 100 } 6 | let(:start_time) { Time.now } 7 | 8 | before { Timecop.freeze(start_time) } 9 | 10 | after { Timecop.return } 11 | 12 | context 'when operation strategy is defined' do 13 | context 'when operation rate is set to 0' do 14 | let(:strategies) do 15 | { 16 | default_sampling_probability: 1.0, 17 | default_lower_bound_traces_per_second: 1, 18 | per_operation_strategies: [ 19 | { operation: 'foo', probabilistic_sampling: { sampling_rate: 0 } } 20 | ] 21 | } 22 | end 23 | 24 | it 'uses lower bound sampler' do 25 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'foo')) 26 | expect(is_sampled).to eq(true) 27 | 28 | # false because limit is full 29 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'foo')) 30 | expect(is_sampled).to eq(false) 31 | 32 | # true because different operation 33 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'bar')) 34 | expect(is_sampled).to eq(true) 35 | end 36 | 37 | it 'returns tags with lower bound param' do 38 | _is_sampled, tags = sampler.sample(sample_args(operation_name: 'foo')) 39 | expect(tags).to eq( 40 | 'sampler.type' => 'lowerbound', 41 | 'sampler.param' => 0 42 | ) 43 | end 44 | end 45 | 46 | context 'when operation rate is set to 1' do 47 | let(:strategies) do 48 | { 49 | default_sampling_probability: 0, 50 | default_lower_bound_traces_per_second: 1, 51 | per_operation_strategies: [ 52 | { operation: 'foo', probabilistic_sampling: { sampling_rate: 1.0 } } 53 | ] 54 | } 55 | end 56 | 57 | it 'uses operation probabilistic sampler' do 58 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'foo')) 59 | expect(is_sampled).to eq(true) 60 | 61 | # true because rate is set to 1 62 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'foo')) 63 | expect(is_sampled).to eq(true) 64 | 65 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'bar')) 66 | expect(is_sampled).to eq(true) 67 | 68 | # false because different operation and lower boundary is full 69 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'bar')) 70 | expect(is_sampled).to eq(false) 71 | end 72 | 73 | it 'returns tags with lower bound param' do 74 | _is_sampled, tags = sampler.sample(sample_args(operation_name: 'foo')) 75 | expect(tags).to eq( 76 | 'sampler.type' => 'probabilistic', 77 | 'sampler.param' => 1.0 78 | ) 79 | end 80 | end 81 | end 82 | 83 | context 'when operation strategy is undefined' do 84 | context 'when default rate is set to 0' do 85 | let(:strategies) do 86 | { 87 | default_sampling_probability: 0, 88 | default_lower_bound_traces_per_second: 1 89 | } 90 | end 91 | 92 | it 'uses lower bound sampler' do 93 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'foo')) 94 | expect(is_sampled).to eq(true) 95 | 96 | # false because limit is full 97 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'foo')) 98 | expect(is_sampled).to eq(false) 99 | 100 | # true because different operation 101 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'bar')) 102 | expect(is_sampled).to eq(true) 103 | end 104 | 105 | it 'returns tags with lower bound param' do 106 | _is_sampled, tags = sampler.sample(sample_args(operation_name: 'foo')) 107 | expect(tags).to eq( 108 | 'sampler.type' => 'lowerbound', 109 | 'sampler.param' => 0 110 | ) 111 | end 112 | end 113 | 114 | context 'when default rate is set to 1' do 115 | let(:strategies) do 116 | { 117 | default_sampling_probability: 1, 118 | default_lower_bound_traces_per_second: 1 119 | } 120 | end 121 | 122 | it 'uses probabilistic sampling which returns always true' do 123 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'foo')) 124 | expect(is_sampled).to eq(true) 125 | 126 | is_sampled, _tags = sampler.sample(sample_args(operation_name: 'foo')) 127 | expect(is_sampled).to eq(true) 128 | end 129 | 130 | it 'returns tags with lower bound param' do 131 | _is_sampled, tags = sampler.sample(sample_args(operation_name: 'foo')) 132 | expect(tags).to eq( 133 | 'sampler.type' => 'probabilistic', 134 | 'sampler.param' => 1 135 | ) 136 | end 137 | end 138 | end 139 | 140 | def sample_args(opts = {}) 141 | { 142 | trace_id: Jaeger::TraceId.generate, 143 | operation_name: 'operation-name' 144 | }.merge(opts) 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /spec/jaeger/samplers/probabilistic_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Samplers::Probabilistic do 4 | let(:sampler) { described_class.new(rate: rate) } 5 | let(:sample_args) { { trace_id: trace_id } } 6 | let(:sample_result) { sampler.sample(**sample_args) } 7 | let(:is_sampled) { sample_result[0] } 8 | let(:tags) { sample_result[1] } 9 | 10 | context 'when rate is set to 0' do 11 | let(:rate) { 0 } 12 | let(:trace_id) { Jaeger::TraceId.generate } 13 | 14 | it 'returns false for every trace' do 15 | expect(is_sampled).to eq(false) 16 | end 17 | 18 | it 'returns tags with param 0' do 19 | expect(tags).to eq( 20 | 'sampler.type' => 'probabilistic', 21 | 'sampler.param' => 0 22 | ) 23 | end 24 | end 25 | 26 | context 'when rate is set to 0.5' do 27 | let(:rate) { 0.5 } 28 | 29 | context 'when trace is over the boundary' do 30 | let(:trace_id) { (Jaeger::TraceId::TRACE_ID_UPPER_BOUND / 2) + 1 } 31 | 32 | it 'returns false' do 33 | expect(is_sampled).to eq(false) 34 | end 35 | 36 | it 'returns tags with param 0.5' do 37 | expect(tags).to eq( 38 | 'sampler.type' => 'probabilistic', 39 | 'sampler.param' => 0.5 40 | ) 41 | end 42 | end 43 | 44 | context 'when trace is under the boundary' do 45 | let(:trace_id) { (Jaeger::TraceId::TRACE_ID_UPPER_BOUND / 2) - 1 } 46 | 47 | it 'returns true' do 48 | expect(is_sampled).to eq(true) 49 | end 50 | 51 | it 'returns tags with param 0.5' do 52 | expect(tags).to eq( 53 | 'sampler.type' => 'probabilistic', 54 | 'sampler.param' => 0.5 55 | ) 56 | end 57 | end 58 | end 59 | 60 | context 'when rate is set to 1' do 61 | let(:rate) { 1 } 62 | let(:trace_id) { Jaeger::TraceId.generate } 63 | 64 | it 'returns true for every trace' do 65 | expect(is_sampled).to eq(true) 66 | end 67 | 68 | it 'returns tags with param 1' do 69 | expect(tags).to eq( 70 | 'sampler.type' => 'probabilistic', 71 | 'sampler.param' => 1 72 | ) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/jaeger/samplers/rate_limiting_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Samplers::RateLimiting do 4 | let(:sampler) { described_class.new(max_traces_per_second: max_traces_per_second) } 5 | let(:max_traces_per_second) { 10 } 6 | let(:sample_args) { { trace_id: Jaeger::TraceId.generate } } 7 | let(:sample_result) { sampler.sample(sample_args) } 8 | let(:is_sampled) { sample_result[0] } 9 | let(:tags) { sample_result[1] } 10 | 11 | context 'when max_traces_per_second is negative' do 12 | let(:max_traces_per_second) { -1 } 13 | 14 | it 'throws an error' do 15 | expect { sampler }.to raise_error( 16 | "max_traces_per_second must not be negative, got #{max_traces_per_second}" 17 | ) 18 | end 19 | end 20 | 21 | describe '#sample' do 22 | it 'returns a boolean' do 23 | expect(is_sampled).to be(true).or be(false) 24 | end 25 | 26 | it 'returns tags' do 27 | expect(tags).to eq( 28 | 'sampler.type' => 'ratelimiting', 29 | 'sampler.param' => max_traces_per_second 30 | ) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/jaeger/samplers/remote_controlled/instructions_fetcher_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Samplers::RemoteControlled::InstructionsFetcher do 4 | let(:fetcher) { described_class.new(host: host, port: port, service_name: service_name) } 5 | let(:host) { 'some-host' } 6 | let(:port) { 1234 } 7 | let(:service_name) { 'test-service' } 8 | 9 | it 'returns parsed response on success' do 10 | body = { 'foo' => 'bar' } 11 | serialized_body = body.to_json 12 | 13 | stub_request(:get, "http://#{host}:#{port}/sampling?service=#{service_name}") 14 | .to_return(status: 200, body: serialized_body, headers: {}) 15 | 16 | expect(fetcher.fetch).to eq(body) 17 | end 18 | 19 | it 'raises FetchFailed when http code is not 2xx' do 20 | stub_request(:get, "http://#{host}:#{port}/sampling?service=#{service_name}") 21 | .to_return(status: 400, body: 'Bad Request', headers: {}) 22 | 23 | expect { fetcher.fetch } 24 | .to raise_error(described_class::FetchFailed, 'Unsuccessful response (code=400)') 25 | end 26 | 27 | it 'raises FetchFailed when request throws an exception' do 28 | stub_request(:get, "http://#{host}:#{port}/sampling?service=#{service_name}") 29 | .to_raise(StandardError.new('some error')) 30 | 31 | expect { fetcher.fetch } 32 | .to raise_error(described_class::FetchFailed, '#') 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/jaeger/samplers/remote_controlled_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Samplers::RemoteControlled do 4 | let(:sampler) do 5 | described_class.new( 6 | logger: logger, 7 | poll_executor: poll_executor, 8 | instructions_fetcher: instructions_fetcher 9 | ) 10 | end 11 | let(:logger) { Logger.new('/dev/null') } 12 | let(:poll_executor) { {} } 13 | let(:instructions_fetcher) { instance_spy(described_class::InstructionsFetcher) } 14 | 15 | let(:trace_id) { Jaeger::TraceId.generate } 16 | let(:operation_name) { 'operation-name' } 17 | let(:sample_args) { { trace_id: trace_id, operation_name: operation_name } } 18 | let(:parsed_response) { nil } 19 | 20 | before do 21 | allow(instructions_fetcher).to receive(:fetch).and_return(parsed_response) 22 | end 23 | 24 | context 'when agent returns probabilistic strategy' do 25 | let(:rate) { 0.6 } 26 | let(:parsed_response) do 27 | { 28 | 'strategyType' => 'PROBABILISTIC', 29 | 'probabilisticSampling' => { 'samplingRate' => rate } 30 | } 31 | end 32 | 33 | it 'sets sampler to probabilistic sampler' do 34 | sampler.poll 35 | expect(underlying_sampler).to be_a(Jaeger::Samplers::Probabilistic) 36 | expect(underlying_sampler.rate).to eq(rate) 37 | end 38 | end 39 | 40 | context 'when agent returns rate limiting strategy' do 41 | let(:max_traces_per_second) { 6 } 42 | 43 | let(:parsed_response) do 44 | { 45 | 'strategyType' => 'RATE_LIMITING', 46 | 'rateLimitingSampling' => { 'maxTracesPerSecond' => max_traces_per_second } 47 | } 48 | end 49 | 50 | it 'sets sampler to ratelimiting sampler' do 51 | sampler.poll 52 | expect(underlying_sampler).to be_a(Jaeger::Samplers::RateLimiting) 53 | expect(underlying_sampler.max_traces_per_second).to eq(max_traces_per_second) 54 | end 55 | end 56 | 57 | context 'when agent returns per operation strategy' do 58 | let(:default_sampling_rate) { 0.002 } 59 | let(:op_sampling_rate) { 0.003 } 60 | let(:default_traces_per_second) { 2 } 61 | 62 | let(:parsed_response) do 63 | { 64 | 'strategyType' => 'PROBABILISTIC', 65 | 'operationSampling' => { 66 | 'defaultSamplingProbability' => default_sampling_rate, 67 | 'defaultLowerBoundTracesPerSecond' => default_traces_per_second, 68 | 'perOperationStrategies' => [ 69 | { 70 | 'operation' => operation_name, 71 | 'probabilisticSampling' => { 72 | 'samplingRate' => op_sampling_rate 73 | } 74 | } 75 | ] 76 | } 77 | } 78 | end 79 | 80 | it 'sets sampler to per operation sampler' do 81 | sampler.poll 82 | expect(underlying_sampler).to be_a(Jaeger::Samplers::PerOperation) 83 | expect(underlying_sampler.default_sampling_probability).to eq(default_sampling_rate) 84 | expect(underlying_sampler.lower_bound).to eq(default_traces_per_second) 85 | 86 | op_sampler = underlying_sampler.samplers[operation_name] 87 | expect(op_sampler).to be_a(Jaeger::Samplers::GuaranteedThroughputProbabilistic) 88 | expect(op_sampler.probabilistic_sampler.rate).to eq(op_sampling_rate) 89 | expect(op_sampler.lower_bound_sampler.max_traces_per_second).to eq(default_traces_per_second) 90 | end 91 | end 92 | 93 | context 'when agent returns unknown strategy' do 94 | let(:parsed_response) do 95 | { 'strategyType' => 'UH_WHAT_IS_THIS' } 96 | end 97 | 98 | it 'keeps the current strategy' do 99 | previous_sampler = underlying_sampler 100 | sampler.poll 101 | expect(underlying_sampler).to be(previous_sampler) 102 | end 103 | end 104 | 105 | context 'when fetching strategies fails' do 106 | before do 107 | allow(instructions_fetcher).to receive(:fetch) do 108 | raise Jaeger::Samplers::RemoteControlled::InstructionsFetcher::FetchFailed, 'ouch' 109 | end 110 | end 111 | 112 | it 'keeps the current strategy' do 113 | previous_sampler = underlying_sampler 114 | sampler.poll 115 | expect(underlying_sampler).to be(previous_sampler) 116 | end 117 | end 118 | 119 | def underlying_sampler 120 | sampler.sampler 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /spec/jaeger/scope_manager/scope_identifier_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::ScopeManager::ScopeIdentifier do 4 | describe '.generate' do 5 | it 'generates an identifier' do 6 | id = described_class.generate 7 | expect(id).to be_a(Symbol) 8 | expect(id).to match(/opentracing_[A-Z]{8}/) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/jaeger/scope_manager_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::ScopeManager do 4 | let(:scope_manager) { described_class.new } 5 | let(:span) { instance_spy(Jaeger::Span) } 6 | 7 | context 'when activating a span' do 8 | it 'marks the span active' do 9 | scope_manager.activate(span) 10 | expect(scope_manager.active.span).to eq(span) 11 | end 12 | 13 | it 'changes the active span' do 14 | span2 = instance_spy(Jaeger::Span) 15 | 16 | scope_manager.activate(span) 17 | scope_manager.activate(span2) 18 | expect(scope_manager.active.span).to eq(span2) 19 | end 20 | end 21 | 22 | context 'when closing an active span' do 23 | it 'reverts to the previous active span' do 24 | span2 = instance_spy(Jaeger::Span) 25 | 26 | scope_manager.activate(span) 27 | 28 | scope_manager.activate(span2) 29 | scope_manager.active.close 30 | 31 | expect(scope_manager.active.span).to eq(span) 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/jaeger/scope_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Scope do 4 | let(:span) { instance_spy(Jaeger::Span) } 5 | let(:scope_stack) { Jaeger::ScopeManager::ScopeStack.new } 6 | let(:finish_on_close) { true } 7 | let(:scope) { described_class.new(span, scope_stack, finish_on_close: finish_on_close) } 8 | 9 | before do 10 | scope_stack.push(scope) 11 | end 12 | 13 | describe '#span' do 14 | it 'returns scope span' do 15 | scope = described_class.new(span, scope_stack, finish_on_close: true) 16 | expect(scope.span).to eq(span) 17 | end 18 | end 19 | 20 | describe '#close' do 21 | context 'when finish_on_close is true' do 22 | let(:finish_on_close) { true } 23 | 24 | it 'finishes the span' do 25 | scope.close 26 | expect(scope.span).to have_received(:finish) 27 | end 28 | 29 | it 'removes the scope from the scope stack' do 30 | expect(scope_stack.peek).to eq(scope) 31 | scope.close 32 | expect(scope_stack.peek).to eq(nil) 33 | end 34 | end 35 | 36 | context 'when finish_on_close is false' do 37 | let(:finish_on_close) { false } 38 | 39 | it 'does not finish the span' do 40 | scope.close 41 | expect(scope.span).not_to have_received(:finish) 42 | end 43 | 44 | it 'removes the scope from the scope stack' do 45 | expect(scope_stack.peek).to eq(scope) 46 | scope.close 47 | expect(scope_stack.peek).to eq(nil) 48 | end 49 | end 50 | 51 | context 'when scope is already closed' do 52 | before { scope.close } 53 | 54 | it 'throws an exception' do 55 | expect { scope.close } 56 | .to raise_error("Tried to close already closed span: #{scope.inspect}") 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /spec/jaeger/span_context_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::SpanContext do 4 | describe '.create_from_parent_context' do 5 | let(:parent) do 6 | described_class.new( 7 | trace_id: trace_id, 8 | parent_id: nil, 9 | span_id: parent_span_id, 10 | flags: parent_flags 11 | ) 12 | end 13 | let(:trace_id) { 'trace-id' } 14 | let(:parent_span_id) { 'span-id' } 15 | let(:parent_flags) { described_class::Flags::SAMPLED } 16 | 17 | it 'has same trace ID' do 18 | context = described_class.create_from_parent_context(parent) 19 | expect(context.trace_id).to eq(trace_id) 20 | end 21 | 22 | it 'has same parent span id as parent id' do 23 | context = described_class.create_from_parent_context(parent) 24 | expect(context.parent_id).to eq(parent_span_id) 25 | end 26 | 27 | it 'has same its own span id' do 28 | context = described_class.create_from_parent_context(parent) 29 | expect(context.span_id).not_to eq(parent_span_id) 30 | end 31 | 32 | it 'has parent flags' do 33 | context = described_class.create_from_parent_context(parent) 34 | expect(context.flags).to eq(parent_flags) 35 | end 36 | 37 | it 'has parent baggage' do 38 | parent.set_baggage_item('foo', 'bar') 39 | 40 | context = described_class.create_from_parent_context(parent) 41 | expect(context.baggage).to eq('foo' => 'bar') 42 | 43 | # Ensure changing parent baggage doesn't change the child 44 | parent.set_baggage_item('foo', 'bar2') 45 | expect(context.baggage).to eq('foo' => 'bar') 46 | end 47 | end 48 | 49 | describe '#to_trace_id' do 50 | it 'returns trace id in hex format' do 51 | span_context = build_span_context(trace_id: 67_667_974_448_284_343) 52 | expect(span_context.to_trace_id).to eq('f067aa0ba902b7') 53 | end 54 | end 55 | 56 | describe '#to_span_id' do 57 | it 'returns span id in hex format' do 58 | span_context = build_span_context(span_id: 67_667_974_448_284_343) 59 | expect(span_context.to_span_id).to eq('f067aa0ba902b7') 60 | end 61 | end 62 | 63 | def build_span_context(opts) 64 | described_class.new(**{ 65 | trace_id: Jaeger::TraceId.generate, 66 | span_id: Jaeger::TraceId.generate, 67 | flags: 0 68 | }.merge(opts)) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/jaeger/span_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | RSpec.describe Jaeger::Span do 4 | describe '#log' do 5 | let(:span) { described_class.new(nil, 'operation_name', nil) } 6 | 7 | it 'is deprecated' do 8 | expect { span.log(key: 'value') } 9 | .to output(/Span#log is deprecated/).to_stderr 10 | end 11 | 12 | it 'delegates to #log_kv' do 13 | allow(span).to receive(:log_kv) 14 | 15 | args = { key: 'value' } 16 | span.log(**args) 17 | 18 | expect(span).to have_received(:log_kv).with(**args) 19 | end 20 | end 21 | 22 | describe '#log_kv' do 23 | let(:span) { described_class.new(nil, 'operation_name', nil) } 24 | let(:fields) { { key1: 'value1', key2: 69 } } 25 | let(:expected_thrift_fields) do 26 | [ 27 | Jaeger::Thrift::Tag.new(key: 'key1', vType: 0, vStr: 'value1'), 28 | Jaeger::Thrift::Tag.new(key: 'key2', vType: 3, vLong: 69) 29 | ] 30 | end 31 | 32 | it 'returns nil' do 33 | expect(span.log_kv(key: 'value')).to be_nil 34 | end 35 | 36 | it 'adds log to span' do 37 | span.log_kv(**fields) 38 | 39 | expect(span.logs.count).to eq(1) 40 | thrift_log = span.logs[0] 41 | expect(thrift_log.timestamp).to be_a(Integer) 42 | expect(thrift_log.fields).to match(expected_thrift_fields) 43 | end 44 | 45 | it 'adds log to span with specific timestamp' do 46 | timestamp = Time.now 47 | span.log_kv(**fields.merge(timestamp: timestamp)) 48 | 49 | expect(span.logs.count).to eq(1) 50 | thrift_log = span.logs[0] 51 | expect(thrift_log.timestamp).to eq((timestamp.to_f * 1_000_000).to_i) 52 | expect(thrift_log.fields).to match(expected_thrift_fields) 53 | end 54 | end 55 | 56 | it 'stores and retrieves baggage' do 57 | span_context = build_span_context 58 | span = described_class.new(span_context, 'operation_name', nil) 59 | 60 | span.set_baggage_item('foo', 'bar') 61 | expect(span.get_baggage_item('foo')).to eq('bar') 62 | 63 | span.set_baggage_item('foo', 'baz') 64 | expect(span.get_baggage_item('foo')).to eq('baz') 65 | end 66 | 67 | describe '#set_tag' do 68 | let(:span_context) { build_span_context } 69 | let(:span) { described_class.new(span_context, 'operation_name', nil) } 70 | 71 | context 'when sampling.priority' do 72 | it 'sets debug flag to true when sampling.priority is greater than 0' do 73 | span.set_tag('sampling.priority', 1) 74 | expect(span.context.debug?).to eq(true) 75 | expect(span.context.sampled?).to eq(true) 76 | end 77 | 78 | it 'sets sampled flag to false when sampling.priority is 0' do 79 | span.set_tag('sampling.priority', 0) 80 | expect(span.context.debug?).to eq(false) 81 | expect(span.context.sampled?).to eq(false) 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /spec/jaeger/trace_id_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::TraceId do 4 | describe '.base16_hex_id_to_uint64' do 5 | it 'returns 0 when negative number' do 6 | id = described_class.base16_hex_id_to_uint64('-1') 7 | expect(id).to eq(0) 8 | end 9 | 10 | it 'returns 0 when larger than 64 bit uint' do 11 | id = described_class.base16_hex_id_to_uint64('10000000000000000') 12 | expect(id).to eq(0) 13 | end 14 | 15 | it 'converts base16 encoded hex to uint64' do 16 | id = described_class.base16_hex_id_to_uint64('ff' * 8) 17 | expect(id).to eq((2**64) - 1) 18 | end 19 | end 20 | 21 | describe '.uint64_id_to_int64' do 22 | it 'converts large IDs to negative longs' do 23 | id = described_class.uint64_id_to_int64((2**64) - 1) 24 | expect(id).to eq(-1) 25 | end 26 | 27 | it 'converts non large IDs to positive longs' do 28 | id = described_class.uint64_id_to_int64((2**63) - 1) 29 | expect(id).to eq((2**63) - 1) 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/jaeger/tracer_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe Jaeger::Tracer do 4 | let(:tracer) do 5 | described_class.new( 6 | reporter: reporter, 7 | sampler: sampler, 8 | injectors: Jaeger::Injectors.prepare(injectors), 9 | extractors: Jaeger::Extractors.prepare(extractors) 10 | ) 11 | end 12 | let(:reporter) { instance_spy(Jaeger::Reporters::RemoteReporter) } 13 | let(:sampler) { Jaeger::Samplers::Const.new(true) } 14 | let(:injectors) { {} } 15 | let(:extractors) { {} } 16 | 17 | describe '#start_span' do 18 | let(:operation_name) { 'operator-name' } 19 | 20 | context 'when a root span' do 21 | let(:span) { tracer.start_span(operation_name) } 22 | 23 | describe 'span context' do 24 | it 'has span_id' do 25 | expect(span.context.span_id).not_to be_nil 26 | end 27 | 28 | it 'has trace_id' do 29 | expect(span.context.trace_id).not_to be_nil 30 | end 31 | 32 | it 'does not have parent' do 33 | expect(span.context.parent_id).to eq(0) 34 | end 35 | end 36 | end 37 | 38 | context 'when a child span context is provided' do 39 | let(:root_span) { tracer.start_span(root_operation_name) } 40 | let(:span) { tracer.start_span(operation_name, child_of: root_span.context) } 41 | let(:root_operation_name) { 'root-operation-name' } 42 | 43 | describe 'span context' do 44 | it 'has span_id' do 45 | expect(span.context.span_id).not_to be_nil 46 | end 47 | 48 | it 'has trace_id' do 49 | expect(span.context.trace_id).not_to be_nil 50 | end 51 | 52 | it 'does not have parent_id' do 53 | expect(span.context.parent_id).not_to eq(0) 54 | end 55 | end 56 | end 57 | 58 | context 'when a child span is provided' do 59 | let(:root_span) { tracer.start_span(root_operation_name) } 60 | let(:span) { tracer.start_span(operation_name, child_of: root_span) } 61 | let(:root_operation_name) { 'root-operation-name' } 62 | 63 | describe 'span context' do 64 | it 'has span_id' do 65 | expect(span.context.span_id).not_to be_nil 66 | end 67 | 68 | it 'has trace_id' do 69 | expect(span.context.trace_id).not_to be_nil 70 | end 71 | 72 | it 'does not have parent_id' do 73 | expect(span.context.parent_id).not_to eq(0) 74 | end 75 | end 76 | end 77 | 78 | context 'when block given' do 79 | let(:span) { tracer.start_span(operation_name) } 80 | 81 | it 'returns the block value' do 82 | block_value = 'block value' 83 | 84 | return_value = tracer.start_span(operation_name) do |_span| 85 | block_value 86 | end 87 | 88 | expect(return_value).to eq(block_value) 89 | end 90 | 91 | it 'finishes the span after executing the block' do 92 | span_in_block = nil 93 | 94 | tracer.start_span(operation_name) do |span| 95 | span_in_block = span 96 | end 97 | 98 | expect(span_in_block.end_time).to be_a(Time) 99 | end 100 | end 101 | end 102 | 103 | describe '#start_active_span' do 104 | let(:operation_name) { 'operator-name' } 105 | 106 | context 'when a root span' do 107 | let(:scope) { tracer.start_active_span(operation_name) } 108 | let(:span) { scope.span } 109 | 110 | describe 'span context' do 111 | it 'has span_id' do 112 | expect(span.context.span_id).not_to be_nil 113 | end 114 | 115 | it 'has trace_id' do 116 | expect(span.context.trace_id).not_to be_nil 117 | end 118 | 119 | it 'does not have parent_id' do 120 | expect(span.context.parent_id).to eq(0) 121 | end 122 | end 123 | end 124 | 125 | context 'when a child span context is provided' do 126 | let(:root_span) { tracer.start_span(root_operation_name) } 127 | let(:scope) { tracer.start_active_span(operation_name, child_of: root_span.context) } 128 | let(:span) { scope.span } 129 | let(:root_operation_name) { 'root-operation-name' } 130 | 131 | describe 'span context' do 132 | it 'has span_id' do 133 | expect(span.context.span_id).not_to be_nil 134 | end 135 | 136 | it 'has trace_id' do 137 | expect(span.context.trace_id).not_to be_nil 138 | end 139 | 140 | it 'does not have parent_id' do 141 | expect(span.context.parent_id).not_to eq(0) 142 | end 143 | end 144 | end 145 | 146 | context 'when a child span is provided' do 147 | let(:root_span) { tracer.start_span(root_operation_name) } 148 | let(:scope) { tracer.start_active_span(operation_name, child_of: root_span) } 149 | let(:span) { scope.span } 150 | let(:root_operation_name) { 'root-operation-name' } 151 | 152 | describe 'span context' do 153 | it 'has span_id' do 154 | expect(span.context.span_id).not_to be_nil 155 | end 156 | 157 | it 'has trace_id' do 158 | expect(span.context.trace_id).not_to be_nil 159 | end 160 | 161 | it 'does not have parent_id' do 162 | expect(span.context.parent_id).not_to eq(0) 163 | end 164 | end 165 | end 166 | 167 | context 'when already existing active span' do 168 | let(:root_operation_name) { 'root-operation-name' } 169 | 170 | it 'uses active span as a parent span' do 171 | tracer.start_active_span(root_operation_name) do |parent_scope| 172 | tracer.start_active_span(operation_name) do |scope| 173 | expect(scope.span.context.parent_id).to eq(parent_scope.span.context.span_id) 174 | end 175 | end 176 | end 177 | end 178 | 179 | context 'when block given' do 180 | it 'returns the block value' do 181 | block_value = 'block value' 182 | 183 | return_value = tracer.start_active_span(operation_name) do |_scope| 184 | block_value 185 | end 186 | 187 | expect(return_value).to eq(block_value) 188 | end 189 | 190 | it 'closes the scope after executing the block' do 191 | scope_in_block = nil 192 | 193 | tracer.start_active_span(operation_name) do |scope| 194 | scope_in_block = scope 195 | end 196 | 197 | expect(tracer.scope_manager.active).to be(nil) 198 | expect(scope_in_block.span.end_time).to be_a(Time) 199 | end 200 | end 201 | end 202 | 203 | describe '#active_span' do 204 | let(:root_operation_name) { 'root-operation-name' } 205 | let(:operation_name) { 'operation-name' } 206 | 207 | it 'returns the span of the active scope' do 208 | expect(tracer.active_span).to eq(nil) 209 | 210 | tracer.start_active_span(root_operation_name) do |parent_scope| 211 | expect(tracer.active_span).to eq(parent_scope.span) 212 | 213 | tracer.start_active_span(operation_name) do |scope| 214 | expect(tracer.active_span).to eq(scope.span) 215 | end 216 | 217 | expect(tracer.active_span).to eq(parent_scope.span) 218 | end 219 | 220 | expect(tracer.active_span).to eq(nil) 221 | end 222 | end 223 | 224 | describe '#inject' do 225 | let(:operation_name) { 'operator-name' } 226 | let(:span) { tracer.start_span(operation_name) } 227 | let(:span_context) { span.context } 228 | let(:carrier) { {} } 229 | 230 | context 'when default injectors' do 231 | it 'calls inject on JaegerTextMapCodec when FORMAT_TEXT_MAP' do 232 | expect(Jaeger::Injectors::JaegerTextMapCodec).to receive(:inject) 233 | .with(span_context, carrier) 234 | tracer.inject(span_context, OpenTracing::FORMAT_TEXT_MAP, carrier) 235 | end 236 | 237 | it 'calls inject on JaegerRackCodec when FORMAT_RACK' do 238 | expect(Jaeger::Injectors::JaegerRackCodec).to receive(:inject) 239 | .with(span_context, carrier) 240 | tracer.inject(span_context, OpenTracing::FORMAT_RACK, carrier) 241 | end 242 | end 243 | 244 | context 'when custom injectors' do 245 | let(:injectors) do 246 | { OpenTracing::FORMAT_RACK => [custom_injector1, custom_injector2] } 247 | end 248 | let(:custom_injector1) { class_double(Jaeger::Injectors::JaegerTextMapCodec, inject: nil) } 249 | let(:custom_injector2) { class_double(Jaeger::Injectors::JaegerTextMapCodec, inject: nil) } 250 | 251 | it 'calls all custom injectors' do 252 | tracer.inject(span_context, OpenTracing::FORMAT_RACK, carrier) 253 | 254 | expect(custom_injector1).to have_received(:inject).with(span_context, carrier) 255 | expect(custom_injector2).to have_received(:inject).with(span_context, carrier) 256 | end 257 | end 258 | end 259 | 260 | describe '#extract' do 261 | let(:carrier) { {} } 262 | let(:span_context) { instance_double(Jaeger::SpanContext) } 263 | 264 | context 'when default extractors' do 265 | it 'calls extract on JaegerTextMapCodec when FORMAT_TEXT_MAP' do 266 | allow(Jaeger::Extractors::JaegerTextMapCodec).to receive(:extract) 267 | .with(carrier) 268 | .and_return(span_context) 269 | expect(tracer.extract(OpenTracing::FORMAT_TEXT_MAP, carrier)).to eq(span_context) 270 | end 271 | 272 | it 'calls extract on JaegerRackCodec when FORMAT_RACK' do 273 | allow(Jaeger::Extractors::JaegerRackCodec).to receive(:extract) 274 | .with(carrier) 275 | .and_return(span_context) 276 | expect(tracer.extract(OpenTracing::FORMAT_RACK, carrier)).to eq(span_context) 277 | end 278 | end 279 | 280 | context 'when custom extractors' do 281 | let(:extractors) do 282 | { OpenTracing::FORMAT_RACK => [custom_extractor1, custom_extractor2] } 283 | end 284 | let(:custom_extractor1) { double } 285 | let(:custom_extractor2) { double } 286 | 287 | it 'calls all custom extractors when no results' do 288 | allow(custom_extractor1).to receive(:extract).with(carrier).and_return(nil) 289 | allow(custom_extractor2).to receive(:extract).with(carrier).and_return(nil) 290 | expect(tracer.extract(OpenTracing::FORMAT_RACK, carrier)).to eq(nil) 291 | 292 | expect(custom_extractor1).to have_received(:extract) 293 | expect(custom_extractor2).to have_received(:extract) 294 | end 295 | 296 | it 'returns result from the first matching extractor' do 297 | allow(custom_extractor1).to receive(:extract).with(carrier) { span_context } 298 | expect(tracer.extract(OpenTracing::FORMAT_RACK, carrier)).to eq(span_context) 299 | end 300 | end 301 | end 302 | end 303 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'bundler/setup' 2 | require 'jaeger/client' 3 | require 'timecop' 4 | 5 | require 'webmock/rspec' 6 | 7 | RSpec.configure do |config| 8 | # Enable flags like --only-failures and --next-failure 9 | config.example_status_persistence_file_path = '.rspec_status' 10 | 11 | config.expect_with :rspec do |c| 12 | c.syntax = :expect 13 | end 14 | 15 | def build_span_context(opts = {}) 16 | Jaeger::SpanContext.new(**{ 17 | trace_id: Jaeger::TraceId.generate, 18 | span_id: Jaeger::TraceId.generate, 19 | flags: Jaeger::SpanContext::Flags::SAMPLED 20 | }.merge(opts)) 21 | end 22 | 23 | def build_span(opts = {}) 24 | span_context = opts.delete(:span_context) || build_span_context 25 | operation_name = opts.delete(:operation_name) || 'operation-name' 26 | reporter = opts.delete(:reporter) || Jaeger::Reporters::NullReporter.new 27 | 28 | Jaeger::Span.new(span_context, operation_name, reporter, **opts) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /thrift/agent.thrift: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2016 Uber Technologies, Inc. 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | # THE SOFTWARE. 22 | 23 | include "jaeger.thrift" 24 | include "zipkincore.thrift" 25 | 26 | namespace java com.uber.jaeger.agent.thrift 27 | namespace rb Jaeger.Thrift 28 | 29 | service Agent { 30 | oneway void emitZipkinBatch(1: list spans) 31 | oneway void emitBatch(1: jaeger.Batch batch) 32 | } 33 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/agent.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | require 'jaeger/thrift/agent_types' 9 | 10 | module Jaeger 11 | module Thrift 12 | module Agent 13 | class Client 14 | include ::Thrift::Client 15 | 16 | def emitZipkinBatch(spans) 17 | send_emitZipkinBatch(spans) 18 | end 19 | 20 | def send_emitZipkinBatch(spans) 21 | send_oneway_message('emitZipkinBatch', EmitZipkinBatch_args, :spans => spans) 22 | end 23 | def emitBatch(batch) 24 | send_emitBatch(batch) 25 | end 26 | 27 | def send_emitBatch(batch) 28 | send_oneway_message('emitBatch', EmitBatch_args, :batch => batch) 29 | end 30 | end 31 | 32 | class Processor 33 | include ::Thrift::Processor 34 | 35 | def process_emitZipkinBatch(seqid, iprot, oprot) 36 | args = read_args(iprot, EmitZipkinBatch_args) 37 | @handler.emitZipkinBatch(args.spans) 38 | return 39 | end 40 | 41 | def process_emitBatch(seqid, iprot, oprot) 42 | args = read_args(iprot, EmitBatch_args) 43 | @handler.emitBatch(args.batch) 44 | return 45 | end 46 | 47 | end 48 | 49 | # HELPER FUNCTIONS AND STRUCTURES 50 | 51 | class EmitZipkinBatch_args 52 | include ::Thrift::Struct, ::Thrift::Struct_Union 53 | SPANS = 1 54 | 55 | FIELDS = { 56 | SPANS => {:type => ::Thrift::Types::LIST, :name => 'spans', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::Span}} 57 | } 58 | 59 | def struct_fields; FIELDS; end 60 | 61 | def validate 62 | end 63 | 64 | ::Thrift::Struct.generate_accessors self 65 | end 66 | 67 | class EmitZipkinBatch_result 68 | include ::Thrift::Struct, ::Thrift::Struct_Union 69 | 70 | FIELDS = { 71 | 72 | } 73 | 74 | def struct_fields; FIELDS; end 75 | 76 | def validate 77 | end 78 | 79 | ::Thrift::Struct.generate_accessors self 80 | end 81 | 82 | class EmitBatch_args 83 | include ::Thrift::Struct, ::Thrift::Struct_Union 84 | BATCH = 1 85 | 86 | FIELDS = { 87 | BATCH => {:type => ::Thrift::Types::STRUCT, :name => 'batch', :class => ::Jaeger::Thrift::Batch} 88 | } 89 | 90 | def struct_fields; FIELDS; end 91 | 92 | def validate 93 | end 94 | 95 | ::Thrift::Struct.generate_accessors self 96 | end 97 | 98 | class EmitBatch_result 99 | include ::Thrift::Struct, ::Thrift::Struct_Union 100 | 101 | FIELDS = { 102 | 103 | } 104 | 105 | def struct_fields; FIELDS; end 106 | 107 | def validate 108 | end 109 | 110 | ::Thrift::Struct.generate_accessors self 111 | end 112 | 113 | end 114 | 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/agent/agent.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | require 'jaeger/thrift/agent/agent_types' 9 | 10 | module Jaeger 11 | module Thrift 12 | module Agent 13 | module Agent 14 | class Client 15 | include ::Thrift::Client 16 | 17 | def emitZipkinBatch(spans) 18 | send_emitZipkinBatch(spans) 19 | end 20 | 21 | def send_emitZipkinBatch(spans) 22 | send_oneway_message('emitZipkinBatch', EmitZipkinBatch_args, :spans => spans) 23 | end 24 | def emitBatch(batch) 25 | send_emitBatch(batch) 26 | end 27 | 28 | def send_emitBatch(batch) 29 | send_oneway_message('emitBatch', EmitBatch_args, :batch => batch) 30 | end 31 | end 32 | 33 | class Processor 34 | include ::Thrift::Processor 35 | 36 | def process_emitZipkinBatch(seqid, iprot, oprot) 37 | args = read_args(iprot, EmitZipkinBatch_args) 38 | @handler.emitZipkinBatch(args.spans) 39 | return 40 | end 41 | 42 | def process_emitBatch(seqid, iprot, oprot) 43 | args = read_args(iprot, EmitBatch_args) 44 | @handler.emitBatch(args.batch) 45 | return 46 | end 47 | 48 | end 49 | 50 | # HELPER FUNCTIONS AND STRUCTURES 51 | 52 | class EmitZipkinBatch_args 53 | include ::Thrift::Struct, ::Thrift::Struct_Union 54 | SPANS = 1 55 | 56 | FIELDS = { 57 | SPANS => {:type => ::Thrift::Types::LIST, :name => 'spans', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::Span}} 58 | } 59 | 60 | def struct_fields; FIELDS; end 61 | 62 | def validate 63 | end 64 | 65 | ::Thrift::Struct.generate_accessors self 66 | end 67 | 68 | class EmitZipkinBatch_result 69 | include ::Thrift::Struct, ::Thrift::Struct_Union 70 | 71 | FIELDS = { 72 | 73 | } 74 | 75 | def struct_fields; FIELDS; end 76 | 77 | def validate 78 | end 79 | 80 | ::Thrift::Struct.generate_accessors self 81 | end 82 | 83 | class EmitBatch_args 84 | include ::Thrift::Struct, ::Thrift::Struct_Union 85 | BATCH = 1 86 | 87 | FIELDS = { 88 | BATCH => {:type => ::Thrift::Types::STRUCT, :name => 'batch', :class => ::Jaeger::Thrift::Batch} 89 | } 90 | 91 | def struct_fields; FIELDS; end 92 | 93 | def validate 94 | end 95 | 96 | ::Thrift::Struct.generate_accessors self 97 | end 98 | 99 | class EmitBatch_result 100 | include ::Thrift::Struct, ::Thrift::Struct_Union 101 | 102 | FIELDS = { 103 | 104 | } 105 | 106 | def struct_fields; FIELDS; end 107 | 108 | def validate 109 | end 110 | 111 | ::Thrift::Struct.generate_accessors self 112 | end 113 | 114 | end 115 | 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/agent/agent_constants.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | require 'jaeger/thrift/agent/agent_types' 9 | 10 | module Jaeger 11 | module Thrift 12 | module Agent 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/agent/agent_types.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | require 'jaeger/thrift/jaeger_types' 9 | require 'jaeger/thrift/zipkin/zipkincore_types' 10 | 11 | 12 | module Jaeger 13 | module Thrift 14 | module Agent 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/agent_constants.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | require 'jaeger/thrift/agent_types' 9 | 10 | module Jaeger 11 | module Thrift 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/agent_types.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | require 'jaeger/thrift/jaeger_types' 9 | require 'jaeger/thrift/zipkin/zipkincore_types' 10 | 11 | 12 | module Jaeger 13 | module Thrift 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/collector.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | require 'jaeger/thrift/jaeger_types' 9 | 10 | module Jaeger 11 | module Thrift 12 | module Collector 13 | class Client 14 | include ::Thrift::Client 15 | 16 | def submitBatches(batches) 17 | send_submitBatches(batches) 18 | return recv_submitBatches() 19 | end 20 | 21 | def send_submitBatches(batches) 22 | send_message('submitBatches', SubmitBatches_args, :batches => batches) 23 | end 24 | 25 | def recv_submitBatches() 26 | result = receive_message(SubmitBatches_result) 27 | return result.success unless result.success.nil? 28 | raise ::Thrift::ApplicationException.new(::Thrift::ApplicationException::MISSING_RESULT, 'submitBatches failed: unknown result') 29 | end 30 | 31 | end 32 | 33 | class Processor 34 | include ::Thrift::Processor 35 | 36 | def process_submitBatches(seqid, iprot, oprot) 37 | args = read_args(iprot, SubmitBatches_args) 38 | result = SubmitBatches_result.new() 39 | result.success = @handler.submitBatches(args.batches) 40 | write_result(result, oprot, 'submitBatches', seqid) 41 | end 42 | 43 | end 44 | 45 | # HELPER FUNCTIONS AND STRUCTURES 46 | 47 | class SubmitBatches_args 48 | include ::Thrift::Struct, ::Thrift::Struct_Union 49 | BATCHES = 1 50 | 51 | FIELDS = { 52 | BATCHES => {:type => ::Thrift::Types::LIST, :name => 'batches', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Batch}} 53 | } 54 | 55 | def struct_fields; FIELDS; end 56 | 57 | def validate 58 | end 59 | 60 | ::Thrift::Struct.generate_accessors self 61 | end 62 | 63 | class SubmitBatches_result 64 | include ::Thrift::Struct, ::Thrift::Struct_Union 65 | SUCCESS = 0 66 | 67 | FIELDS = { 68 | SUCCESS => {:type => ::Thrift::Types::LIST, :name => 'success', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::BatchSubmitResponse}} 69 | } 70 | 71 | def struct_fields; FIELDS; end 72 | 73 | def validate 74 | end 75 | 76 | ::Thrift::Struct.generate_accessors self 77 | end 78 | 79 | end 80 | 81 | end 82 | end 83 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/jaeger_constants.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | require 'jaeger/thrift/jaeger_types' 9 | 10 | module Jaeger 11 | module Thrift 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/jaeger_types.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | 9 | module Jaeger 10 | module Thrift 11 | module TagType 12 | STRING = 0 13 | DOUBLE = 1 14 | BOOL = 2 15 | LONG = 3 16 | BINARY = 4 17 | VALUE_MAP = {0 => "STRING", 1 => "DOUBLE", 2 => "BOOL", 3 => "LONG", 4 => "BINARY"} 18 | VALID_VALUES = Set.new([STRING, DOUBLE, BOOL, LONG, BINARY]).freeze 19 | end 20 | 21 | module SpanRefType 22 | CHILD_OF = 0 23 | FOLLOWS_FROM = 1 24 | VALUE_MAP = {0 => "CHILD_OF", 1 => "FOLLOWS_FROM"} 25 | VALID_VALUES = Set.new([CHILD_OF, FOLLOWS_FROM]).freeze 26 | end 27 | 28 | class Tag 29 | include ::Thrift::Struct, ::Thrift::Struct_Union 30 | KEY = 1 31 | VTYPE = 2 32 | VSTR = 3 33 | VDOUBLE = 4 34 | VBOOL = 5 35 | VLONG = 6 36 | VBINARY = 7 37 | 38 | FIELDS = { 39 | KEY => {:type => ::Thrift::Types::STRING, :name => 'key'}, 40 | VTYPE => {:type => ::Thrift::Types::I32, :name => 'vType', :enum_class => ::Jaeger::Thrift::TagType}, 41 | VSTR => {:type => ::Thrift::Types::STRING, :name => 'vStr', :optional => true}, 42 | VDOUBLE => {:type => ::Thrift::Types::DOUBLE, :name => 'vDouble', :optional => true}, 43 | VBOOL => {:type => ::Thrift::Types::BOOL, :name => 'vBool', :optional => true}, 44 | VLONG => {:type => ::Thrift::Types::I64, :name => 'vLong', :optional => true}, 45 | VBINARY => {:type => ::Thrift::Types::STRING, :name => 'vBinary', :binary => true, :optional => true} 46 | } 47 | 48 | def struct_fields; FIELDS; end 49 | 50 | def validate 51 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field key is unset!') unless @key 52 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field vType is unset!') unless @vType 53 | unless @vType.nil? || ::Jaeger::Thrift::TagType::VALID_VALUES.include?(@vType) 54 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Invalid value of field vType!') 55 | end 56 | end 57 | 58 | ::Thrift::Struct.generate_accessors self 59 | end 60 | 61 | class Log 62 | include ::Thrift::Struct, ::Thrift::Struct_Union 63 | TIMESTAMP = 1 64 | LOG_FIELDS = 2 65 | 66 | FIELDS = { 67 | TIMESTAMP => {:type => ::Thrift::Types::I64, :name => 'timestamp'}, 68 | LOG_FIELDS => {:type => ::Thrift::Types::LIST, :name => 'fields', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Tag}} 69 | } 70 | 71 | def struct_fields; FIELDS; end 72 | 73 | def validate 74 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field timestamp is unset!') unless @timestamp 75 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field fields is unset!') unless @fields 76 | end 77 | 78 | ::Thrift::Struct.generate_accessors self 79 | end 80 | 81 | class SpanRef 82 | include ::Thrift::Struct, ::Thrift::Struct_Union 83 | REFTYPE = 1 84 | TRACEIDLOW = 2 85 | TRACEIDHIGH = 3 86 | SPANID = 4 87 | 88 | FIELDS = { 89 | REFTYPE => {:type => ::Thrift::Types::I32, :name => 'refType', :enum_class => ::Jaeger::Thrift::SpanRefType}, 90 | TRACEIDLOW => {:type => ::Thrift::Types::I64, :name => 'traceIdLow'}, 91 | TRACEIDHIGH => {:type => ::Thrift::Types::I64, :name => 'traceIdHigh'}, 92 | SPANID => {:type => ::Thrift::Types::I64, :name => 'spanId'} 93 | } 94 | 95 | def struct_fields; FIELDS; end 96 | 97 | def validate 98 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field refType is unset!') unless @refType 99 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field traceIdLow is unset!') unless @traceIdLow 100 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field traceIdHigh is unset!') unless @traceIdHigh 101 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field spanId is unset!') unless @spanId 102 | unless @refType.nil? || ::Jaeger::Thrift::SpanRefType::VALID_VALUES.include?(@refType) 103 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Invalid value of field refType!') 104 | end 105 | end 106 | 107 | ::Thrift::Struct.generate_accessors self 108 | end 109 | 110 | class Span 111 | include ::Thrift::Struct, ::Thrift::Struct_Union 112 | TRACEIDLOW = 1 113 | TRACEIDHIGH = 2 114 | SPANID = 3 115 | PARENTSPANID = 4 116 | OPERATIONNAME = 5 117 | REFERENCES = 6 118 | FLAGS = 7 119 | STARTTIME = 8 120 | DURATION = 9 121 | TAGS = 10 122 | LOGS = 11 123 | 124 | FIELDS = { 125 | TRACEIDLOW => {:type => ::Thrift::Types::I64, :name => 'traceIdLow'}, 126 | TRACEIDHIGH => {:type => ::Thrift::Types::I64, :name => 'traceIdHigh'}, 127 | SPANID => {:type => ::Thrift::Types::I64, :name => 'spanId'}, 128 | PARENTSPANID => {:type => ::Thrift::Types::I64, :name => 'parentSpanId'}, 129 | OPERATIONNAME => {:type => ::Thrift::Types::STRING, :name => 'operationName'}, 130 | REFERENCES => {:type => ::Thrift::Types::LIST, :name => 'references', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::SpanRef}, :optional => true}, 131 | FLAGS => {:type => ::Thrift::Types::I32, :name => 'flags'}, 132 | STARTTIME => {:type => ::Thrift::Types::I64, :name => 'startTime'}, 133 | DURATION => {:type => ::Thrift::Types::I64, :name => 'duration'}, 134 | TAGS => {:type => ::Thrift::Types::LIST, :name => 'tags', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Tag}, :optional => true}, 135 | LOGS => {:type => ::Thrift::Types::LIST, :name => 'logs', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Log}, :optional => true} 136 | } 137 | 138 | def struct_fields; FIELDS; end 139 | 140 | def validate 141 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field traceIdLow is unset!') unless @traceIdLow 142 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field traceIdHigh is unset!') unless @traceIdHigh 143 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field spanId is unset!') unless @spanId 144 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field parentSpanId is unset!') unless @parentSpanId 145 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field operationName is unset!') unless @operationName 146 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field flags is unset!') unless @flags 147 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field startTime is unset!') unless @startTime 148 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field duration is unset!') unless @duration 149 | end 150 | 151 | ::Thrift::Struct.generate_accessors self 152 | end 153 | 154 | class Process 155 | include ::Thrift::Struct, ::Thrift::Struct_Union 156 | SERVICENAME = 1 157 | TAGS = 2 158 | 159 | FIELDS = { 160 | SERVICENAME => {:type => ::Thrift::Types::STRING, :name => 'serviceName'}, 161 | TAGS => {:type => ::Thrift::Types::LIST, :name => 'tags', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Tag}, :optional => true} 162 | } 163 | 164 | def struct_fields; FIELDS; end 165 | 166 | def validate 167 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field serviceName is unset!') unless @serviceName 168 | end 169 | 170 | ::Thrift::Struct.generate_accessors self 171 | end 172 | 173 | class Batch 174 | include ::Thrift::Struct, ::Thrift::Struct_Union 175 | PROCESS = 1 176 | SPANS = 2 177 | 178 | FIELDS = { 179 | PROCESS => {:type => ::Thrift::Types::STRUCT, :name => 'process', :class => ::Jaeger::Thrift::Process}, 180 | SPANS => {:type => ::Thrift::Types::LIST, :name => 'spans', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Span}} 181 | } 182 | 183 | def struct_fields; FIELDS; end 184 | 185 | def validate 186 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field process is unset!') unless @process 187 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field spans is unset!') unless @spans 188 | end 189 | 190 | ::Thrift::Struct.generate_accessors self 191 | end 192 | 193 | class BatchSubmitResponse 194 | include ::Thrift::Struct, ::Thrift::Struct_Union 195 | OK = 1 196 | 197 | FIELDS = { 198 | OK => {:type => ::Thrift::Types::BOOL, :name => 'ok'} 199 | } 200 | 201 | def struct_fields; FIELDS; end 202 | 203 | def validate 204 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field ok is unset!') if @ok.nil? 205 | end 206 | 207 | ::Thrift::Struct.generate_accessors self 208 | end 209 | 210 | end 211 | end 212 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/zipkin/zipkin_collector.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | require 'jaeger/thrift/zipkin/zipkincore_types' 9 | 10 | module Jaeger 11 | module Thrift 12 | module Zipkin 13 | module ZipkinCollector 14 | class Client 15 | include ::Thrift::Client 16 | 17 | def submitZipkinBatch(spans) 18 | send_submitZipkinBatch(spans) 19 | return recv_submitZipkinBatch() 20 | end 21 | 22 | def send_submitZipkinBatch(spans) 23 | send_message('submitZipkinBatch', SubmitZipkinBatch_args, :spans => spans) 24 | end 25 | 26 | def recv_submitZipkinBatch() 27 | result = receive_message(SubmitZipkinBatch_result) 28 | return result.success unless result.success.nil? 29 | raise ::Thrift::ApplicationException.new(::Thrift::ApplicationException::MISSING_RESULT, 'submitZipkinBatch failed: unknown result') 30 | end 31 | 32 | end 33 | 34 | class Processor 35 | include ::Thrift::Processor 36 | 37 | def process_submitZipkinBatch(seqid, iprot, oprot) 38 | args = read_args(iprot, SubmitZipkinBatch_args) 39 | result = SubmitZipkinBatch_result.new() 40 | result.success = @handler.submitZipkinBatch(args.spans) 41 | write_result(result, oprot, 'submitZipkinBatch', seqid) 42 | end 43 | 44 | end 45 | 46 | # HELPER FUNCTIONS AND STRUCTURES 47 | 48 | class SubmitZipkinBatch_args 49 | include ::Thrift::Struct, ::Thrift::Struct_Union 50 | SPANS = 1 51 | 52 | FIELDS = { 53 | SPANS => {:type => ::Thrift::Types::LIST, :name => 'spans', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::Span}} 54 | } 55 | 56 | def struct_fields; FIELDS; end 57 | 58 | def validate 59 | end 60 | 61 | ::Thrift::Struct.generate_accessors self 62 | end 63 | 64 | class SubmitZipkinBatch_result 65 | include ::Thrift::Struct, ::Thrift::Struct_Union 66 | SUCCESS = 0 67 | 68 | FIELDS = { 69 | SUCCESS => {:type => ::Thrift::Types::LIST, :name => 'success', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::Response}} 70 | } 71 | 72 | def struct_fields; FIELDS; end 73 | 74 | def validate 75 | end 76 | 77 | ::Thrift::Struct.generate_accessors self 78 | end 79 | 80 | end 81 | 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/zipkin/zipkincore_constants.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | require 'jaeger/thrift/zipkin/zipkincore_types' 9 | 10 | module Jaeger 11 | module Thrift 12 | module Zipkin 13 | CLIENT_SEND = %q"cs" 14 | 15 | CLIENT_RECV = %q"cr" 16 | 17 | SERVER_SEND = %q"ss" 18 | 19 | SERVER_RECV = %q"sr" 20 | 21 | WIRE_SEND = %q"ws" 22 | 23 | WIRE_RECV = %q"wr" 24 | 25 | CLIENT_SEND_FRAGMENT = %q"csf" 26 | 27 | CLIENT_RECV_FRAGMENT = %q"crf" 28 | 29 | SERVER_SEND_FRAGMENT = %q"ssf" 30 | 31 | SERVER_RECV_FRAGMENT = %q"srf" 32 | 33 | LOCAL_COMPONENT = %q"lc" 34 | 35 | CLIENT_ADDR = %q"ca" 36 | 37 | SERVER_ADDR = %q"sa" 38 | 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /thrift/gen-rb/jaeger/thrift/zipkin/zipkincore_types.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Autogenerated by Thrift Compiler (0.10.0) 3 | # 4 | # DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING 5 | # 6 | 7 | require 'thrift' 8 | 9 | module Jaeger 10 | module Thrift 11 | module Zipkin 12 | module AnnotationType 13 | BOOL = 0 14 | BYTES = 1 15 | I16 = 2 16 | I32 = 3 17 | I64 = 4 18 | DOUBLE = 5 19 | STRING = 6 20 | VALUE_MAP = {0 => "BOOL", 1 => "BYTES", 2 => "I16", 3 => "I32", 4 => "I64", 5 => "DOUBLE", 6 => "STRING"} 21 | VALID_VALUES = Set.new([BOOL, BYTES, I16, I32, I64, DOUBLE, STRING]).freeze 22 | end 23 | 24 | # Indicates the network context of a service recording an annotation with two 25 | # exceptions. 26 | # 27 | # When a BinaryAnnotation, and key is CLIENT_ADDR or SERVER_ADDR, 28 | # the endpoint indicates the source or destination of an RPC. This exception 29 | # allows zipkin to display network context of uninstrumented services, or 30 | # clients such as web browsers. 31 | class Endpoint 32 | include ::Thrift::Struct, ::Thrift::Struct_Union 33 | IPV4 = 1 34 | PORT = 2 35 | SERVICE_NAME = 3 36 | 37 | FIELDS = { 38 | # IPv4 host address packed into 4 bytes. 39 | # 40 | # Ex for the ip 1.2.3.4, it would be (1 << 24) | (2 << 16) | (3 << 8) | 4 41 | IPV4 => {:type => ::Thrift::Types::I32, :name => 'ipv4'}, 42 | # IPv4 port 43 | # 44 | # Note: this is to be treated as an unsigned integer, so watch for negatives. 45 | # 46 | # Conventionally, when the port isn't known, port = 0. 47 | PORT => {:type => ::Thrift::Types::I16, :name => 'port'}, 48 | # Service name in lowercase, such as "memcache" or "zipkin-web" 49 | # 50 | # Conventionally, when the service name isn't known, service_name = "unknown". 51 | SERVICE_NAME => {:type => ::Thrift::Types::STRING, :name => 'service_name'} 52 | } 53 | 54 | def struct_fields; FIELDS; end 55 | 56 | def validate 57 | end 58 | 59 | ::Thrift::Struct.generate_accessors self 60 | end 61 | 62 | # An annotation is similar to a log statement. It includes a host field which 63 | # allows these events to be attributed properly, and also aggregatable. 64 | class Annotation 65 | include ::Thrift::Struct, ::Thrift::Struct_Union 66 | TIMESTAMP = 1 67 | VALUE = 2 68 | HOST = 3 69 | 70 | FIELDS = { 71 | # Microseconds from epoch. 72 | # 73 | # This value should use the most precise value possible. For example, 74 | # gettimeofday or syncing nanoTime against a tick of currentTimeMillis. 75 | TIMESTAMP => {:type => ::Thrift::Types::I64, :name => 'timestamp'}, 76 | VALUE => {:type => ::Thrift::Types::STRING, :name => 'value'}, 77 | # Always the host that recorded the event. By specifying the host you allow 78 | # rollup of all events (such as client requests to a service) by IP address. 79 | HOST => {:type => ::Thrift::Types::STRUCT, :name => 'host', :class => ::Jaeger::Thrift::Zipkin::Endpoint, :optional => true} 80 | } 81 | 82 | def struct_fields; FIELDS; end 83 | 84 | def validate 85 | end 86 | 87 | ::Thrift::Struct.generate_accessors self 88 | end 89 | 90 | # Binary annotations are tags applied to a Span to give it context. For 91 | # example, a binary annotation of "http.uri" could the path to a resource in a 92 | # RPC call. 93 | # 94 | # Binary annotations of type STRING are always queryable, though more a 95 | # historical implementation detail than a structural concern. 96 | # 97 | # Binary annotations can repeat, and vary on the host. Similar to Annotation, 98 | # the host indicates who logged the event. This allows you to tell the 99 | # difference between the client and server side of the same key. For example, 100 | # the key "http.uri" might be different on the client and server side due to 101 | # rewriting, like "/api/v1/myresource" vs "/myresource. Via the host field, 102 | # you can see the different points of view, which often help in debugging. 103 | class BinaryAnnotation 104 | include ::Thrift::Struct, ::Thrift::Struct_Union 105 | KEY = 1 106 | VALUE = 2 107 | ANNOTATION_TYPE = 3 108 | HOST = 4 109 | 110 | FIELDS = { 111 | KEY => {:type => ::Thrift::Types::STRING, :name => 'key'}, 112 | VALUE => {:type => ::Thrift::Types::STRING, :name => 'value', :binary => true}, 113 | ANNOTATION_TYPE => {:type => ::Thrift::Types::I32, :name => 'annotation_type', :enum_class => ::Jaeger::Thrift::Zipkin::AnnotationType}, 114 | # The host that recorded tag, which allows you to differentiate between 115 | # multiple tags with the same key. There are two exceptions to this. 116 | # 117 | # When the key is CLIENT_ADDR or SERVER_ADDR, host indicates the source or 118 | # destination of an RPC. This exception allows zipkin to display network 119 | # context of uninstrumented services, or clients such as web browsers. 120 | HOST => {:type => ::Thrift::Types::STRUCT, :name => 'host', :class => ::Jaeger::Thrift::Zipkin::Endpoint, :optional => true} 121 | } 122 | 123 | def struct_fields; FIELDS; end 124 | 125 | def validate 126 | unless @annotation_type.nil? || ::Jaeger::Thrift::Zipkin::AnnotationType::VALID_VALUES.include?(@annotation_type) 127 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Invalid value of field annotation_type!') 128 | end 129 | end 130 | 131 | ::Thrift::Struct.generate_accessors self 132 | end 133 | 134 | # A trace is a series of spans (often RPC calls) which form a latency tree. 135 | # 136 | # The root span is where trace_id = id and parent_id = Nil. The root span is 137 | # usually the longest interval in the trace, starting with a SERVER_RECV 138 | # annotation and ending with a SERVER_SEND. 139 | class Span 140 | include ::Thrift::Struct, ::Thrift::Struct_Union 141 | TRACE_ID = 1 142 | NAME = 3 143 | ID = 4 144 | PARENT_ID = 5 145 | ANNOTATIONS = 6 146 | BINARY_ANNOTATIONS = 8 147 | DEBUG = 9 148 | TIMESTAMP = 10 149 | DURATION = 11 150 | 151 | FIELDS = { 152 | TRACE_ID => {:type => ::Thrift::Types::I64, :name => 'trace_id'}, 153 | # Span name in lowercase, rpc method for example 154 | # 155 | # Conventionally, when the span name isn't known, name = "unknown". 156 | NAME => {:type => ::Thrift::Types::STRING, :name => 'name'}, 157 | ID => {:type => ::Thrift::Types::I64, :name => 'id'}, 158 | PARENT_ID => {:type => ::Thrift::Types::I64, :name => 'parent_id', :optional => true}, 159 | ANNOTATIONS => {:type => ::Thrift::Types::LIST, :name => 'annotations', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::Annotation}}, 160 | BINARY_ANNOTATIONS => {:type => ::Thrift::Types::LIST, :name => 'binary_annotations', :element => {:type => ::Thrift::Types::STRUCT, :class => ::Jaeger::Thrift::Zipkin::BinaryAnnotation}}, 161 | DEBUG => {:type => ::Thrift::Types::BOOL, :name => 'debug', :default => false, :optional => true}, 162 | # Microseconds from epoch of the creation of this span. 163 | # 164 | # This value should be set directly by instrumentation, using the most 165 | # precise value possible. For example, gettimeofday or syncing nanoTime 166 | # against a tick of currentTimeMillis. 167 | # 168 | # For compatibilty with instrumentation that precede this field, collectors 169 | # or span stores can derive this via Annotation.timestamp. 170 | # For example, SERVER_RECV.timestamp or CLIENT_SEND.timestamp. 171 | # 172 | # This field is optional for compatibility with old data: first-party span 173 | # stores are expected to support this at time of introduction. 174 | TIMESTAMP => {:type => ::Thrift::Types::I64, :name => 'timestamp', :optional => true}, 175 | # Measurement of duration in microseconds, used to support queries. 176 | # 177 | # This value should be set directly, where possible. Doing so encourages 178 | # precise measurement decoupled from problems of clocks, such as skew or NTP 179 | # updates causing time to move backwards. 180 | # 181 | # For compatibilty with instrumentation that precede this field, collectors 182 | # or span stores can derive this by subtracting Annotation.timestamp. 183 | # For example, SERVER_SEND.timestamp - SERVER_RECV.timestamp. 184 | # 185 | # If this field is persisted as unset, zipkin will continue to work, except 186 | # duration query support will be implementation-specific. Similarly, setting 187 | # this field non-atomically is implementation-specific. 188 | # 189 | # This field is i64 vs i32 to support spans longer than 35 minutes. 190 | DURATION => {:type => ::Thrift::Types::I64, :name => 'duration', :optional => true} 191 | } 192 | 193 | def struct_fields; FIELDS; end 194 | 195 | def validate 196 | end 197 | 198 | ::Thrift::Struct.generate_accessors self 199 | end 200 | 201 | class Response 202 | include ::Thrift::Struct, ::Thrift::Struct_Union 203 | OK = 1 204 | 205 | FIELDS = { 206 | OK => {:type => ::Thrift::Types::BOOL, :name => 'ok'} 207 | } 208 | 209 | def struct_fields; FIELDS; end 210 | 211 | def validate 212 | raise ::Thrift::ProtocolException.new(::Thrift::ProtocolException::UNKNOWN, 'Required field ok is unset!') if @ok.nil? 213 | end 214 | 215 | ::Thrift::Struct.generate_accessors self 216 | end 217 | 218 | end 219 | end 220 | end 221 | -------------------------------------------------------------------------------- /thrift/jaeger.thrift: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Uber Technologies, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | # THE SOFTWARE. 20 | 21 | namespace java com.uber.jaeger.thriftjava 22 | namespace rb Jaeger.Thrift 23 | 24 | # TagType denotes the type of a Tag's value. 25 | enum TagType { STRING, DOUBLE, BOOL, LONG, BINARY } 26 | 27 | # Tag is a basic strongly typed key/value pair. It has been flattened to reduce the use of pointers in golang 28 | struct Tag { 29 | 1: required string key 30 | 2: required TagType vType 31 | 3: optional string vStr 32 | 4: optional double vDouble 33 | 5: optional bool vBool 34 | 6: optional i64 vLong 35 | 7: optional binary vBinary 36 | } 37 | 38 | # Log is a timed even with an arbitrary set of tags. 39 | struct Log { 40 | 1: required i64 timestamp 41 | 2: required list fields 42 | } 43 | 44 | enum SpanRefType { CHILD_OF, FOLLOWS_FROM } 45 | 46 | # SpanRef describes causal relationship of the current span to another span (e.g. 'child-of') 47 | struct SpanRef { 48 | 1: required SpanRefType refType 49 | 2: required i64 traceIdLow 50 | 3: required i64 traceIdHigh 51 | 4: required i64 spanId 52 | } 53 | 54 | # Span represents a named unit of work performed by a service. 55 | struct Span { 56 | 1: required i64 traceIdLow # the least significant 64 bits of a traceID 57 | 2: required i64 traceIdHigh # the most significant 64 bits of a traceID; 0 when only 64bit IDs are used 58 | 3: required i64 spanId # unique span id (only unique within a given trace) 59 | 4: required i64 parentSpanId # since nearly all spans will have parents spans, CHILD_OF refs do not have to be explicit 60 | 5: required string operationName 61 | 6: optional list references # causal references to other spans 62 | 7: required i32 flags # tbd 63 | 8: required i64 startTime 64 | 9: required i64 duration 65 | 10: optional list tags 66 | 11: optional list logs 67 | } 68 | 69 | # Process describes the traced process/service that emits spans. 70 | struct Process { 71 | 1: required string serviceName 72 | 2: optional list tags 73 | } 74 | 75 | # Batch is a collection of spans reported out of process. 76 | struct Batch { 77 | 1: required Process process 78 | 2: required list spans 79 | } 80 | 81 | # BatchSubmitResponse is the response on submitting a batch. 82 | struct BatchSubmitResponse { 83 | 1: required bool ok # The Collector's client is expected to only log (or emit a counter) when not ok equals false 84 | } 85 | 86 | service Collector { 87 | list submitBatches(1: list batches) 88 | } 89 | -------------------------------------------------------------------------------- /thrift/zipkincore.thrift: -------------------------------------------------------------------------------- 1 | # Copyright 2012 Twitter Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | namespace java com.twitter.zipkin.thriftjava 15 | #@namespace scala com.twitter.zipkin.thriftscala 16 | namespace rb Jaeger.Thrift.Zipkin 17 | 18 | #************** Annotation.value ************** 19 | /** 20 | * The client sent ("cs") a request to a server. There is only one send per 21 | * span. For example, if there's a transport error, each attempt can be logged 22 | * as a WIRE_SEND annotation. 23 | * 24 | * If chunking is involved, each chunk could be logged as a separate 25 | * CLIENT_SEND_FRAGMENT in the same span. 26 | * 27 | * Annotation.host is not the server. It is the host which logged the send 28 | * event, almost always the client. When logging CLIENT_SEND, instrumentation 29 | * should also log the SERVER_ADDR. 30 | */ 31 | const string CLIENT_SEND = "cs" 32 | /** 33 | * The client received ("cr") a response from a server. There is only one 34 | * receive per span. For example, if duplicate responses were received, each 35 | * can be logged as a WIRE_RECV annotation. 36 | * 37 | * If chunking is involved, each chunk could be logged as a separate 38 | * CLIENT_RECV_FRAGMENT in the same span. 39 | * 40 | * Annotation.host is not the server. It is the host which logged the receive 41 | * event, almost always the client. The actual endpoint of the server is 42 | * recorded separately as SERVER_ADDR when CLIENT_SEND is logged. 43 | */ 44 | const string CLIENT_RECV = "cr" 45 | /** 46 | * The server sent ("ss") a response to a client. There is only one response 47 | * per span. If there's a transport error, each attempt can be logged as a 48 | * WIRE_SEND annotation. 49 | * 50 | * Typically, a trace ends with a server send, so the last timestamp of a trace 51 | * is often the timestamp of the root span's server send. 52 | * 53 | * If chunking is involved, each chunk could be logged as a separate 54 | * SERVER_SEND_FRAGMENT in the same span. 55 | * 56 | * Annotation.host is not the client. It is the host which logged the send 57 | * event, almost always the server. The actual endpoint of the client is 58 | * recorded separately as CLIENT_ADDR when SERVER_RECV is logged. 59 | */ 60 | const string SERVER_SEND = "ss" 61 | /** 62 | * The server received ("sr") a request from a client. There is only one 63 | * request per span. For example, if duplicate responses were received, each 64 | * can be logged as a WIRE_RECV annotation. 65 | * 66 | * Typically, a trace starts with a server receive, so the first timestamp of a 67 | * trace is often the timestamp of the root span's server receive. 68 | * 69 | * If chunking is involved, each chunk could be logged as a separate 70 | * SERVER_RECV_FRAGMENT in the same span. 71 | * 72 | * Annotation.host is not the client. It is the host which logged the receive 73 | * event, almost always the server. When logging SERVER_RECV, instrumentation 74 | * should also log the CLIENT_ADDR. 75 | */ 76 | const string SERVER_RECV = "sr" 77 | /** 78 | * Optionally logs an attempt to send a message on the wire. Multiple wire send 79 | * events could indicate network retries. A lag between client or server send 80 | * and wire send might indicate queuing or processing delay. 81 | */ 82 | const string WIRE_SEND = "ws" 83 | /** 84 | * Optionally logs an attempt to receive a message from the wire. Multiple wire 85 | * receive events could indicate network retries. A lag between wire receive 86 | * and client or server receive might indicate queuing or processing delay. 87 | */ 88 | const string WIRE_RECV = "wr" 89 | /** 90 | * Optionally logs progress of a (CLIENT_SEND, WIRE_SEND). For example, this 91 | * could be one chunk in a chunked request. 92 | */ 93 | const string CLIENT_SEND_FRAGMENT = "csf" 94 | /** 95 | * Optionally logs progress of a (CLIENT_RECV, WIRE_RECV). For example, this 96 | * could be one chunk in a chunked response. 97 | */ 98 | const string CLIENT_RECV_FRAGMENT = "crf" 99 | /** 100 | * Optionally logs progress of a (SERVER_SEND, WIRE_SEND). For example, this 101 | * could be one chunk in a chunked response. 102 | */ 103 | const string SERVER_SEND_FRAGMENT = "ssf" 104 | /** 105 | * Optionally logs progress of a (SERVER_RECV, WIRE_RECV). For example, this 106 | * could be one chunk in a chunked request. 107 | */ 108 | const string SERVER_RECV_FRAGMENT = "srf" 109 | 110 | #***** BinaryAnnotation.key ****** 111 | /** 112 | * The value of "lc" is the component or namespace of a local span. 113 | * 114 | * BinaryAnnotation.host adds service context needed to support queries. 115 | * 116 | * Local Component("lc") supports three key features: flagging, query by 117 | * service and filtering Span.name by namespace. 118 | * 119 | * While structurally the same, local spans are fundamentally different than 120 | * RPC spans in how they should be interpreted. For example, zipkin v1 tools 121 | * center on RPC latency and service graphs. Root local-spans are neither 122 | * indicative of critical path RPC latency, nor have impact on the shape of a 123 | * service graph. By flagging with "lc", tools can special-case local spans. 124 | * 125 | * Zipkin v1 Spans are unqueryable unless they can be indexed by service name. 126 | * The only path to a service name is by (Binary)?Annotation.host.serviceName. 127 | * By logging "lc", a local span can be queried even if no other annotations 128 | * are logged. 129 | * 130 | * The value of "lc" is the namespace of Span.name. For example, it might be 131 | * "finatra2", for a span named "bootstrap". "lc" allows you to resolves 132 | * conflicts for the same Span.name, for example "finatra/bootstrap" vs 133 | * "finch/bootstrap". Using local component, you'd search for spans named 134 | * "bootstrap" where "lc=finch" 135 | */ 136 | const string LOCAL_COMPONENT = "lc" 137 | 138 | #***** BinaryAnnotation.key where value = [1] and annotation_type = BOOL ****** 139 | /** 140 | * Indicates a client address ("ca") in a span. Most likely, there's only one. 141 | * Multiple addresses are possible when a client changes its ip or port within 142 | * a span. 143 | */ 144 | const string CLIENT_ADDR = "ca" 145 | /** 146 | * Indicates a server address ("sa") in a span. Most likely, there's only one. 147 | * Multiple addresses are possible when a client is redirected, or fails to a 148 | * different server ip or port. 149 | */ 150 | const string SERVER_ADDR = "sa" 151 | 152 | /** 153 | * Indicates the network context of a service recording an annotation with two 154 | * exceptions. 155 | * 156 | * When a BinaryAnnotation, and key is CLIENT_ADDR or SERVER_ADDR, 157 | * the endpoint indicates the source or destination of an RPC. This exception 158 | * allows zipkin to display network context of uninstrumented services, or 159 | * clients such as web browsers. 160 | */ 161 | struct Endpoint { 162 | /** 163 | * IPv4 host address packed into 4 bytes. 164 | * 165 | * Ex for the ip 1.2.3.4, it would be (1 << 24) | (2 << 16) | (3 << 8) | 4 166 | */ 167 | 1: i32 ipv4 168 | /** 169 | * IPv4 port 170 | * 171 | * Note: this is to be treated as an unsigned integer, so watch for negatives. 172 | * 173 | * Conventionally, when the port isn't known, port = 0. 174 | */ 175 | 2: i16 port 176 | /** 177 | * Service name in lowercase, such as "memcache" or "zipkin-web" 178 | * 179 | * Conventionally, when the service name isn't known, service_name = "unknown". 180 | */ 181 | 3: string service_name 182 | } 183 | 184 | /** 185 | * An annotation is similar to a log statement. It includes a host field which 186 | * allows these events to be attributed properly, and also aggregatable. 187 | */ 188 | struct Annotation { 189 | /** 190 | * Microseconds from epoch. 191 | * 192 | * This value should use the most precise value possible. For example, 193 | * gettimeofday or syncing nanoTime against a tick of currentTimeMillis. 194 | */ 195 | 1: i64 timestamp 196 | 2: string value // what happened at the timestamp? 197 | /** 198 | * Always the host that recorded the event. By specifying the host you allow 199 | * rollup of all events (such as client requests to a service) by IP address. 200 | */ 201 | 3: optional Endpoint host 202 | // don't reuse 4: optional i32 OBSOLETE_duration // how long did the operation take? microseconds 203 | } 204 | 205 | enum AnnotationType { BOOL, BYTES, I16, I32, I64, DOUBLE, STRING } 206 | 207 | /** 208 | * Binary annotations are tags applied to a Span to give it context. For 209 | * example, a binary annotation of "http.uri" could the path to a resource in a 210 | * RPC call. 211 | * 212 | * Binary annotations of type STRING are always queryable, though more a 213 | * historical implementation detail than a structural concern. 214 | * 215 | * Binary annotations can repeat, and vary on the host. Similar to Annotation, 216 | * the host indicates who logged the event. This allows you to tell the 217 | * difference between the client and server side of the same key. For example, 218 | * the key "http.uri" might be different on the client and server side due to 219 | * rewriting, like "/api/v1/myresource" vs "/myresource. Via the host field, 220 | * you can see the different points of view, which often help in debugging. 221 | */ 222 | struct BinaryAnnotation { 223 | 1: string key, 224 | 2: binary value, 225 | 3: AnnotationType annotation_type, 226 | /** 227 | * The host that recorded tag, which allows you to differentiate between 228 | * multiple tags with the same key. There are two exceptions to this. 229 | * 230 | * When the key is CLIENT_ADDR or SERVER_ADDR, host indicates the source or 231 | * destination of an RPC. This exception allows zipkin to display network 232 | * context of uninstrumented services, or clients such as web browsers. 233 | */ 234 | 4: optional Endpoint host 235 | } 236 | 237 | /** 238 | * A trace is a series of spans (often RPC calls) which form a latency tree. 239 | * 240 | * The root span is where trace_id = id and parent_id = Nil. The root span is 241 | * usually the longest interval in the trace, starting with a SERVER_RECV 242 | * annotation and ending with a SERVER_SEND. 243 | */ 244 | struct Span { 245 | 1: i64 trace_id # unique trace id, use for all spans in trace 246 | /** 247 | * Span name in lowercase, rpc method for example 248 | * 249 | * Conventionally, when the span name isn't known, name = "unknown". 250 | */ 251 | 3: string name, 252 | 4: i64 id, # unique span id, only used for this span 253 | 5: optional i64 parent_id, # parent span id 254 | 6: list annotations, # all annotations/events that occured, sorted by timestamp 255 | 8: list binary_annotations # any binary annotations 256 | 9: optional bool debug = 0 # if true, we DEMAND that this span passes all samplers 257 | /** 258 | * Microseconds from epoch of the creation of this span. 259 | * 260 | * This value should be set directly by instrumentation, using the most 261 | * precise value possible. For example, gettimeofday or syncing nanoTime 262 | * against a tick of currentTimeMillis. 263 | * 264 | * For compatibilty with instrumentation that precede this field, collectors 265 | * or span stores can derive this via Annotation.timestamp. 266 | * For example, SERVER_RECV.timestamp or CLIENT_SEND.timestamp. 267 | * 268 | * This field is optional for compatibility with old data: first-party span 269 | * stores are expected to support this at time of introduction. 270 | */ 271 | 10: optional i64 timestamp, 272 | /** 273 | * Measurement of duration in microseconds, used to support queries. 274 | * 275 | * This value should be set directly, where possible. Doing so encourages 276 | * precise measurement decoupled from problems of clocks, such as skew or NTP 277 | * updates causing time to move backwards. 278 | * 279 | * For compatibilty with instrumentation that precede this field, collectors 280 | * or span stores can derive this by subtracting Annotation.timestamp. 281 | * For example, SERVER_SEND.timestamp - SERVER_RECV.timestamp. 282 | * 283 | * If this field is persisted as unset, zipkin will continue to work, except 284 | * duration query support will be implementation-specific. Similarly, setting 285 | * this field non-atomically is implementation-specific. 286 | * 287 | * This field is i64 vs i32 to support spans longer than 35 minutes. 288 | */ 289 | 11: optional i64 duration 290 | } 291 | 292 | # define TChannel service 293 | 294 | struct Response { 295 | 1: required bool ok 296 | } 297 | 298 | service ZipkinCollector { 299 | list submitZipkinBatch(1: list spans) 300 | } 301 | --------------------------------------------------------------------------------