ddtrace 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env +11 -0
- data/.gitignore +56 -0
- data/.rubocop.yml +43 -0
- data/Appraisals +65 -0
- data/Gemfile +3 -0
- data/LICENSE +24 -0
- data/README.md +119 -0
- data/Rakefile +103 -0
- data/circle.yml +68 -0
- data/ddtrace.gemspec +41 -0
- data/docker-compose.yml +33 -0
- data/docs/GettingStarted +352 -0
- data/gemfiles/contrib.gemfile +9 -0
- data/gemfiles/rails3_mysql2.gemfile +11 -0
- data/gemfiles/rails3_postgres.gemfile +10 -0
- data/gemfiles/rails3_postgres_redis.gemfile +11 -0
- data/gemfiles/rails4_mysql2.gemfile +9 -0
- data/gemfiles/rails4_postgres.gemfile +9 -0
- data/gemfiles/rails4_postgres_redis.gemfile +10 -0
- data/gemfiles/rails5_mysql2.gemfile +8 -0
- data/gemfiles/rails5_postgres.gemfile +8 -0
- data/gemfiles/rails5_postgres_redis.gemfile +9 -0
- data/lib/ddtrace.rb +63 -0
- data/lib/ddtrace/buffer.rb +77 -0
- data/lib/ddtrace/contrib/elasticsearch/core.rb +56 -0
- data/lib/ddtrace/contrib/elasticsearch/patcher.rb +35 -0
- data/lib/ddtrace/contrib/elasticsearch/quantize.rb +22 -0
- data/lib/ddtrace/contrib/rails/action_controller.rb +75 -0
- data/lib/ddtrace/contrib/rails/action_view.rb +121 -0
- data/lib/ddtrace/contrib/rails/active_record.rb +44 -0
- data/lib/ddtrace/contrib/rails/active_support.rb +115 -0
- data/lib/ddtrace/contrib/rails/core_extensions.rb +89 -0
- data/lib/ddtrace/contrib/rails/framework.rb +107 -0
- data/lib/ddtrace/contrib/rails/utils.rb +42 -0
- data/lib/ddtrace/contrib/redis/core.rb +72 -0
- data/lib/ddtrace/contrib/redis/patcher.rb +36 -0
- data/lib/ddtrace/contrib/redis/quantize.rb +30 -0
- data/lib/ddtrace/contrib/redis/tags.rb +19 -0
- data/lib/ddtrace/encoding.rb +65 -0
- data/lib/ddtrace/ext/app_types.rb +9 -0
- data/lib/ddtrace/ext/cache.rb +7 -0
- data/lib/ddtrace/ext/errors.rb +9 -0
- data/lib/ddtrace/ext/http.rb +11 -0
- data/lib/ddtrace/ext/net.rb +8 -0
- data/lib/ddtrace/ext/redis.rb +16 -0
- data/lib/ddtrace/ext/sql.rb +8 -0
- data/lib/ddtrace/monkey.rb +60 -0
- data/lib/ddtrace/pin.rb +62 -0
- data/lib/ddtrace/span.rb +163 -0
- data/lib/ddtrace/tracer.rb +180 -0
- data/lib/ddtrace/transport.rb +149 -0
- data/lib/ddtrace/utils.rb +9 -0
- data/lib/ddtrace/version.rb +9 -0
- data/lib/ddtrace/workers.rb +109 -0
- data/lib/ddtrace/writer.rb +119 -0
- 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,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,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
|
data/lib/ddtrace/pin.rb
ADDED
@@ -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
|
data/lib/ddtrace/span.rb
ADDED
@@ -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
|