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.
- 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
|