ddtrace 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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