elastic-apm 3.15.1 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/.jenkins_exclude.yml +13 -0
  3. data/.ci/.jenkins_ruby.yml +0 -1
  4. data/.ci/Jenkinsfile +1 -0
  5. data/.github/dependabot.yml +16 -0
  6. data/.rubocop.yml +0 -7
  7. data/CHANGELOG.asciidoc +77 -4
  8. data/Gemfile +4 -1
  9. data/README.md +12 -0
  10. data/SECURITY.md +7 -0
  11. data/bench/report.rb +1 -1
  12. data/bin/run-bdd +17 -0
  13. data/bin/run-tests +1 -1
  14. data/docker-compose.yml +1 -1
  15. data/docs/configuration.asciidoc +62 -147
  16. data/docs/release-notes.asciidoc +1 -0
  17. data/docs/upgrading.asciidoc +0 -27
  18. data/lib/elastic_apm.rb +12 -1
  19. data/lib/elastic_apm/agent.rb +7 -3
  20. data/lib/elastic_apm/central_config.rb +8 -7
  21. data/lib/elastic_apm/config.rb +2 -88
  22. data/lib/elastic_apm/config/regexp_list.rb +1 -1
  23. data/lib/elastic_apm/config/wildcard_pattern_list.rb +1 -1
  24. data/lib/elastic_apm/context/response.rb +1 -3
  25. data/lib/elastic_apm/fields.rb +88 -0
  26. data/lib/elastic_apm/grpc.rb +2 -4
  27. data/lib/elastic_apm/instrumenter.rb +1 -1
  28. data/lib/elastic_apm/metadata/cloud_info.rb +32 -5
  29. data/lib/elastic_apm/metadata/system_info.rb +14 -4
  30. data/lib/elastic_apm/metrics/cpu_mem_set.rb +1 -1
  31. data/lib/elastic_apm/normalizers.rb +2 -2
  32. data/lib/elastic_apm/normalizers/rails/active_record.rb +3 -3
  33. data/lib/elastic_apm/opentracing.rb +3 -2
  34. data/lib/elastic_apm/span.rb +26 -1
  35. data/lib/elastic_apm/span/context.rb +2 -1
  36. data/lib/elastic_apm/span/context/destination.rb +53 -40
  37. data/lib/elastic_apm/span_helpers.rb +6 -8
  38. data/lib/elastic_apm/spies.rb +20 -0
  39. data/lib/elastic_apm/spies/action_dispatch.rb +10 -9
  40. data/lib/elastic_apm/spies/azure_storage_table.rb +148 -0
  41. data/lib/elastic_apm/spies/dynamo_db.rb +12 -12
  42. data/lib/elastic_apm/spies/elasticsearch.rb +32 -29
  43. data/lib/elastic_apm/spies/faraday.rb +83 -63
  44. data/lib/elastic_apm/spies/http.rb +33 -34
  45. data/lib/elastic_apm/spies/mongo.rb +5 -3
  46. data/lib/elastic_apm/spies/net_http.rb +59 -56
  47. data/lib/elastic_apm/spies/rake.rb +28 -26
  48. data/lib/elastic_apm/spies/redis.rb +11 -10
  49. data/lib/elastic_apm/spies/resque.rb +18 -21
  50. data/lib/elastic_apm/spies/s3.rb +14 -15
  51. data/lib/elastic_apm/spies/sequel.rb +42 -48
  52. data/lib/elastic_apm/spies/sidekiq.rb +13 -15
  53. data/lib/elastic_apm/spies/sinatra.rb +20 -21
  54. data/lib/elastic_apm/spies/sns.rb +39 -44
  55. data/lib/elastic_apm/spies/sqs.rb +21 -31
  56. data/lib/elastic_apm/spies/tilt.rb +10 -9
  57. data/lib/elastic_apm/sql/tokenizer.rb +21 -5
  58. data/lib/elastic_apm/stacktrace_builder.rb +3 -1
  59. data/lib/elastic_apm/subscriber.rb +1 -0
  60. data/lib/elastic_apm/trace_context.rb +5 -13
  61. data/lib/elastic_apm/trace_context/traceparent.rb +5 -6
  62. data/lib/elastic_apm/transport/connection.rb +1 -1
  63. data/lib/elastic_apm/transport/connection/http.rb +4 -2
  64. data/lib/elastic_apm/transport/connection/proxy_pipe.rb +1 -2
  65. data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +5 -23
  66. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +3 -2
  67. data/lib/elastic_apm/transport/serializers/metricset_serializer.rb +2 -2
  68. data/lib/elastic_apm/transport/serializers/span_serializer.rb +12 -12
  69. data/lib/elastic_apm/transport/user_agent.rb +3 -2
  70. data/lib/elastic_apm/transport/worker.rb +1 -1
  71. data/lib/elastic_apm/util/deep_dup.rb +1 -1
  72. data/lib/elastic_apm/version.rb +1 -1
  73. metadata +12 -9
  74. data/lib/elastic_apm/sql.rb +0 -36
  75. data/lib/elastic_apm/sql_summarizer.rb +0 -53
@@ -22,6 +22,7 @@ module ElasticAPM
22
22
  module Spies
23
23
  # @api private
24
24
  class DynamoDBSpy
25
+ NAME = 'dynamodb'
25
26
  TYPE = 'db'
26
27
  SUBTYPE = 'dynamodb'
27
28
 
@@ -49,15 +50,12 @@ module ElasticAPM
49
50
  end
50
51
  end
51
52
 
52
- def install
53
- ::Aws::DynamoDB::Client.class_eval do
53
+ # @api private
54
+ module Ext
55
+ def self.prepended(mod)
54
56
  # Alias all available operations
55
- api.operation_names.each do |operation_name|
56
- alias :"#{operation_name}_without_apm" :"#{operation_name}"
57
-
57
+ mod.api.operation_names.each do |operation_name|
58
58
  define_method(operation_name) do |params = {}, options = {}|
59
- cloud = ElasticAPM::Span::Context::Destination::Cloud.new(region: config.region)
60
-
61
59
  context = ElasticAPM::Span::Context.new(
62
60
  db: {
63
61
  instance: config.region,
@@ -65,9 +63,8 @@ module ElasticAPM
65
63
  statement: params[:key_condition_expression]
66
64
  },
67
65
  destination: {
68
- cloud: cloud,
69
- resource: SUBTYPE,
70
- type: TYPE
66
+ service: { resource: "#{SUBTYPE}/#{config.region}" },
67
+ cloud: { region: config.region }
71
68
  }
72
69
  )
73
70
 
@@ -79,14 +76,17 @@ module ElasticAPM
79
76
  context: context
80
77
  ) do
81
78
  ElasticAPM::Spies::DynamoDBSpy.without_net_http do
82
- original_method = method("#{operation_name}_without_apm")
83
- original_method.call(params, options)
79
+ super(params, options)
84
80
  end
85
81
  end
86
82
  end
87
83
  end
88
84
  end
89
85
  end
86
+
87
+ def install
88
+ ::Aws::DynamoDB::Client.prepend(Ext)
89
+ end
90
90
  end
91
91
 
92
92
  register(
@@ -37,47 +37,50 @@ module ElasticAPM
37
37
  end
38
38
  end
39
39
 
40
- def install
41
- ::Elasticsearch::Transport::Client.class_eval do
42
- alias perform_request_without_apm perform_request
43
-
44
- def perform_request(method, path, *args, &block)
45
- unless ElasticAPM.current_transaction
46
- return perform_request_without_apm(method, path, *args, &block)
47
- end
40
+ # @api private
41
+ module Ext
42
+ def perform_request(method, path, *args, &block)
43
+ unless ElasticAPM.current_transaction
44
+ return super(method, path, *args, &block)
45
+ end
48
46
 
49
- name = format(NAME_FORMAT, method, path)
50
- statement = []
47
+ name = format(NAME_FORMAT, method, path)
48
+ statement = []
51
49
 
52
- statement << { params: args&.[](0) }
50
+ statement << { params: args&.[](0) }
53
51
 
54
- if ElasticAPM.agent.config.capture_elasticsearch_queries
55
- unless args[1].nil? || args[1].empty?
56
- statement << {
57
- body: ElasticAPM::Spies::ElasticsearchSpy
58
- .sanitizer.strip_from(args[1])
59
- }
60
- end
52
+ if ElasticAPM.agent.config.capture_elasticsearch_queries
53
+ unless args[1].nil? || args[1].empty?
54
+ body =
55
+ ElasticAPM::Spies::ElasticsearchSpy
56
+ .sanitizer.strip_from(args[1])
57
+ statement << { body: body }
61
58
  end
59
+ end
62
60
 
63
- context = Span::Context.new(
64
- db: { statement: statement.reduce({}, :merge).to_json },
65
- destination: {
61
+ context = Span::Context.new(
62
+ db: { statement: statement.reduce({}, :merge).to_json },
63
+ destination: {
64
+ service: {
66
65
  name: SUBTYPE,
67
66
  resource: SUBTYPE,
68
67
  type: TYPE
69
68
  }
70
- )
69
+ }
70
+ )
71
71
 
72
- ElasticAPM.with_span(
73
- name,
74
- TYPE,
75
- subtype: SUBTYPE,
76
- context: context
77
- ) { perform_request_without_apm(method, path, *args, &block) }
78
- end
72
+ ElasticAPM.with_span(
73
+ name,
74
+ TYPE,
75
+ subtype: SUBTYPE,
76
+ context: context
77
+ ) { super(method, path, *args, &block) }
79
78
  end
80
79
  end
80
+
81
+ def install
82
+ ::Elasticsearch::Transport::Client.prepend(Ext)
83
+ end
81
84
  end
82
85
 
83
86
  register(
@@ -22,88 +22,108 @@ module ElasticAPM
22
22
  module Spies
23
23
  # @api private
24
24
  class FaradaySpy
25
- TYPE = 'ext'
26
- SUBTYPE = 'faraday'
25
+ DISABLE_KEY = :__elastic_apm_faraday_disabled
26
+ TYPE = 'external'
27
+ SUBTYPE = 'http'
27
28
 
28
- def self.without_net_http
29
- return yield unless defined?(NetHTTPSpy)
29
+ class << self
30
+ def disabled=(disabled)
31
+ Thread.current[DISABLE_KEY] = disabled
32
+ end
33
+
34
+ def disabled?
35
+ Thread.current[DISABLE_KEY] ||= false
36
+ end
30
37
 
31
- # rubocop:disable Style/ExplicitBlockArgument
32
- ElasticAPM::Spies::NetHTTPSpy.disable_in do
33
- yield
38
+ def disable_in
39
+ self.disabled = true
40
+
41
+ begin
42
+ yield
43
+ ensure
44
+ self.disabled = false
45
+ end
34
46
  end
35
- # rubocop:enable Style/ExplicitBlockArgument
36
47
  end
37
48
 
38
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
39
- def install
40
- ::Faraday::Connection.class_eval do
41
- alias run_request_without_apm run_request
49
+ # @api private
50
+ module Ext
51
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
52
+ def run_request(method, url, body, headers, &block)
53
+ unless (transaction = ElasticAPM.current_transaction)
54
+ return super(method, url, body, headers, &block)
55
+ end
42
56
 
43
- def run_request(method, url, body, headers, &block)
44
- unless (transaction = ElasticAPM.current_transaction)
45
- return run_request_without_apm(method, url, body, headers, &block)
46
- end
57
+ if ElasticAPM::Spies::FaradaySpy.disabled?
58
+ return super(method, url, body, headers, &block)
59
+ end
47
60
 
48
- uri = URI(build_url(url))
61
+ uri = URI(build_url(url))
49
62
 
50
- # If url is set inside block it isn't available until yield,
51
- # so we temporarily build the request to yield. This could be a
52
- # problem if the block has side effects as it will be yielded twice
53
- # ~mikker
54
- unless uri.host
55
- tmp_request = build_request(method) do |req|
56
- yield(req) if block_given?
57
- end
58
- uri = tmp_request.path && URI(tmp_request.path)
63
+ # If url is set inside block it isn't available until yield,
64
+ # so we temporarily build the request to yield. This could be a
65
+ # problem if the block has side effects as it will be yielded twice
66
+ # ~mikker
67
+ unless uri.host
68
+ tmp_request = build_request(method) do |req|
69
+ yield(req) if block_given?
59
70
  end
71
+ uri = tmp_request.path && URI(tmp_request.path)
72
+ end
60
73
 
61
- host = uri&.host || 'localhost'
74
+ host = uri&.host || 'localhost'
62
75
 
63
- upcased_method = method.to_s.upcase
76
+ upcased_method = method.to_s.upcase
64
77
 
65
- if uri
66
- destination = ElasticAPM::Span::Context::Destination.from_uri(uri)
78
+ if uri
79
+ destination = ElasticAPM::Span::Context::Destination.from_uri(uri, type: SUBTYPE)
67
80
 
68
- context =
69
- ElasticAPM::Span::Context.new(
70
- http: { url: uri, method: upcased_method },
71
- destination: destination
72
- )
73
- else
74
- context =
75
- ElasticAPM::Span::Context.new(http: { url: uri, method: upcased_method })
76
- end
81
+ context =
82
+ ElasticAPM::Span::Context.new(
83
+ http: { url: uri, method: upcased_method },
84
+ destination: destination
85
+ )
86
+ else
87
+ context =
88
+ ElasticAPM::Span::Context.new(http: { url: uri, method: upcased_method })
89
+ end
90
+
91
+ context =
92
+ ElasticAPM::Span::Context.new(
93
+ http: { url: uri, method: upcased_method },
94
+ destination: destination
95
+ )
96
+
97
+ ElasticAPM.with_span(
98
+ "#{upcased_method} #{host}",
99
+ TYPE,
100
+ subtype: SUBTYPE,
101
+ context: context
102
+ ) do |span|
103
+ ElasticAPM::Spies.without_net_http do
104
+ trace_context = span&.trace_context || transaction.trace_context
105
+
106
+ result = super(method, url, body, headers) do |req|
107
+ trace_context.apply_headers { |k, v| req[k] = v }
108
+
109
+ yield req if block
110
+ end
77
111
 
78
- ElasticAPM.with_span(
79
- "#{upcased_method} #{host}",
80
- TYPE,
81
- subtype: SUBTYPE,
82
- action: upcased_method,
83
- context: context
84
- ) do |span|
85
- ElasticAPM::Spies::FaradaySpy.without_net_http do
86
- trace_context = span&.trace_context || transaction.trace_context
87
-
88
- result =
89
- run_request_without_apm(method, url, body, headers) do |req|
90
- trace_context.apply_headers { |k, v| req[k] = v }
91
-
92
- yield req if block_given?
93
- end
94
-
95
- if (http = span&.context&.http)
96
- http.status_code = result.status.to_s
97
- end
98
-
99
- span&.outcome = Span::Outcome.from_http_status(result.status)
100
- result
112
+ if (http = span&.context&.http)
113
+ http.status_code = result.status.to_s
101
114
  end
115
+
116
+ span&.outcome = Span::Outcome.from_http_status(result.status)
117
+ result
102
118
  end
103
119
  end
104
120
  end
105
121
  end
106
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
122
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
123
+
124
+ def install
125
+ ::Faraday::Connection.prepend(Ext)
126
+ end
107
127
  end
108
128
 
109
129
  register 'Faraday', 'faraday', FaradaySpy.new
@@ -22,51 +22,50 @@ module ElasticAPM
22
22
  module Spies
23
23
  # @api private
24
24
  class HTTPSpy
25
- TYPE = 'ext'
26
- SUBTYPE = 'http_rb'
27
- def install
28
- ::HTTP::Client.class_eval do
29
- alias perform_without_apm perform
30
-
31
- def perform(req, options)
32
- unless (transaction = ElasticAPM.current_transaction)
33
- return perform_without_apm(req, options)
34
- end
25
+ TYPE = 'external'
26
+ SUBTYPE = 'http'
35
27
 
36
- method = req.verb.to_s.upcase
37
- host = req.uri.host
28
+ # @api private
29
+ module Ext
30
+ def perform(req, options)
31
+ unless (transaction = ElasticAPM.current_transaction)
32
+ return super(req, options)
33
+ end
38
34
 
39
- destination =
40
- ElasticAPM::Span::Context::Destination.from_uri(req.uri)
41
- context = ElasticAPM::Span::Context.new(
42
- http: { url: req.uri, method: method },
43
- destination: destination
44
- )
35
+ method = req.verb.to_s.upcase
36
+ host = req.uri.host
45
37
 
46
- name = "#{method} #{host}"
38
+ context = ElasticAPM::Span::Context.new(
39
+ http: { url: req.uri, method: method },
40
+ destination: ElasticAPM::Span::Context::Destination.from_uri(req.uri, type: SUBTYPE)
41
+ )
47
42
 
48
- ElasticAPM.with_span(
49
- name,
50
- TYPE,
51
- subtype: SUBTYPE,
52
- action: method,
53
- context: context
54
- ) do |span|
55
- trace_context = span&.trace_context || transaction.trace_context
56
- trace_context.apply_headers { |key, value| req[key] = value }
43
+ name = "#{method} #{host}"
57
44
 
58
- result = perform_without_apm(req, options)
45
+ ElasticAPM.with_span(
46
+ name,
47
+ TYPE,
48
+ subtype: SUBTYPE,
49
+ context: context
50
+ ) do |span|
51
+ trace_context = span&.trace_context || transaction.trace_context
52
+ trace_context.apply_headers { |key, value| req[key] = value }
59
53
 
60
- if (http = span&.context&.http)
61
- http.status_code = result.status.to_s
62
- end
54
+ result = super(req, options)
63
55
 
64
- span&.outcome = Span::Outcome.from_http_status(result.status)
65
- result
56
+ if (http = span&.context&.http)
57
+ http.status_code = result.status.to_s
66
58
  end
59
+
60
+ span&.outcome = Span::Outcome.from_http_status(result.status)
61
+ result
67
62
  end
68
63
  end
69
64
  end
65
+
66
+ def install
67
+ ::HTTP::Client.prepend(Ext)
68
+ end
70
69
  end
71
70
 
72
71
  register 'HTTP', 'http', HTTPSpy.new
@@ -108,9 +108,11 @@ module ElasticAPM
108
108
  user: nil
109
109
  },
110
110
  destination: {
111
- name: SUBTYPE,
112
- resource: SUBTYPE,
113
- type: TYPE
111
+ service: {
112
+ name: SUBTYPE,
113
+ resource: SUBTYPE,
114
+ type: TYPE
115
+ }
114
116
  }
115
117
  )
116
118
  end
@@ -22,17 +22,17 @@ module ElasticAPM
22
22
  module Spies
23
23
  # @api private
24
24
  class NetHTTPSpy
25
- KEY = :__elastic_apm_net_http_disabled
26
- TYPE = 'ext'
27
- SUBTYPE = 'net_http'
25
+ DISABLE_KEY = :__elastic_apm_net_http_disabled
26
+ TYPE = 'external'
27
+ SUBTYPE = 'http'
28
28
 
29
29
  class << self
30
30
  def disabled=(disabled)
31
- Thread.current[KEY] = disabled
31
+ Thread.current[DISABLE_KEY] = disabled
32
32
  end
33
33
 
34
34
  def disabled?
35
- Thread.current[KEY] ||= false
35
+ Thread.current[DISABLE_KEY] ||= false
36
36
  end
37
37
 
38
38
  def disable_in
@@ -46,65 +46,68 @@ module ElasticAPM
46
46
  end
47
47
  end
48
48
 
49
- # rubocop:disable Metrics/CyclomaticComplexity
50
- # rubocop:disable Metrics/PerceivedComplexity
51
- def install
52
- Net::HTTP.class_eval do
53
- alias request_without_apm request
49
+ # @api private
50
+ module Ext
51
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
52
+ def request(req, body = nil, &block)
53
+ unless (transaction = ElasticAPM.current_transaction)
54
+ return super(req, body, &block)
55
+ end
54
56
 
55
- def request(req, body = nil, &block)
56
- unless (transaction = ElasticAPM.current_transaction)
57
- return request_without_apm(req, body, &block)
58
- end
57
+ if ElasticAPM::Spies::NetHTTPSpy.disabled?
58
+ return super(req, body, &block)
59
+ end
59
60
 
60
- if ElasticAPM::Spies::NetHTTPSpy.disabled?
61
- return request_without_apm(req, body, &block)
61
+ host = req['host']&.split(':')&.first || address || 'localhost'
62
+ method = req.method.to_s.upcase
63
+
64
+ uri_or_path = URI(req.path)
65
+
66
+ # Support the case where a whole url is passed as a path to a nil host
67
+ uri =
68
+ if uri_or_path.host
69
+ uri_or_path
70
+ else
71
+ path, query = req.path.split('?')
72
+ url = use_ssl? ? +'https://' : +'http://'
73
+ url << host
74
+ url << ":#{port}" if port
75
+ url << path
76
+ url << "?#{query}" if query
77
+ URI(url)
62
78
  end
63
79
 
64
- host = req['host']&.split(':')&.first || address || 'localhost'
65
- method = req.method.to_s.upcase
66
- path, query = req.path.split('?')
67
-
68
- url = use_ssl? ? +'https://' : +'http://'
69
- url << host
70
- url << ":#{port}" if port
71
- url << path
72
- url << "?#{query}" if query
73
- uri = URI(url)
74
-
75
- destination =
76
- ElasticAPM::Span::Context::Destination.from_uri(uri)
77
-
78
- context =
79
- ElasticAPM::Span::Context.new(
80
- http: { url: uri, method: method },
81
- destination: destination
82
- )
83
-
84
- ElasticAPM.with_span(
85
- "#{method} #{host}",
86
- TYPE,
87
- subtype: SUBTYPE,
88
- action: method,
89
- context: context
90
- ) do |span|
91
- trace_context = span&.trace_context || transaction.trace_context
92
- trace_context.apply_headers { |key, value| req[key] = value }
93
-
94
- result = request_without_apm(req, body, &block)
95
-
96
- if (http = span&.context&.http)
97
- http.status_code = result.code
98
- end
99
-
100
- span&.outcome = Span::Outcome.from_http_status(result.code)
101
- result
80
+ context =
81
+ ElasticAPM::Span::Context.new(
82
+ http: { url: uri, method: method },
83
+ destination: ElasticAPM::Span::Context::Destination.from_uri(uri, type: SUBTYPE)
84
+ )
85
+
86
+ ElasticAPM.with_span(
87
+ "#{method} #{host}",
88
+ TYPE,
89
+ subtype: SUBTYPE,
90
+ context: context
91
+ ) do |span|
92
+ trace_context = span&.trace_context || transaction.trace_context
93
+ trace_context.apply_headers { |key, value| req[key] = value }
94
+
95
+ result = super(req, body, &block)
96
+
97
+ if (http = span&.context&.http)
98
+ http.status_code = result.code
102
99
  end
100
+
101
+ span&.outcome = Span::Outcome.from_http_status(result.code)
102
+ result
103
103
  end
104
104
  end
105
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
106
+ end
107
+
108
+ def install
109
+ Net::HTTP.prepend(Ext)
105
110
  end
106
- # rubocop:enable Metrics/CyclomaticComplexity
107
- # rubocop:enable Metrics/PerceivedComplexity
108
111
  end
109
112
 
110
113
  register 'Net::HTTP', 'net/http', NetHTTPSpy.new