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,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "test-unit"
6
+ gem "rails", "3.2.22.5"
7
+ gem "pg", :platform => :ruby
8
+ gem "activerecord-jdbcpostgresql-adapter", :platform => :jruby
9
+
10
+ gemspec :path => "../"
@@ -0,0 +1,11 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "test-unit"
6
+ gem "rails", "3.2.22.5"
7
+ gem "pg", :platform => :ruby
8
+ gem "activerecord-jdbcpostgresql-adapter", :platform => :jruby
9
+ gem "redis-rails"
10
+
11
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "4.2.7.1"
6
+ gem "mysql2", :platform => :ruby
7
+ gem "activerecord-jdbcmysql-adapter", :platform => :jruby
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "4.2.7.1"
6
+ gem "pg", :platform => :ruby
7
+ gem "activerecord-jdbcpostgresql-adapter", :platform => :jruby
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "4.2.7.1"
6
+ gem "pg", :platform => :ruby
7
+ gem "activerecord-jdbcpostgresql-adapter", :platform => :jruby
8
+ gem "redis-rails"
9
+
10
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "5.0.0.1"
6
+ gem "mysql2", :platform => :ruby
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "5.0.0.1"
6
+ gem "pg", :platform => :ruby
7
+
8
+ gemspec :path => "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "5.0.0.1"
6
+ gem "pg", :platform => :ruby
7
+ gem "redis-rails"
8
+
9
+ gemspec :path => "../"
@@ -0,0 +1,63 @@
1
+ require 'ddtrace/monkey'
2
+ require 'ddtrace/pin'
3
+ require 'ddtrace/tracer'
4
+
5
+ # \Datadog global namespace that includes all tracing functionality for Tracer and Span classes.
6
+ module Datadog
7
+ @tracer = Datadog::Tracer.new()
8
+
9
+ # Default tracer that can be used as soon as +ddtrace+ is required:
10
+ #
11
+ # require 'ddtrace'
12
+ #
13
+ # span = Datadog.tracer.trace('web.request')
14
+ # span.finish()
15
+ #
16
+ # If you want to override the default tracer, the recommended way
17
+ # is to "pin" your own tracer onto your traced component:
18
+ #
19
+ # tracer = Datadog::Tracer.new
20
+ # pin = Datadog::Pin.get_from(mypatchcomponent)
21
+ # pin.tracer = tracer
22
+
23
+ def self.tracer
24
+ @tracer
25
+ end
26
+ end
27
+
28
+ # Datadog auto instrumentation for frameworks
29
+ if defined?(Rails::VERSION)
30
+ if Rails::VERSION::MAJOR.to_i >= 3
31
+ begin
32
+ # We include 'redis-rails' here if it's available, doing it later
33
+ # (typically in initialize callback) does not work, it does not
34
+ # get loaded in the right context.
35
+ require 'redis-rails'
36
+ Datadog::Tracer.log.info("'redis-rails' module found, datadog redis integration is available")
37
+ rescue LoadError
38
+ Datadog::Tracer.log.info("no 'redis-rails' module found, datadog redis integration is not available")
39
+ end
40
+ require 'ddtrace/contrib/rails/framework'
41
+
42
+ Datadog::Monkey.patch_module(:redis) # does nothing if redis is not loaded
43
+ Datadog::RailsPatcher.patch_renderer()
44
+ Datadog::RailsPatcher.patch_cache_store()
45
+
46
+ module Datadog
47
+ # Run the auto instrumentation directly after the initialization of the application and
48
+ # after the application initializers in config/initializers are run
49
+ class Railtie < Rails::Railtie
50
+ config.after_initialize do |app|
51
+ Datadog::Contrib::Rails::Framework.configure(config: app.config)
52
+ Datadog::Contrib::Rails::Framework.auto_instrument()
53
+ Datadog::Contrib::Rails::Framework.auto_instrument_redis()
54
+ end
55
+ end
56
+ end
57
+ else
58
+ logger = Logger.new(STDOUT)
59
+ logger.warn 'Detected a Rails version < 3.x.'\
60
+ 'This version is not supported yet and the'\
61
+ 'auto-instrumentation for core components will be disabled.'
62
+ end
63
+ end
@@ -0,0 +1,77 @@
1
+ require 'thread'
2
+
3
+ module Datadog
4
+ # Buffer used to store active spans
5
+ class SpanBuffer
6
+ # ensure that a new SpanBuffer clears the thread spans
7
+ def initialize
8
+ Thread.current[:datadog_span] = nil
9
+ end
10
+
11
+ # Set the current active span.
12
+ def set(span)
13
+ Thread.current[:datadog_span] = span
14
+ end
15
+
16
+ # Return the current active span or nil.
17
+ def get
18
+ Thread.current[:datadog_span]
19
+ end
20
+
21
+ # Pop the current active span.
22
+ def pop
23
+ span = get()
24
+ set(nil)
25
+ span
26
+ end
27
+ end
28
+
29
+ # Trace buffer that stores application traces. The buffer has a maximum size and when
30
+ # the buffer is full, a random trace is discarded. This class is thread-safe and is used
31
+ # automatically by the ``Tracer`` instance when a ``Span`` is finished.
32
+ class TraceBuffer
33
+ def initialize(max_size)
34
+ @max_size = max_size
35
+
36
+ @mutex = Mutex.new()
37
+ @traces = []
38
+ end
39
+
40
+ # Add a new ``trace`` in the local queue. This method doesn't block the execution
41
+ # even if the buffer is full. In that case, a random trace is discarded.
42
+ def push(trace)
43
+ @mutex.synchronize do
44
+ len = @traces.length
45
+ if len < @max_size || @max_size <= 0
46
+ @traces << trace
47
+ else
48
+ # we should replace a random trace with the new one
49
+ @traces[rand(len)] = trace
50
+ end
51
+ end
52
+ end
53
+
54
+ # Return the current number of stored traces.
55
+ def length
56
+ @mutex.synchronize do
57
+ return @traces.length
58
+ end
59
+ end
60
+
61
+ # Return if the buffer is empty.
62
+ def empty?
63
+ @mutex.synchronize do
64
+ return @traces.empty?
65
+ end
66
+ end
67
+
68
+ # Stored traces are returned and the local buffer is reset
69
+ def pop
70
+ @mutex.synchronize do
71
+ traces = @traces
72
+ @traces = []
73
+ return traces
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,56 @@
1
+ require 'uri'
2
+ require 'ddtrace/pin'
3
+ require 'ddtrace/ext/app_types'
4
+ require 'json'
5
+ require 'ddtrace/contrib/elasticsearch/quantize'
6
+
7
+ module Datadog
8
+ module Contrib
9
+ module Elasticsearch
10
+ URL = 'elasticsearch.url'.freeze
11
+ METHOD = 'elasticsearch.method'.freeze
12
+ PARAMS = 'elasticsearch.params'.freeze
13
+ BODY = 'elasticsearch.body'.freeze
14
+
15
+ SERVICE = 'elasticsearch'.freeze
16
+ SPAN_TYPE = 'elasticsearch'.freeze
17
+
18
+ # Datadog APM Elastic Search integration.
19
+ module TracedClient
20
+ def initialize(*args)
21
+ pin = Datadog::Pin.new(SERVICE, app: 'elasticsearch', app_type: Datadog::Ext::AppTypes::DB)
22
+ pin.onto(self)
23
+ super(*args)
24
+ end
25
+
26
+ def perform_request(*args)
27
+ pin = Datadog::Pin.get_from(self)
28
+ method = args[0]
29
+ path = args[1]
30
+ params = args[2]
31
+ body = args[3]
32
+ full_url = URI.parse(path)
33
+
34
+ url = full_url.path
35
+ response = nil
36
+ pin.tracer.trace('elasticsearch.query') do |span|
37
+ span.service = pin.service
38
+ span.span_type = SPAN_TYPE
39
+
40
+ span.set_tag(METHOD, method)
41
+ span.set_tag(URL, url)
42
+ span.set_tag(PARAMS, JSON.generate(params)) if params
43
+ span.set_tag(BODY, JSON.generate(body)) if body
44
+
45
+ quantized_url = Datadog::Contrib::Elasticsearch::Quantize.format_url(url)
46
+ span.resource = "#{method} #{quantized_url}"
47
+
48
+ response = super(*args)
49
+ end
50
+
51
+ response
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,35 @@
1
+ # requirements should be kept minimal as Patcher is a shared requirement.
2
+
3
+ module Datadog
4
+ module Contrib
5
+ module Elasticsearch
6
+ # Patcher enables patching of 'elasticsearch/transport' 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?(::Elasticsearch::Transport::VERSION) && \
16
+ Gem::Version.new(::Elasticsearch::Transport::VERSION) >= Gem::Version.new('1.0.0'))
17
+ begin
18
+ require 'ddtrace/contrib/elasticsearch/core'
19
+ ::Elasticsearch::Transport::Client.prepend Datadog::Contrib::Elasticsearch::TracedClient
20
+ @patched = true
21
+ rescue StandardError => e
22
+ Datadog::Tracer.log.error("Unable to apply Elastic Search integration: #{e}")
23
+ end
24
+ end
25
+ @patched
26
+ end
27
+
28
+ # patched? tells wether patch has been successfully applied
29
+ def patched?
30
+ @patched
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ module Datadog
2
+ module Contrib
3
+ module Elasticsearch
4
+ # Quantize contains ES-specific resource quantization tools.
5
+ module Quantize
6
+ ID_REGEXP = %r{\/([0-9]+)([\/\?]|$)}
7
+ ID_PLACEHOLDER = '/?\2'.freeze
8
+
9
+ INDEX_REGEXP = /[0-9]{2,}/
10
+ INDEX_PLACEHOLDER = '?'.freeze
11
+
12
+ module_function
13
+
14
+ # Very basic quantization, complex processing should be done in the agent
15
+ def format_url(url)
16
+ quantized_url = url.gsub(ID_REGEXP, ID_PLACEHOLDER)
17
+ quantized_url.gsub(INDEX_REGEXP, INDEX_PLACEHOLDER)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,75 @@
1
+ require 'ddtrace/ext/http'
2
+ require 'ddtrace/ext/errors'
3
+
4
+ module Datadog
5
+ module Contrib
6
+ module Rails
7
+ # TODO[manu]: write docs
8
+ module ActionController
9
+ KEY = 'datadog_actioncontroller'.freeze
10
+
11
+ def self.instrument
12
+ # subscribe when the request processing starts
13
+ ::ActiveSupport::Notifications.subscribe('start_processing.action_controller') do |*args|
14
+ start_processing(*args)
15
+ end
16
+
17
+ # subscribe when the request processing has been completed
18
+ ::ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
19
+ process_action(*args)
20
+ end
21
+ end
22
+
23
+ def self.start_processing(*)
24
+ return if Thread.current[KEY]
25
+
26
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
27
+ service = ::Rails.configuration.datadog_trace.fetch(:default_service)
28
+ type = Datadog::Ext::HTTP::TYPE
29
+ tracer.trace('rails.request', service: service, span_type: type)
30
+
31
+ Thread.current[KEY] = true
32
+ rescue StandardError => e
33
+ Datadog::Tracer.log.error(e.message)
34
+ end
35
+
36
+ def self.process_action(_name, start, finish, _id, payload)
37
+ return unless Thread.current[KEY]
38
+ Thread.current[KEY] = false
39
+
40
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
41
+ span = tracer.active_span()
42
+ return unless span
43
+
44
+ begin
45
+ span.resource = "#{payload.fetch(:controller)}##{payload.fetch(:action)}"
46
+ span.set_tag(Datadog::Ext::HTTP::URL, payload.fetch(:path))
47
+ span.set_tag(Datadog::Ext::HTTP::METHOD, payload.fetch(:method))
48
+ span.set_tag('rails.route.action', payload.fetch(:action))
49
+ span.set_tag('rails.route.controller', payload.fetch(:controller))
50
+
51
+ if payload[:exception].nil?
52
+ # [christian] in some cases :status is not defined,
53
+ # rather than firing an error, simply acknowledge we don't know it.
54
+ span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, payload.fetch(:status, '?').to_s)
55
+ else
56
+ error = payload[:exception]
57
+ # TODO[manu]: it's right to have a 500? there are cases in Rails that let
58
+ # user to recover the error after this point?
59
+ span.status = 1
60
+ span.set_tag(Datadog::Ext::Errors::TYPE, error[0])
61
+ span.set_tag(Datadog::Ext::Errors::MSG, error[1])
62
+ span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, '500')
63
+ end
64
+
65
+ ensure
66
+ span.start_time = start
67
+ span.finish_at(finish)
68
+ end
69
+ rescue StandardError => e
70
+ Datadog::Tracer.log.error(e.message)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,121 @@
1
+ require 'ddtrace/contrib/rails/utils'
2
+
3
+ module Datadog
4
+ module Contrib
5
+ module Rails
6
+ # TODO[manu]: write docs
7
+ module ActionView
8
+ def self.instrument
9
+ # subscribe when the template rendering starts
10
+ ::ActiveSupport::Notifications.subscribe('start_render_template.action_view') do |*args|
11
+ start_render_template(*args)
12
+ end
13
+
14
+ # subscribe when the partial rendering starts
15
+ ::ActiveSupport::Notifications.subscribe('start_render_partial.action_view') do |*args|
16
+ start_render_partial(*args)
17
+ end
18
+
19
+ # subscribe when the template rendering has been processed
20
+ ::ActiveSupport::Notifications.subscribe('render_template.action_view') do |*args|
21
+ render_template(*args)
22
+ end
23
+
24
+ # subscribe when the partial rendering has been processed
25
+ ::ActiveSupport::Notifications.subscribe('render_partial.action_view') do |*args|
26
+ render_partial(*args)
27
+ end
28
+ end
29
+
30
+ def self.get_key(f)
31
+ 'datadog_actionview_' + f
32
+ end
33
+
34
+ def self.start_render_template(*)
35
+ key = get_key('render_template')
36
+ return if Thread.current[key]
37
+
38
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
39
+ type = Datadog::Ext::HTTP::TEMPLATE
40
+ tracer.trace('rails.render_template', span_type: type)
41
+
42
+ Thread.current[key] = true
43
+ rescue StandardError => e
44
+ Datadog::Tracer.log.error(e.message)
45
+ end
46
+
47
+ def self.start_render_partial(*)
48
+ key = get_key('render_partial')
49
+ return if Thread.current[key]
50
+
51
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
52
+ type = Datadog::Ext::HTTP::TEMPLATE
53
+ tracer.trace('rails.render_partial', span_type: type)
54
+
55
+ Thread.current[key] = true
56
+ rescue StandardError => e
57
+ Datadog::Tracer.log.error(e.message)
58
+ end
59
+
60
+ def self.render_template(_name, start, finish, _id, payload)
61
+ key = get_key('render_template')
62
+ return unless Thread.current[key]
63
+ Thread.current[key] = false
64
+
65
+ # finish the tracing and update the execution time
66
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
67
+ span = tracer.active_span()
68
+ return unless span
69
+
70
+ begin
71
+ template_name = Datadog::Contrib::Rails::Utils.normalize_template_name(payload.fetch(:identifier))
72
+ span.set_tag('rails.template_name', template_name)
73
+ span.set_tag('rails.layout', payload.fetch(:layout))
74
+
75
+ if payload[:exception]
76
+ error = payload[:exception]
77
+ span.status = 1
78
+ span.set_tag(Datadog::Ext::Errors::TYPE, error[0])
79
+ span.set_tag(Datadog::Ext::Errors::MSG, error[1])
80
+ end
81
+
82
+ ensure
83
+ span.start_time = start
84
+ span.finish_at(finish)
85
+ end
86
+ rescue StandardError => e
87
+ Datadog::Tracer.log.error(e.message)
88
+ end
89
+
90
+ def self.render_partial(_name, start, finish, _id, payload)
91
+ key = get_key('render_partial')
92
+ return unless Thread.current[key]
93
+ Thread.current[key] = false
94
+
95
+ # finish the tracing and update the execution time
96
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
97
+ span = tracer.active_span()
98
+ return unless span
99
+
100
+ begin
101
+ template_name = Datadog::Contrib::Rails::Utils.normalize_template_name(payload.fetch(:identifier))
102
+ span.set_tag('rails.template_name', template_name)
103
+
104
+ if payload[:exception]
105
+ error = payload[:exception]
106
+ span.status = 1
107
+ span.set_tag(Datadog::Ext::Errors::TYPE, error[0])
108
+ span.set_tag(Datadog::Ext::Errors::MSG, error[1])
109
+ end
110
+
111
+ ensure
112
+ span.start_time = start
113
+ span.finish_at(finish)
114
+ end
115
+ rescue StandardError => e
116
+ Datadog::Tracer.log.error(e.message)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end