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,44 @@
1
+ require 'ddtrace/ext/sql'
2
+
3
+ require 'ddtrace/contrib/rails/utils'
4
+
5
+ module Datadog
6
+ module Contrib
7
+ module Rails
8
+ # TODO[manu]: write docs
9
+ module ActiveRecord
10
+ def self.instrument
11
+ # subscribe when the active record query has been processed
12
+ ::ActiveSupport::Notifications.subscribe('sql.active_record') do |*args|
13
+ sql(*args)
14
+ end
15
+ end
16
+
17
+ def self.sql(_name, start, finish, _id, payload)
18
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
19
+ database_service = ::Rails.configuration.datadog_trace.fetch(:default_database_service)
20
+ adapter_name = ::ActiveRecord::Base.connection_config[:adapter]
21
+ adapter_name = Datadog::Contrib::Rails::Utils.normalize_vendor(adapter_name)
22
+ span_type = Datadog::Ext::SQL::TYPE
23
+
24
+ span = tracer.trace(
25
+ "#{adapter_name}.query",
26
+ resource: payload.fetch(:sql),
27
+ service: database_service,
28
+ span_type: span_type
29
+ )
30
+
31
+ # the span should have the query ONLY in the Resource attribute,
32
+ # so that the ``sql.query`` tag will be set in the agent with an
33
+ # obfuscated version
34
+ span.span_type = Datadog::Ext::SQL::TYPE
35
+ span.set_tag('rails.db.vendor', adapter_name)
36
+ span.start_time = start
37
+ span.finish_at(finish)
38
+ rescue StandardError => e
39
+ Datadog::Tracer.log.error(e.message)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,115 @@
1
+ require 'thread'
2
+ require 'ddtrace/ext/cache'
3
+
4
+ module Datadog
5
+ module Contrib
6
+ module Rails
7
+ # TODO[manu]: write docs
8
+ module ActiveSupport
9
+ def self.instrument
10
+ # subscribe when a cache read starts being processed
11
+ ::ActiveSupport::Notifications.subscribe('start_cache_read.active_support') do |*args|
12
+ start_trace_cache('GET', *args)
13
+ end
14
+
15
+ # subscribe when a cache fetch starts being processed
16
+ ::ActiveSupport::Notifications.subscribe('start_cache_fetch.active_support') do |*args|
17
+ start_trace_cache('GET', *args)
18
+ end
19
+
20
+ # subscribe when a cache write starts being processed
21
+ ::ActiveSupport::Notifications.subscribe('start_cache_write.active_support') do |*args|
22
+ start_trace_cache('SET', *args)
23
+ end
24
+
25
+ # subscribe when a cache delete starts being processed
26
+ ::ActiveSupport::Notifications.subscribe('start_cache_delete.active_support') do |*args|
27
+ start_trace_cache('DELETE', *args)
28
+ end
29
+
30
+ # subscribe when a cache read has been processed
31
+ ::ActiveSupport::Notifications.subscribe('cache_read.active_support') do |*args|
32
+ trace_cache('GET', *args)
33
+ end
34
+
35
+ # subscribe when a cache write has been processed
36
+ ::ActiveSupport::Notifications.subscribe('cache_write.active_support') do |*args|
37
+ trace_cache('SET', *args)
38
+ end
39
+
40
+ # subscribe when a cache delete has been processed
41
+ ::ActiveSupport::Notifications.subscribe('cache_delete.active_support') do |*args|
42
+ trace_cache('DELETE', *args)
43
+ end
44
+ end
45
+
46
+ def self.create_span(tracer)
47
+ service = ::Rails.configuration.datadog_trace.fetch(:default_cache_service)
48
+ type = Datadog::Ext::CACHE::TYPE
49
+ tracer.trace('rails.cache', service: service, span_type: type)
50
+ end
51
+
52
+ def self.get_key(resource)
53
+ 'datadog_activesupport_' + resource
54
+ end
55
+
56
+ def self.start_trace_cache(resource, *_args)
57
+ key = get_key(resource)
58
+ # This is mostly to trap the case of fetch/read. In some cases the framework
59
+ # will call fetch but fetch won't call read. In some cases read can be called
60
+ # alone. And in some cases they are nested. In all cases we want to have one
61
+ # and only one span.
62
+ return if Thread.current[key]
63
+ create_span(::Rails.configuration.datadog_trace.fetch(:tracer))
64
+ Thread.current[key] = true
65
+ rescue StandardError => e
66
+ Datadog::Tracer.log.error(e.message)
67
+ end
68
+
69
+ def self.trace_cache(resource, _name, start, finish, _id, payload)
70
+ tracer = ::Rails.configuration.datadog_trace.fetch(:tracer)
71
+ key = get_key(resource)
72
+ if Thread.current[key]
73
+ # span was created by start_trace_cache, plan to re-use this one
74
+ Thread.current[key] = false
75
+ else
76
+ # Create a span now, as start_trace_cache was not called.
77
+ #
78
+ # This could typically happen if, for some reason the monkey-patching
79
+ # of the cache class did not work as expected. Doing this, we might
80
+ # loose some interesting parentship between some spans, because this
81
+ # span is created too late, and children won't "find" their parent.
82
+ # But, it's better than no span at all, and it case there is no child
83
+ # at all, it will work just as expected. In practice, it's required to
84
+ # have standard file cache work together with redis cache.
85
+ create_span(tracer)
86
+ end
87
+ span = tracer.active_span()
88
+ return unless span
89
+
90
+ begin
91
+ # finish the tracing and update the execution time
92
+ span.resource = resource
93
+ span.set_tag('rails.cache.backend', ::Rails.configuration.cache_store)
94
+ span.set_tag('rails.cache.key', payload.fetch(:key))
95
+
96
+ if payload[:exception]
97
+ error = payload[:exception]
98
+ span.status = 1
99
+ span.set_tag(Datadog::Ext::Errors::TYPE, error[0])
100
+ span.set_tag(Datadog::Ext::Errors::MSG, error[1])
101
+ end
102
+
103
+ ensure
104
+ span.start_time = start
105
+ span.finish_at(finish)
106
+ end
107
+ rescue StandardError => e
108
+ Datadog::Tracer.log.error(e.message)
109
+ end
110
+
111
+ private_class_method :create_span, :get_key, :start_trace_cache, :trace_cache
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,89 @@
1
+ module Datadog
2
+ # TODO[manu]: write docs
3
+ module RendererExtension
4
+ def render_template(*args)
5
+ ActiveSupport::Notifications.instrument('start_render_template.action_view')
6
+ super(*args)
7
+ end
8
+ end
9
+
10
+ # TODO[manu]: write docs
11
+ module PartialRendererExtension
12
+ def render_partial(*args)
13
+ ActiveSupport::Notifications.instrument('start_render_partial.action_view')
14
+ super(*args)
15
+ end
16
+ end
17
+
18
+ # CacheStoreReadExtension contains a new read function that notifies
19
+ # the framework of a read, then calls read.
20
+ module CacheStoreReadExtension
21
+ def read(*args)
22
+ ActiveSupport::Notifications.instrument('start_cache_read.active_support')
23
+ super(*args)
24
+ end
25
+ end
26
+
27
+ # CacheStoreFetchExtension contains a new fetch function that notifies
28
+ # the framework of a fetch, then calls fetch.
29
+ module CacheStoreFetchExtension
30
+ # It might seem redundant to instrument both read and fetch since
31
+ # fetch very often calls read. But there's no garantee of this, in
32
+ # some cases fetch can call directly read_entry without calling read.
33
+ def fetch(*args)
34
+ ActiveSupport::Notifications.instrument('start_cache_fetch.active_support')
35
+ super(*args)
36
+ end
37
+ end
38
+
39
+ # CacheWriteReadExtension contains a new read function that notifies
40
+ # the framework of a write, then calls write.
41
+ module CacheStoreWriteExtension
42
+ def write(*args)
43
+ ActiveSupport::Notifications.instrument('start_cache_write.active_support')
44
+ super(*args)
45
+ end
46
+ end
47
+
48
+ # CacheDeleteExtension contains a new read function that notifies
49
+ # the framework of a delete, then calls delete.
50
+ module CacheStoreDeleteExtension
51
+ def delete(*args)
52
+ ActiveSupport::Notifications.instrument('start_cache_delete.active_support')
53
+ super(*args)
54
+ end
55
+ end
56
+
57
+ # RailsPatcher contains function to patch the Rails libraries.
58
+ module RailsPatcher
59
+ module_function
60
+
61
+ def patch_renderer
62
+ ::ActionView::Renderer.prepend Datadog::RendererExtension
63
+ ::ActionView::PartialRenderer.prepend Datadog::PartialRendererExtension
64
+ end
65
+
66
+ def patch_cache_store
67
+ # When Redis is used, we can't only patch Cache::Store as it is
68
+ # Cache::RedisStore, a sub-class of it that is used, in practice.
69
+ # We need to do a per-method monkey patching as some of them might
70
+ # be redefined, and some of them not. The latest version of redis-activesupport
71
+ # redefines write but leaves untouched read and delete:
72
+ # https://github.com/redis-store/redis-activesupport/blob/master/lib/active_support/cache/redis_store.rb
73
+
74
+ { read: Datadog::CacheStoreReadExtension,
75
+ fetch: Datadog::CacheStoreFetchExtension,
76
+ write: Datadog::CacheStoreWriteExtension,
77
+ delete: Datadog::CacheStoreDeleteExtension }.each do |k, v|
78
+ c = if defined?(::ActiveSupport::Cache::RedisStore) &&
79
+ ::ActiveSupport::Cache::RedisStore.instance_methods(false).include?(k)
80
+ ::ActiveSupport::Cache::RedisStore
81
+ else
82
+ ::ActiveSupport::Cache::Store
83
+ end
84
+ Datadog::Tracer.log.debug("monkey patching #{c}.#{k} with #{v}.#{k}")
85
+ c.prepend v
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,107 @@
1
+ require 'ddtrace'
2
+ require 'ddtrace/ext/app_types'
3
+
4
+ require 'ddtrace/contrib/rails/core_extensions'
5
+ require 'ddtrace/contrib/rails/action_controller'
6
+ require 'ddtrace/contrib/rails/action_view'
7
+ require 'ddtrace/contrib/rails/active_record' if defined?(::ActiveRecord)
8
+ require 'ddtrace/contrib/rails/active_support'
9
+ require 'ddtrace/contrib/rails/utils'
10
+
11
+ module Datadog
12
+ module Contrib
13
+ # TODO[manu]: write docs
14
+ module Rails
15
+ # TODO[manu]: write docs
16
+ module Framework
17
+ # the default configuration
18
+ DEFAULT_CONFIG = {
19
+ enabled: true,
20
+ auto_instrument: true,
21
+ auto_instrument_redis: true,
22
+ default_service: 'rails-app',
23
+ default_cache_service: 'rails-cache',
24
+ template_base_path: 'views/',
25
+ tracer: Datadog.tracer,
26
+ debug: false,
27
+ trace_agent_hostname: Datadog::Writer::HOSTNAME,
28
+ trace_agent_port: Datadog::Writer::PORT
29
+ }.freeze
30
+
31
+ # configure Datadog settings
32
+ def self.configure(config)
33
+ # tracer defaults
34
+ # merge default configurations with users settings
35
+ user_config = config[:config].datadog_trace rescue {}
36
+ datadog_config = DEFAULT_CONFIG.merge(user_config)
37
+ datadog_config[:tracer].enabled = datadog_config[:enabled]
38
+
39
+ # set debug logging
40
+ Datadog::Tracer.debug_logging = datadog_config[:debug]
41
+
42
+ # set the address of the trace agent
43
+ datadog_config[:tracer].configure(
44
+ hostname: datadog_config[:trace_agent_hostname],
45
+ port: datadog_config[:trace_agent_port]
46
+ )
47
+
48
+ # set default service details
49
+ datadog_config[:tracer].set_service_info(
50
+ datadog_config[:default_service],
51
+ 'rails',
52
+ Datadog::Ext::AppTypes::WEB
53
+ )
54
+ datadog_config[:tracer].set_service_info(
55
+ datadog_config[:default_cache_service],
56
+ 'rails',
57
+ Datadog::Ext::AppTypes::CACHE
58
+ )
59
+
60
+ # set default database service details and store it in the configuration
61
+ if defined?(::ActiveRecord)
62
+ begin
63
+ conn_cfg = ::ActiveRecord::Base.connection_config()
64
+ adapter_name = Datadog::Contrib::Rails::Utils.normalize_vendor(conn_cfg[:adapter])
65
+ database_service = datadog_config.fetch(:default_database_service, adapter_name)
66
+ datadog_config[:default_database_service] = database_service
67
+ datadog_config[:tracer].set_service_info(
68
+ database_service,
69
+ adapter_name,
70
+ Datadog::Ext::AppTypes::DB
71
+ )
72
+ rescue StandardError => e
73
+ Datadog::Tracer.log.info("cannot configuring database service (#{e}), skipping activerecord instrumentation")
74
+ end
75
+ end
76
+
77
+ # update global configurations
78
+ ::Rails.configuration.datadog_trace = datadog_config
79
+ end
80
+
81
+ def self.auto_instrument_redis
82
+ Datadog::Tracer.log.debug('instrumenting redis')
83
+ return unless (defined? ::Rails.cache) && ::Rails.cache.respond_to?(:data)
84
+ Datadog::Tracer.log.debug('redis cache exists')
85
+ pin = Datadog::Pin.get_from(::Rails.cache.data)
86
+ return unless pin
87
+ Datadog::Tracer.log.debug('redis cache pin is set')
88
+ pin.tracer = nil unless ::Rails.configuration.datadog_trace[:auto_instrument_redis]
89
+ end
90
+
91
+ # automatically instrument all Rails component
92
+ def self.auto_instrument
93
+ return unless ::Rails.configuration.datadog_trace[:auto_instrument]
94
+ Datadog::Tracer.log.info('Detected Rails >= 3.x. Enabling auto-instrumentation for core components.')
95
+ Datadog::Contrib::Rails::ActionController.instrument()
96
+ Datadog::Contrib::Rails::ActionView.instrument()
97
+ Datadog::Contrib::Rails::ActiveRecord.instrument() if defined?(::ActiveRecord)
98
+ Datadog::Contrib::Rails::ActiveSupport.instrument()
99
+
100
+ # by default, Rails 3 doesn't instrument the cache system
101
+ return unless ::Rails::VERSION::MAJOR.to_i == 3
102
+ ::ActiveSupport::Cache::Store.instrument = true
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,42 @@
1
+ module Datadog
2
+ module Contrib
3
+ module Rails
4
+ # common utilities for Rails
5
+ module Utils
6
+ # in Rails the template name includes the template full path
7
+ # and it's better to avoid storing such information. This method
8
+ # returns the relative path from `views/` or the template name
9
+ # if a `views/` folder is not in the template full path. A wrong
10
+ # usage ensures that this method will not crash the tracing system.
11
+ def self.normalize_template_name(name)
12
+ return if name.nil?
13
+
14
+ base_path = ::Rails.configuration.datadog_trace.fetch(:template_base_path, 'views/')
15
+ sections_view = name.split(base_path)
16
+
17
+ if sections_view.length == 1
18
+ name.split('/')[-1]
19
+ else
20
+ sections_view[-1]
21
+ end
22
+ rescue
23
+ return name.to_s
24
+ end
25
+
26
+ # Return a canonical name for a type of database
27
+ def self.normalize_vendor(vendor)
28
+ case vendor
29
+ when nil
30
+ 'defaultdb'
31
+ when 'sqlite3'
32
+ 'sqlite'
33
+ when 'postgresql'
34
+ 'postgres'
35
+ else
36
+ vendor
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,72 @@
1
+ require 'ddtrace/ext/app_types'
2
+ require 'ddtrace/contrib/redis/tags'
3
+ require 'ddtrace/contrib/redis/quantize'
4
+
5
+ module Datadog
6
+ module Contrib
7
+ module Redis
8
+ SERVICE = 'redis'.freeze
9
+
10
+ DRIVER = 'redis.driver'.freeze
11
+
12
+ # TracedRedis is a wrapper so that caller can pin on parent object without knowing about client member.
13
+ module TracedRedis
14
+ def datadog_pin=(pin)
15
+ # Forward the pin to client, which actually traces calls.
16
+ Datadog::Pin.onto(client, pin)
17
+ end
18
+
19
+ def datadog_pin
20
+ # Get the pin from client, which actually traces calls.
21
+ Datadog::Pin.get_from(client)
22
+ end
23
+ end
24
+
25
+ # Datadog APM Redis integration.
26
+ module TracedRedisClient
27
+ def initialize(*args)
28
+ pin = Datadog::Pin.new(SERVICE, app: 'redis', app_type: Datadog::Ext::AppTypes::DB)
29
+ pin.onto(self)
30
+ super(*args)
31
+ end
32
+
33
+ def call(*args, &block)
34
+ pin = Datadog::Pin.get_from(self)
35
+ return super(*args, &block) unless pin
36
+
37
+ response = nil
38
+ pin.tracer.trace('redis.command') do |span|
39
+ span.service = pin.service
40
+ span.span_type = Datadog::Ext::Redis::TYPE
41
+ span.resource = Datadog::Contrib::Redis::Quantize.format_command_args(*args)
42
+ span.set_tag(Datadog::Ext::Redis::RAWCMD, span.resource)
43
+ Datadog::Contrib::Redis::Tags.set_common_tags(self, span)
44
+
45
+ response = super(*args, &block)
46
+ end
47
+
48
+ response
49
+ end
50
+
51
+ def call_pipeline(*args, &block)
52
+ pin = Datadog::Pin.get_from(self)
53
+ return super(*args, &block) unless pin
54
+
55
+ response = nil
56
+ pin.tracer.trace('redis.command') do |span|
57
+ span.service = pin.service
58
+ span.span_type = Datadog::Ext::Redis::TYPE
59
+ commands = args[0].commands.map { |c| Datadog::Contrib::Redis::Quantize.format_command_args(c) }
60
+ span.resource = commands.join("\n")
61
+ span.set_tag(Datadog::Ext::Redis::RAWCMD, span.resource)
62
+ Datadog::Contrib::Redis::Tags.set_common_tags(self, span)
63
+
64
+ response = super(*args, &block)
65
+ end
66
+
67
+ response
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end