elastic-apm 3.15.1 → 4.2.0

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