ddtrace 0.3.1

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 (57) hide show
  1. checksums.yaml +7 -0
  2. data/.env +11 -0
  3. data/.gitignore +56 -0
  4. data/.rubocop.yml +43 -0
  5. data/Appraisals +65 -0
  6. data/Gemfile +3 -0
  7. data/LICENSE +24 -0
  8. data/README.md +119 -0
  9. data/Rakefile +103 -0
  10. data/circle.yml +68 -0
  11. data/ddtrace.gemspec +41 -0
  12. data/docker-compose.yml +33 -0
  13. data/docs/GettingStarted +352 -0
  14. data/gemfiles/contrib.gemfile +9 -0
  15. data/gemfiles/rails3_mysql2.gemfile +11 -0
  16. data/gemfiles/rails3_postgres.gemfile +10 -0
  17. data/gemfiles/rails3_postgres_redis.gemfile +11 -0
  18. data/gemfiles/rails4_mysql2.gemfile +9 -0
  19. data/gemfiles/rails4_postgres.gemfile +9 -0
  20. data/gemfiles/rails4_postgres_redis.gemfile +10 -0
  21. data/gemfiles/rails5_mysql2.gemfile +8 -0
  22. data/gemfiles/rails5_postgres.gemfile +8 -0
  23. data/gemfiles/rails5_postgres_redis.gemfile +9 -0
  24. data/lib/ddtrace.rb +63 -0
  25. data/lib/ddtrace/buffer.rb +77 -0
  26. data/lib/ddtrace/contrib/elasticsearch/core.rb +56 -0
  27. data/lib/ddtrace/contrib/elasticsearch/patcher.rb +35 -0
  28. data/lib/ddtrace/contrib/elasticsearch/quantize.rb +22 -0
  29. data/lib/ddtrace/contrib/rails/action_controller.rb +75 -0
  30. data/lib/ddtrace/contrib/rails/action_view.rb +121 -0
  31. data/lib/ddtrace/contrib/rails/active_record.rb +44 -0
  32. data/lib/ddtrace/contrib/rails/active_support.rb +115 -0
  33. data/lib/ddtrace/contrib/rails/core_extensions.rb +89 -0
  34. data/lib/ddtrace/contrib/rails/framework.rb +107 -0
  35. data/lib/ddtrace/contrib/rails/utils.rb +42 -0
  36. data/lib/ddtrace/contrib/redis/core.rb +72 -0
  37. data/lib/ddtrace/contrib/redis/patcher.rb +36 -0
  38. data/lib/ddtrace/contrib/redis/quantize.rb +30 -0
  39. data/lib/ddtrace/contrib/redis/tags.rb +19 -0
  40. data/lib/ddtrace/encoding.rb +65 -0
  41. data/lib/ddtrace/ext/app_types.rb +9 -0
  42. data/lib/ddtrace/ext/cache.rb +7 -0
  43. data/lib/ddtrace/ext/errors.rb +9 -0
  44. data/lib/ddtrace/ext/http.rb +11 -0
  45. data/lib/ddtrace/ext/net.rb +8 -0
  46. data/lib/ddtrace/ext/redis.rb +16 -0
  47. data/lib/ddtrace/ext/sql.rb +8 -0
  48. data/lib/ddtrace/monkey.rb +60 -0
  49. data/lib/ddtrace/pin.rb +62 -0
  50. data/lib/ddtrace/span.rb +163 -0
  51. data/lib/ddtrace/tracer.rb +180 -0
  52. data/lib/ddtrace/transport.rb +149 -0
  53. data/lib/ddtrace/utils.rb +9 -0
  54. data/lib/ddtrace/version.rb +9 -0
  55. data/lib/ddtrace/workers.rb +109 -0
  56. data/lib/ddtrace/writer.rb +119 -0
  57. metadata +187 -0
@@ -0,0 +1,36 @@
1
+ # requirements should be kept minimal as Patcher is a shared requirement.
2
+
3
+ module Datadog
4
+ module Contrib
5
+ module Redis
6
+ # Patcher enables patching of 'redis' module.
7
+ # This is used in monkey.rb to automatically apply patches
8
+ module Patcher
9
+ @patched = false
10
+
11
+ module_function
12
+
13
+ # patch applies our patch if needed
14
+ def patch
15
+ if !@patched && (defined?(::Redis::VERSION) && \
16
+ Gem::Version.new(::Redis::VERSION) >= Gem::Version.new('3.0.0'))
17
+ begin
18
+ require 'ddtrace/contrib/redis/core'
19
+ ::Redis.prepend Datadog::Contrib::Redis::TracedRedis
20
+ ::Redis::Client.prepend Datadog::Contrib::Redis::TracedRedisClient
21
+ @patched = true
22
+ rescue StandardError => e
23
+ Datadog::Tracer.log.error("Unable to apply Redis integration: #{e}")
24
+ end
25
+ end
26
+ @patched
27
+ end
28
+
29
+ # patched? tells wether patch has been successfully applied
30
+ def patched?
31
+ @patched
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ module Datadog
2
+ module Contrib
3
+ module Redis
4
+ # Quantize contains Redis-specific resource quantization tools.
5
+ module Quantize
6
+ PLACEHOLDER = '?'.freeze
7
+ TOO_LONG_MARK = '...'.freeze
8
+ VALUE_MAX_LEN = 100
9
+ CMD_MAX_LEN = 1000
10
+
11
+ module_function
12
+
13
+ def format_arg(arg)
14
+ a = arg.to_s
15
+ a = a[0..(VALUE_MAX_LEN - TOO_LONG_MARK.length - 1)] + TOO_LONG_MARK if a.length > VALUE_MAX_LEN
16
+ a
17
+ rescue StandardError => e
18
+ Datadog::Tracer.log.debug("non formattable Redis arg #{a}: #{e}")
19
+ PLACEHOLDER
20
+ end
21
+
22
+ def format_command_args(command_args)
23
+ cmd = command_args.map { |x| format_arg(x) }.join(' ')
24
+ cmd = cmd[0..(CMD_MAX_LEN - TOO_LONG_MARK.length - 1)] + TOO_LONG_MARK if cmd.length > CMD_MAX_LEN
25
+ cmd
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ require 'ddtrace/ext/net'
2
+ require 'ddtrace/ext/redis'
3
+
4
+ module Datadog
5
+ module Contrib
6
+ module Redis
7
+ # Tags handles generic common tags assignment.
8
+ module Tags
9
+ module_function
10
+
11
+ def set_common_tags(client, span)
12
+ span.set_tag Datadog::Ext::NET::TARGET_HOST, client.host
13
+ span.set_tag Datadog::Ext::NET::TARGET_PORT, client.port
14
+ span.set_tag Datadog::Ext::Redis::DB, client.db
15
+ end
16
+ end
17
+ end
18
+ end
19
+ 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,9 @@
1
+ module Datadog
2
+ module Ext
3
+ module AppTypes
4
+ WEB = 'web'.freeze
5
+ DB = 'db'.freeze
6
+ CACHE = 'cache'.freeze
7
+ end
8
+ end
9
+ 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,9 @@
1
+ module Datadog
2
+ module Ext
3
+ module Errors
4
+ MSG = 'error.msg'.freeze
5
+ TYPE = 'error.type'.freeze
6
+ STACK = 'error.stack'.freeze
7
+ end
8
+ end
9
+ 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,16 @@
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
+
10
+ # standard tags
11
+ RAWCMD = 'redis.raw_command'.freeze
12
+ ARGS_LEN = 'redis.args_length'.freeze
13
+ PIPELINE_LEN = 'redis.pipeline_length'.freeze
14
+ end
15
+ end
16
+ 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,60 @@
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/elasticsearch/patcher'
7
+ require 'ddtrace/contrib/redis/patcher'
8
+
9
+ module Datadog
10
+ # Monkey is used for monkey-patching 3rd party libs.
11
+ module Monkey
12
+ @patched = []
13
+ @autopatch_modules = { elasticsearch: true, redis: true }
14
+ # Patchers should expose 2 methods:
15
+ # - patch, which applies our patch if needed. Should be idempotent,
16
+ # can be call twice but should just do nothing the second time.
17
+ # - patched?, which returns true if the module has been succesfully
18
+ # patched (patching might have failed if requirements were not here)
19
+ @patchers = { elasticsearch: Datadog::Contrib::Elasticsearch::Patcher,
20
+ redis: Datadog::Contrib::Redis::Patcher }
21
+ @mutex = Mutex.new
22
+
23
+ module_function
24
+
25
+ def autopatch_modules
26
+ @autopatch_modules.clone
27
+ end
28
+
29
+ def patch_all
30
+ patch @autopatch_modules
31
+ end
32
+
33
+ def patch_module(m)
34
+ @mutex.synchronize do
35
+ patcher = @patchers[m]
36
+ raise 'Unsupported module #{m}' unless patcher
37
+ patcher.patch
38
+ end
39
+ end
40
+
41
+ def patch(modules)
42
+ modules.each do |k, v|
43
+ patch_module(k) if v
44
+ end
45
+ end
46
+
47
+ def get_patched_modules
48
+ patched = autopatch_modules
49
+ @autopatch_modules.each do |k, v|
50
+ @mutex.synchronize do
51
+ if v
52
+ patcher = @patchers[k]
53
+ patched[k] = patcher.patched? if patcher
54
+ end
55
+ end
56
+ end
57
+ patched
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,62 @@
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
+
20
+ def initialize(service, app: nil, tags: nil, app_type: nil, tracer: Datadog.tracer)
21
+ @service = service
22
+ @app = app
23
+ @tags = tags
24
+ @app_type = app_type
25
+ @name = nil # this would rarely be overriden as it's really span-specific
26
+ @tracer = tracer
27
+ end
28
+
29
+ def enabled?
30
+ return @tracer.enabled if @tracer
31
+ false
32
+ end
33
+
34
+ def onto(obj)
35
+ # rubocop:disable Lint/NestedMethodDefinition
36
+ unless obj.respond_to? :datadog_pin=
37
+ obj.instance_exec do
38
+ def datadog_pin=(pin)
39
+ Datadog::Tracer.log.debug('Set pin #{pin.service} on #{obj.class}.')
40
+ @datadog_pin = pin
41
+ end
42
+ end
43
+ end
44
+
45
+ unless obj.respond_to? :datadog_pin
46
+ obj.instance_exec do
47
+ def datadog_pin
48
+ Datadog::Tracer.log.debug('Get pin from #{obj.class}.')
49
+ @datadog_pin
50
+ end
51
+ end
52
+ end
53
+ # rubocop:enable Lint/NestedMethodDefinition
54
+
55
+ obj.datadog_pin = self
56
+ end
57
+
58
+ def to_s
59
+ "Pin(service:#{@service},app:#{@app},app_type:#{@app_type},name:#{@name})"
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,163 @@
1
+ require 'time'
2
+
3
+ require 'ddtrace/utils'
4
+ require 'ddtrace/ext/errors'
5
+
6
+ module Datadog
7
+ # Represents a logical unit of work in the system. Each trace consists of one or more spans.
8
+ # Each span consists of a start time and a duration. For example, a span can describe the time
9
+ # spent on a distributed call on a separate machine, or the time spent in a small component
10
+ # within a larger operation. Spans can be nested within each other, and in those instances
11
+ # will have a parent-child relationship.
12
+ class Span
13
+ # The max value for a \Span identifier
14
+ MAX_ID = 2**64 - 1
15
+
16
+ attr_accessor :name, :service, :resource, :span_type,
17
+ :start_time, :end_time,
18
+ :span_id, :trace_id, :parent_id,
19
+ :status, :parent
20
+
21
+ # Create a new span linked to the given tracer. Call the <tt>finish()</tt> method once the
22
+ # tracer operation is over or use the <tt>finish_at(time)</tt> helper to close the span with the
23
+ # given +time+. Available options are:
24
+ #
25
+ # * +service+: the service name for this span
26
+ # * +resource+: the resource this span refers, or +name+ if it's missing
27
+ # * +span_type+: the type of the span (such as +http+, +db+ and so on)
28
+ # * +parent_id+: the identifier of the parent span
29
+ # * +trace_id+: the identifier of the root span for this trace
30
+ def initialize(tracer, name, options = {})
31
+ @tracer = tracer
32
+
33
+ @name = name
34
+ @service = options.fetch(:service, nil)
35
+ @resource = options.fetch(:resource, name)
36
+ @span_type = options.fetch(:span_type, nil)
37
+
38
+ @span_id = Datadog::Utils.next_id()
39
+ @parent_id = options.fetch(:parent_id, 0)
40
+ @trace_id = options.fetch(:trace_id, @span_id)
41
+
42
+ @meta = {}
43
+ @status = 0
44
+
45
+ @parent = nil
46
+
47
+ @start_time = Time.now.utc
48
+ @end_time = nil
49
+ end
50
+
51
+ # Set the given key / value tag pair on the span. Keys and values
52
+ # must be strings. A valid example is:
53
+ #
54
+ # span.set_tag('http.method', request.method)
55
+ def set_tag(key, value)
56
+ @meta[key] = value.to_s
57
+ rescue StandardError => e
58
+ Datadog::Tracer.log.error("Unable to set the tag #{key}, ignoring it. Caused by: #{e}")
59
+ end
60
+
61
+ # Return the tag wth the given key, nil if it doesn't exist.
62
+ def get_tag(key)
63
+ @meta[key]
64
+ end
65
+
66
+ # Mark the span with the given error.
67
+ def set_error(e)
68
+ return if e.nil?
69
+ @status = 1
70
+ @meta[Datadog::Ext::Errors::MSG] = e.message
71
+ @meta[Datadog::Ext::Errors::TYPE] = e.class.to_s
72
+ @meta[Datadog::Ext::Errors::STACK] = e.backtrace.join("\n")
73
+ end
74
+
75
+ # Mark the span finished at the current time and submit it.
76
+ def finish
77
+ finish_at(Time.now.utc)
78
+ end
79
+
80
+ # Mark the span finished at the given time and submit it.
81
+ def finish_at(end_time)
82
+ @end_time = end_time
83
+
84
+ @tracer.record(self) unless @tracer.nil?
85
+
86
+ self
87
+ end
88
+
89
+ # Return whether the span is finished or not.
90
+ def finished?
91
+ !@end_time.nil?
92
+ end
93
+
94
+ # Return a string representation of the span.
95
+ def to_s
96
+ "Span(name:#{@name},sid:#{@span_id},tid:#{@trace_id},pid:#{@parent_id})"
97
+ end
98
+
99
+ # Set this span's parent, inheriting any properties not explicitly set.
100
+ # If the parent is nil, set the span zero values.
101
+ def set_parent(parent)
102
+ @parent = parent
103
+
104
+ if parent.nil?
105
+ @trace_id = @span_id
106
+ @parent_id = 0
107
+ else
108
+ @trace_id = parent.trace_id
109
+ @parent_id = parent.span_id
110
+ @service ||= parent.service
111
+ end
112
+ end
113
+
114
+ # Return the hash representation of the current span.
115
+ def to_hash
116
+ h = {
117
+ span_id: @span_id,
118
+ parent_id: @parent_id,
119
+ trace_id: @trace_id,
120
+ name: @name,
121
+ service: @service,
122
+ resource: @resource,
123
+ type: @span_type,
124
+ meta: @meta,
125
+ error: @status
126
+ }
127
+
128
+ if !@start_time.nil? && !@end_time.nil?
129
+ h[:start] = (@start_time.to_f * 1e9).to_i
130
+ h[:duration] = ((@end_time - @start_time) * 1e9).to_i
131
+ end
132
+
133
+ h
134
+ end
135
+
136
+ # Return a human readable version of the span
137
+ def pretty_print(q)
138
+ start_time = (@start_time.to_f * 1e9).to_i rescue '-'
139
+ end_time = (@end_time.to_f * 1e9).to_i rescue '-'
140
+ duration = ((@end_time - @start_time) * 1e9).to_i rescue 0
141
+ q.group 0 do
142
+ q.breakable
143
+ q.text "Name: #{@name}\n"
144
+ q.text "Span ID: #{@span_id}\n"
145
+ q.text "Parent ID: #{@parent_id}\n"
146
+ q.text "Trace ID: #{@trace_id}\n"
147
+ q.text "Type: #{@span_type}\n"
148
+ q.text "Service: #{@service}\n"
149
+ q.text "Resource: #{@resource}\n"
150
+ q.text "Error: #{@status}\n"
151
+ q.text "Start: #{start_time}\n"
152
+ q.text "End: #{end_time}\n"
153
+ q.text "Duration: #{duration}\n"
154
+ q.group(2, 'Tags: [', ']') do
155
+ q.breakable
156
+ q.seplist @meta.each do |key, value|
157
+ q.text "#{key} => #{value}"
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end