fair-ddtrace 0.8.2.a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/.env +11 -0
  3. data/.gitignore +59 -0
  4. data/.rubocop.yml +61 -0
  5. data/.yardopts +5 -0
  6. data/Appraisals +136 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +24 -0
  9. data/README.md +156 -0
  10. data/Rakefile +176 -0
  11. data/circle.yml +61 -0
  12. data/ddtrace.gemspec +44 -0
  13. data/docker-compose.yml +42 -0
  14. data/docs/GettingStarted.md +735 -0
  15. data/gemfiles/contrib.gemfile +16 -0
  16. data/gemfiles/contrib_old.gemfile +15 -0
  17. data/gemfiles/rails30_postgres.gemfile +10 -0
  18. data/gemfiles/rails30_postgres_sidekiq.gemfile +11 -0
  19. data/gemfiles/rails32_mysql2.gemfile +11 -0
  20. data/gemfiles/rails32_postgres.gemfile +10 -0
  21. data/gemfiles/rails32_postgres_redis.gemfile +11 -0
  22. data/gemfiles/rails32_postgres_sidekiq.gemfile +11 -0
  23. data/gemfiles/rails4_mysql2.gemfile +9 -0
  24. data/gemfiles/rails4_postgres.gemfile +9 -0
  25. data/gemfiles/rails4_postgres_redis.gemfile +10 -0
  26. data/gemfiles/rails4_postgres_sidekiq.gemfile +11 -0
  27. data/gemfiles/rails5_mysql2.gemfile +8 -0
  28. data/gemfiles/rails5_postgres.gemfile +8 -0
  29. data/gemfiles/rails5_postgres_redis.gemfile +9 -0
  30. data/gemfiles/rails5_postgres_sidekiq.gemfile +10 -0
  31. data/lib/ddtrace.rb +73 -0
  32. data/lib/ddtrace/buffer.rb +52 -0
  33. data/lib/ddtrace/context.rb +145 -0
  34. data/lib/ddtrace/contrib/active_record/patcher.rb +94 -0
  35. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +108 -0
  36. data/lib/ddtrace/contrib/elasticsearch/quantize.rb +22 -0
  37. data/lib/ddtrace/contrib/grape/endpoint.rb +164 -0
  38. data/lib/ddtrace/contrib/grape/patcher.rb +73 -0
  39. data/lib/ddtrace/contrib/http/patcher.rb +156 -0
  40. data/lib/ddtrace/contrib/rack/middlewares.rb +150 -0
  41. data/lib/ddtrace/contrib/rails/action_controller.rb +81 -0
  42. data/lib/ddtrace/contrib/rails/action_view.rb +110 -0
  43. data/lib/ddtrace/contrib/rails/active_record.rb +56 -0
  44. data/lib/ddtrace/contrib/rails/active_support.rb +113 -0
  45. data/lib/ddtrace/contrib/rails/core_extensions.rb +137 -0
  46. data/lib/ddtrace/contrib/rails/framework.rb +171 -0
  47. data/lib/ddtrace/contrib/rails/middlewares.rb +32 -0
  48. data/lib/ddtrace/contrib/rails/utils.rb +43 -0
  49. data/lib/ddtrace/contrib/redis/patcher.rb +118 -0
  50. data/lib/ddtrace/contrib/redis/quantize.rb +30 -0
  51. data/lib/ddtrace/contrib/redis/tags.rb +19 -0
  52. data/lib/ddtrace/contrib/sidekiq/tracer.rb +103 -0
  53. data/lib/ddtrace/contrib/sinatra/tracer.rb +169 -0
  54. data/lib/ddtrace/distributed.rb +38 -0
  55. data/lib/ddtrace/encoding.rb +65 -0
  56. data/lib/ddtrace/error.rb +37 -0
  57. data/lib/ddtrace/ext/app_types.rb +10 -0
  58. data/lib/ddtrace/ext/cache.rb +7 -0
  59. data/lib/ddtrace/ext/distributed.rb +10 -0
  60. data/lib/ddtrace/ext/errors.rb +10 -0
  61. data/lib/ddtrace/ext/http.rb +11 -0
  62. data/lib/ddtrace/ext/net.rb +8 -0
  63. data/lib/ddtrace/ext/redis.rb +11 -0
  64. data/lib/ddtrace/ext/sql.rb +8 -0
  65. data/lib/ddtrace/logger.rb +39 -0
  66. data/lib/ddtrace/monkey.rb +84 -0
  67. data/lib/ddtrace/pin.rb +63 -0
  68. data/lib/ddtrace/provider.rb +21 -0
  69. data/lib/ddtrace/sampler.rb +49 -0
  70. data/lib/ddtrace/span.rb +222 -0
  71. data/lib/ddtrace/tracer.rb +310 -0
  72. data/lib/ddtrace/transport.rb +162 -0
  73. data/lib/ddtrace/utils.rb +16 -0
  74. data/lib/ddtrace/version.rb +9 -0
  75. data/lib/ddtrace/workers.rb +108 -0
  76. data/lib/ddtrace/writer.rb +118 -0
  77. metadata +208 -0
@@ -0,0 +1,38 @@
1
+ require 'ddtrace/span'
2
+
3
+ module Datadog
4
+ # Common code related to distributed tracing.
5
+ module Distributed
6
+ module_function
7
+
8
+ # Parses a trace_id and a parent_id, typically sent as headers in
9
+ # a distributed tracing context, and returns a couple of trace_id,parent_id
10
+ # which are garanteed to be both non-zero. This does not 100% ensure they
11
+ # are valid (after all, the caller could mess up data) but at least it
12
+ # sorts out most common errors, such as syntax, nil values, etc.
13
+ # Both headers must be set, else nil values are returned, for both.
14
+ # Reports problem on debug log.
15
+ def parse_trace_headers(trace_id_header, parent_id_header)
16
+ return nil, nil if trace_id_header.nil? || parent_id_header.nil?
17
+ trace_id = trace_id_header.to_i
18
+ parent_id = parent_id_header.to_i
19
+ if trace_id.zero?
20
+ Datadog::Tracer.log.debug("invalid trace_id header: #{trace_id_header}")
21
+ return nil, nil
22
+ end
23
+ if parent_id.zero?
24
+ Datadog::Tracer.log.debug("invalid parent_id header: #{parent_id_header}")
25
+ return nil, nil
26
+ end
27
+ if trace_id < 0 || trace_id >= Datadog::Span::MAX_ID
28
+ Datadog::Tracer.log.debug("trace_id out of range: #{trace_id_header}")
29
+ return nil, nil
30
+ end
31
+ if parent_id < 0 || parent_id >= Datadog::Span::MAX_ID
32
+ Datadog::Tracer.log.debug("parent_id out of range: #{parent_id_header}")
33
+ return nil, nil
34
+ end
35
+ [trace_id, parent_id]
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,65 @@
1
+ require 'json'
2
+ require 'msgpack'
3
+
4
+ module Datadog
5
+ # Encoding module that encodes data for the AgentTransport
6
+ module Encoding
7
+ # Encoder interface that provides the logic to encode traces and service
8
+ class Encoder
9
+ attr_reader :content_type
10
+
11
+ # When extending the ``Encoder`` class, ``content_type`` must be set because
12
+ # they're used by the HTTPTransport so that it should not need to know what is
13
+ # the right header to suggest the decoding format to the agent
14
+ def initialize
15
+ @content_type = ''
16
+ end
17
+
18
+ # Encodes a list of traces, expecting a list of items where each items
19
+ # is a list of spans. Before dump the string in a serialized format all
20
+ # traces are normalized. The traces nesting is not changed.
21
+ def encode_traces(traces)
22
+ to_send = []
23
+ traces.each do |trace|
24
+ to_send << trace.map(&:to_hash)
25
+ end
26
+ encode(to_send)
27
+ end
28
+
29
+ # Encodes services hash
30
+ def encode_services(services)
31
+ encode(services)
32
+ end
33
+
34
+ # Defines the underlying format used during traces or services encoding.
35
+ # This method must be implemented and should only be used by the internal functions.
36
+ def encode(_)
37
+ raise NotImplementedError
38
+ end
39
+ end
40
+
41
+ # Encoder for the JSON format
42
+ class JSONEncoder < Encoder
43
+ def initialize
44
+ Datadog::Tracer.log.debug('using JSON encoder; application performance may be degraded')
45
+ @content_type = 'application/json'
46
+ end
47
+
48
+ def encode(obj)
49
+ JSON.dump(obj)
50
+ end
51
+ end
52
+
53
+ # Encoder for the Msgpack format
54
+ class MsgpackEncoder < Encoder
55
+ def initialize
56
+ Datadog::Tracer.log.debug('using Msgpack encoder')
57
+ @content_type = 'application/msgpack'
58
+ end
59
+
60
+ def encode(obj)
61
+ MessagePack.pack(obj)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,37 @@
1
+ # Datadog global namespace
2
+ module Datadog
3
+ # Error is a value-object responsible for sanitizing/encapsulating error data
4
+ class Error
5
+ attr_reader :type, :message, :backtrace
6
+
7
+ def self.build_from(value)
8
+ case value
9
+ when Error then value
10
+ when Array then new(*value)
11
+ when Exception then new(value.class, value.message, value.backtrace)
12
+ when ContainsMessage then new(value.class, value.message)
13
+ else BlankError
14
+ end
15
+ end
16
+
17
+ def initialize(type = nil, message = nil, backtrace = nil)
18
+ backtrace = Array(backtrace).join("\n")
19
+ @type = sanitize(type)
20
+ @message = sanitize(message)
21
+ @backtrace = sanitize(backtrace)
22
+ end
23
+
24
+ private
25
+
26
+ def sanitize(value)
27
+ value = value.to_s
28
+
29
+ return value if value.encoding == ::Encoding::UTF_8
30
+
31
+ value.encode(::Encoding::UTF_8)
32
+ end
33
+
34
+ BlankError = Error.new
35
+ ContainsMessage = ->(v) { v.respond_to?(:message) }
36
+ end
37
+ end
@@ -0,0 +1,10 @@
1
+ module Datadog
2
+ module Ext
3
+ module AppTypes
4
+ WEB = 'web'.freeze
5
+ DB = 'db'.freeze
6
+ CACHE = 'cache'.freeze
7
+ WORKER = 'worker'.freeze
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Datadog
2
+ module Ext
3
+ module CACHE
4
+ TYPE = 'cache'.freeze
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,10 @@
1
+ module Datadog
2
+ module Ext
3
+ module DistributedTracing
4
+ # HTTP headers one should set for distributed tracing.
5
+ # These are cross-language (eg: Python, Go and other implementations should honor these)
6
+ HTTP_HEADER_TRACE_ID = 'x-datadog-trace-id'.freeze
7
+ HTTP_HEADER_PARENT_ID = 'x-datadog-parent-id'.freeze
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Datadog
2
+ module Ext
3
+ module Errors
4
+ STATUS = 1
5
+ MSG = 'error.msg'.freeze
6
+ TYPE = 'error.type'.freeze
7
+ STACK = 'error.stack'.freeze
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ module Datadog
2
+ module Ext
3
+ module HTTP
4
+ TYPE = 'http'.freeze
5
+ TEMPLATE = 'template'.freeze
6
+ URL = 'http.url'.freeze
7
+ METHOD = 'http.method'.freeze
8
+ STATUS_CODE = 'http.status_code'.freeze
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module Datadog
2
+ module Ext
3
+ module NET
4
+ TARGET_HOST = 'out.host'.freeze
5
+ TARGET_PORT = 'out.port'.freeze
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,11 @@
1
+ module Datadog
2
+ module Ext
3
+ module Redis
4
+ # type of the spans
5
+ TYPE = 'redis'.freeze
6
+
7
+ # net extension
8
+ DB = 'out.redis_db'.freeze
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module Datadog
2
+ module Ext
3
+ module SQL
4
+ TYPE = 'sql'.freeze
5
+ QUERY = 'sql.query'.freeze
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,39 @@
1
+ require 'logger'
2
+
3
+ module Datadog
4
+ LOG_PREFIX = 'ddtrace'.freeze
5
+
6
+ # A custom logger with minor enhancements:
7
+ # - progname defaults to ddtrace to clearly identify Datadog dd-trace-rb related messages
8
+ # - adds last caller stack-trace info to know where the message comes from
9
+ class Logger < ::Logger
10
+ def initialize(*args, &block)
11
+ super
12
+ self.progname = LOG_PREFIX
13
+ end
14
+
15
+ def add(severity, message = nil, progname = nil, &block)
16
+ where = ''
17
+
18
+ # We are in debug mode, or this is an error, add stack trace to help debugging
19
+ if debug? || severity >= ::Logger::ERROR
20
+ c = caller
21
+ where = "(#{c[1]}) " if c.length > 1
22
+ end
23
+
24
+ if message.nil?
25
+ if block_given?
26
+ super(severity, message, progname) do
27
+ "[#{self.progname}] #{where}#{yield}"
28
+ end
29
+ else
30
+ super(severity, message, "[#{self.progname}] #{where}#{progname}")
31
+ end
32
+ else
33
+ super(severity, "[#{self.progname}] #{where}#{message}")
34
+ end
35
+ end
36
+
37
+ alias log add
38
+ end
39
+ end
@@ -0,0 +1,84 @@
1
+ require 'thread'
2
+
3
+ # We import all patchers for every module we support, but this is fine
4
+ # because patchers do not include any 3rd party module nor even our
5
+ # patching code, which is required on demand, when patching.
6
+ require 'ddtrace/contrib/active_record/patcher'
7
+ require 'ddtrace/contrib/elasticsearch/patcher'
8
+ require 'ddtrace/contrib/grape/patcher'
9
+ require 'ddtrace/contrib/redis/patcher'
10
+ require 'ddtrace/contrib/http/patcher'
11
+
12
+ module Datadog
13
+ # Monkey is used for monkey-patching 3rd party libs.
14
+ module Monkey
15
+ @patched = []
16
+ @autopatch_modules = {
17
+ elasticsearch: true,
18
+ http: true,
19
+ redis: true,
20
+ grape: true,
21
+ active_record: false
22
+ }
23
+ # Patchers should expose 2 methods:
24
+ # - patch, which applies our patch if needed. Should be idempotent,
25
+ # can be call twice but should just do nothing the second time.
26
+ # - patched?, which returns true if the module has been succesfully
27
+ # patched (patching might have failed if requirements were not here)
28
+ @patchers = { elasticsearch: Datadog::Contrib::Elasticsearch::Patcher,
29
+ http: Datadog::Contrib::HTTP::Patcher,
30
+ redis: Datadog::Contrib::Redis::Patcher,
31
+ grape: Datadog::Contrib::Grape::Patcher,
32
+ active_record: Datadog::Contrib::ActiveRecord::Patcher }
33
+ @mutex = Mutex.new
34
+
35
+ module_function
36
+
37
+ def autopatch_modules
38
+ @autopatch_modules.clone
39
+ end
40
+
41
+ def patch_all
42
+ patch @autopatch_modules
43
+ end
44
+
45
+ def patch_module(m)
46
+ @mutex.synchronize do
47
+ patcher = @patchers[m]
48
+ raise "Unsupported module #{m}" unless patcher
49
+ patcher.patch
50
+ end
51
+ end
52
+
53
+ def patch(modules)
54
+ modules.each do |k, v|
55
+ patch_module(k) if v
56
+ end
57
+ end
58
+
59
+ def get_patched_modules
60
+ patched = autopatch_modules
61
+ @patchers.each do |k, v|
62
+ @mutex.synchronize do
63
+ if v
64
+ patcher = @patchers[k]
65
+ patched[k] = patcher.patched? if patcher
66
+ end
67
+ end
68
+ end
69
+ patched
70
+ end
71
+
72
+ def without_warnings
73
+ # This is typically used when monkey patching functions such as
74
+ # intialize, which Ruby advices you not to. Use cautiously.
75
+ v = $VERBOSE
76
+ $VERBOSE = nil
77
+ begin
78
+ yield
79
+ ensure
80
+ $VERBOSE = v
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,63 @@
1
+ # \Datadog global namespace that includes all tracing functionality for Tracer and Span classes.
2
+ module Datadog
3
+ # A \Pin (a.k.a Patch INfo) is a small class which is used to
4
+ # set tracing metadata on a particular traced object.
5
+ # This is useful if you wanted to, say, trace two different
6
+ # database clusters.
7
+ class Pin
8
+ def self.get_from(obj)
9
+ return nil unless obj.respond_to? :datadog_pin
10
+ obj.datadog_pin
11
+ end
12
+
13
+ attr_accessor :service
14
+ attr_accessor :app
15
+ attr_accessor :tags
16
+ attr_accessor :app_type
17
+ attr_accessor :name
18
+ attr_accessor :tracer
19
+ attr_accessor :config
20
+
21
+ # [ruby19] named parameters would be more idiomatic here, but would break backward compatibility
22
+ def initialize(service, options = { app: nil, tags: nil, app_type: nil, tracer: nil, config: nil })
23
+ @service = service
24
+ @app = options.fetch(:app, nil)
25
+ @tags = options.fetch(:tags, nil)
26
+ @app_type = options.fetch(:app_type, nil)
27
+ @name = nil # this would rarely be overriden as it's really span-specific
28
+ @tracer = options[:tracer] || Datadog.tracer
29
+ @config = options.fetch(:config, nil)
30
+ end
31
+
32
+ def enabled?
33
+ return @tracer.enabled if @tracer
34
+ false
35
+ end
36
+
37
+ def onto(obj)
38
+ unless obj.respond_to? :datadog_pin=
39
+ obj.instance_exec do
40
+ def datadog_pin=(pin)
41
+ Datadog::Tracer.log.debug("Set pin #{pin.service} on #{self.class}.")
42
+ @datadog_pin = pin
43
+ end
44
+ end
45
+ end
46
+
47
+ unless obj.respond_to? :datadog_pin
48
+ obj.instance_exec do
49
+ def datadog_pin
50
+ Datadog::Tracer.log.debug("Get pin from #{self.class}.")
51
+ @datadog_pin
52
+ end
53
+ end
54
+ end
55
+
56
+ obj.datadog_pin = self
57
+ end
58
+
59
+ def to_s
60
+ "Pin(service:#{@service},app:#{@app},app_type:#{@app_type},name:#{@name})"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ module Datadog
2
+ # DefaultContextProvider is a default context provider that retrieves
3
+ # all contexts from the current thread-local storage. It is suitable for
4
+ # synchronous programming.
5
+ class DefaultContextProvider
6
+ # Initializes the default context provider with a thread-bound context.
7
+ def initialize
8
+ @context = Datadog::ThreadLocalContext.new
9
+ end
10
+
11
+ # Sets the current context.
12
+ def context=(ctx)
13
+ @context.local = ctx
14
+ end
15
+
16
+ # Return the current context.
17
+ def context
18
+ @context.local
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ module Datadog
2
+ # \Sampler performs client-side trace sampling.
3
+ class Sampler
4
+ def sample(_span)
5
+ raise NotImplementedError, 'samplers have to implement the sample() method'
6
+ end
7
+ end
8
+
9
+ # \AllSampler samples all the traces.
10
+ class AllSampler < Sampler
11
+ def sample(span)
12
+ span.sampled = true
13
+ end
14
+ end
15
+
16
+ # \RateSampler is based on a sample rate.
17
+ class RateSampler < Sampler
18
+ KNUTH_FACTOR = 1111111111111111111
19
+ SAMPLE_RATE_METRIC_KEY = '_sample_rate'.freeze()
20
+
21
+ attr_reader :sample_rate
22
+
23
+ # Initialize a \RateSampler.
24
+ # This sampler keeps a random subset of the traces. Its main purpose is to
25
+ # reduce the instrumentation footprint.
26
+ #
27
+ # * +sample_rate+: the sample rate as a \Float between 0.0 and 1.0. 0.0
28
+ # means that no trace will be sampled; 1.0 means that all traces will be
29
+ # sampled.
30
+ def initialize(sample_rate = 1.0)
31
+ unless sample_rate > 0.0 && sample_rate <= 1.0
32
+ Datadog::Tracer.log.error('sample rate is not between 0 and 1, disabling the sampler')
33
+ sample_rate = 1.0
34
+ end
35
+
36
+ self.sample_rate = sample_rate
37
+ end
38
+
39
+ def sample_rate=(sample_rate)
40
+ @sample_rate = sample_rate
41
+ @sampling_id_threshold = sample_rate * Span::MAX_ID
42
+ end
43
+
44
+ def sample(span)
45
+ span.sampled = ((span.trace_id * KNUTH_FACTOR) % Datadog::Span::MAX_ID) <= @sampling_id_threshold
46
+ span.set_metric(SAMPLE_RATE_METRIC_KEY, @sample_rate)
47
+ end
48
+ end
49
+ end