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