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