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
@@ -6,6 +6,7 @@
6
6
  All notable changes to this project will be documented here.
7
7
  This project adheres to http://semver.org/spec/v2.0.0.html[Semantic Versioning].
8
8
 
9
+ * <<release-notes-4.x>>
9
10
  * <<release-notes-3.x>>
10
11
  * <<release-notes-2.x>>
11
12
  * <<release-notes-1.x>>
@@ -16,30 +16,3 @@ We love all our products, but sometimes we must say goodbye to a release so that
16
16
  forward on future development and innovation.
17
17
  Our https://www.elastic.co/support/eol[End of life policy] defines how long a given release is considered supported,
18
18
  as well as how long a release is considered still in active development or maintenance.
19
- The table below is a simplified description of this policy.
20
-
21
- [options="header"]
22
- |====
23
- |Agent version |EOL Date |Maintained until
24
- |3.5.x |2021-07-17 | 3.6
25
- |3.4.x |2021-07-10 | 3.5
26
- |3.3.x |2021-06-04 | 3.4
27
- |3.2.x |2021-05-19 | 3.3
28
- |3.1.x |2021-04-21 | 3.2
29
- |3.0.x |2021-04-08 | 3.1
30
- |2.12.x |2021-04-01 |4.0
31
- |2.11.x |2021-03-23 |2.12
32
- |2.10.x |2021-03-03 |2.11
33
- |2.9.x |2020-12-25 |2.10
34
- |2.8.x |2020-11-20 |2.9
35
- |2.7.x |2020-11-07 |2.8
36
- |2.6.x |2020-09-19 |2.7
37
- |2.5.x |2020-09-01 |2.6
38
- |2.4.x |2020-08-27 |2.5
39
- |2.3.x |2020-07-29 |2.4
40
- |2.2.x |2020-07-22 |2.3
41
- |2.1.x |2020-06-04 |2.2
42
- |2.0.x |2020-05-14 |2.1
43
- |1.1.x |2020-03-07 |3.0
44
- |1.0.x |2019-12-29 |1.1
45
- |====
data/lib/elastic_apm.rb CHANGED
@@ -32,6 +32,7 @@ require 'elastic_apm/internal_error'
32
32
  require 'elastic_apm/logging'
33
33
 
34
34
  # Core
35
+ require 'elastic_apm/fields'
35
36
  require 'elastic_apm/agent'
36
37
  require 'elastic_apm/config'
37
38
  require 'elastic_apm/context'
@@ -380,6 +381,16 @@ module ElasticAPM
380
381
  agent&.set_user(user)
381
382
  end
382
383
 
384
+ # Set destination fields on the current span
385
+ #
386
+ # @param address [String] Destination address
387
+ # @param address [String] Destination address
388
+ # @param address [Hash] Destination service
389
+ # @param address [Hash] Destination cloud
390
+ def set_destination(address: nil, port: nil, service: nil, cloud: nil)
391
+ agent&.set_destination(address: address, port: port, service: service, cloud: cloud)
392
+ end
393
+
383
394
  # Provide a filter to transform payloads before sending them off
384
395
  #
385
396
  # @param key [Symbol] Unique filter key
@@ -387,7 +398,7 @@ module ElasticAPM
387
398
  # @yield [Hash] A filter. Used if provided. Otherwise using `callback`
388
399
  # @return [Bool] true
389
400
  def add_filter(key, callback = nil, &block)
390
- if callback.nil? && !block_given?
401
+ if callback.nil? && !block
391
402
  raise ArgumentError, '#add_filter needs either `callback\' or a block'
392
403
  end
393
404
 
@@ -127,7 +127,7 @@ module ElasticAPM
127
127
  end
128
128
 
129
129
  def stop
130
- debug 'Stopping agent'
130
+ info 'Stopping agent'
131
131
 
132
132
  central_config.stop
133
133
  metrics.stop
@@ -227,6 +227,10 @@ module ElasticAPM
227
227
  instrumenter.set_user(user)
228
228
  end
229
229
 
230
+ def set_destination(address: nil, port: nil, service: nil, cloud: nil)
231
+ current_span&.set_destination(address: nil, port: nil, service: nil, cloud: nil)
232
+ end
233
+
230
234
  def build_context(rack_env:, for_type:)
231
235
  @context_builder.build(rack_env: rack_env, for_type: for_type)
232
236
  end
@@ -276,8 +280,8 @@ module ElasticAPM
276
280
  def detect_forking!
277
281
  return if @pid == Process.pid
278
282
 
279
- config.logger.debug "Detected forking,
280
- restarting threads in process [PID:#{Process.pid}]"
283
+ config.logger.debug(
284
+ "Forked process detected, restarting threads in process [PID:#{Process.pid}]")
281
285
 
282
286
  central_config.handle_forking!
283
287
  transport.handle_forking!
@@ -66,9 +66,9 @@ module ElasticAPM
66
66
  def fetch_and_apply_config
67
67
  @promise =
68
68
  Concurrent::Promise
69
- .execute(&method(:fetch_config))
70
- .on_success(&method(:handle_success))
71
- .rescue(&method(:handle_error))
69
+ .execute { fetch_config }
70
+ .on_success { |resp| handle_success(resp) }
71
+ .rescue { |err| handle_error(err) }
72
72
  end
73
73
 
74
74
  def fetch_config
@@ -119,7 +119,7 @@ module ElasticAPM
119
119
  end
120
120
 
121
121
  if resp.status == 304
122
- info 'Received 304 Not Modified'
122
+ debug 'Received 304 Not Modified'
123
123
  else
124
124
  if resp.body && !resp.body.empty?
125
125
  update = JSON.parse(resp.body.to_s)
@@ -164,11 +164,12 @@ module ElasticAPM
164
164
  @server_url ||=
165
165
  config.server_url +
166
166
  '/config/v1/agents' \
167
- "?service.name=#{CGI.escape(config.service_name)}"
167
+ "?service.name=#{CGI.escape(config.service_name)}" \
168
+ "&service.environment=#{CGI.escape(config.environment || '')}"
168
169
  end
169
170
 
170
171
  def headers
171
- { 'Etag': @etag }
172
+ { 'If-None-Match': @etag }
172
173
  end
173
174
 
174
175
  def schedule_next_fetch(resp = nil)
@@ -182,7 +183,7 @@ module ElasticAPM
182
183
 
183
184
  @scheduled_task =
184
185
  Concurrent::ScheduledTask
185
- .execute(seconds, &method(:fetch_and_apply_config))
186
+ .execute(seconds) { fetch_and_apply_config }
186
187
  end
187
188
  end
188
189
  end
@@ -30,14 +30,8 @@ module ElasticAPM
30
30
  class Config
31
31
  extend Options
32
32
 
33
- DEPRECATED_OPTIONS = %i[].freeze
34
-
35
- # DEPRECATED: To align with other agents, change on next major bump to:
36
- # "password, passwd, pwd, secret, *key, *token*, *session*, *credit*,
37
- # *card*, authorization, set-cookie"
38
33
  SANITIZE_FIELD_NAMES_DEFAULT =
39
- %w[*password* *passwd* *pwd* *secret* *key* *token* *session*
40
- *credit* *card* *authorization* *set-cookie*].freeze
34
+ %w[password passwd pwd secret *key *token* *session* *credit* *card* authorization set-cookie].freeze
41
35
 
42
36
  # rubocop:disable Layout/LineLength, Layout/ExtraSpacing
43
37
  option :config_file, type: :string, default: 'config/elastic_apm.yml'
@@ -101,7 +95,6 @@ module ElasticAPM
101
95
  option :transaction_max_spans, type: :int, default: 500
102
96
  option :transaction_sample_rate, type: :float, default: 1.0, converter: RoundFloat.new
103
97
  option :use_elastic_traceparent_header, type: :bool, default: true
104
- option :use_legacy_sql_parser, type: :bool, default: false
105
98
  option :verify_server_cert, type: :bool, default: true
106
99
 
107
100
  # rubocop:enable Layout/LineLength, Layout/ExtraSpacing
@@ -140,6 +133,7 @@ module ElasticAPM
140
133
  def available_instrumentations
141
134
  %w[
142
135
  action_dispatch
136
+ azure_storage_table
143
137
  delayed_job
144
138
  dynamo_db
145
139
  elasticsearch
@@ -168,11 +162,6 @@ module ElasticAPM
168
162
  available_instrumentations - disable_instrumentations
169
163
  end
170
164
 
171
- def method_missing(name, *args)
172
- return super unless DEPRECATED_OPTIONS.include?(name)
173
- warn "The option `#{name}' has been removed."
174
- end
175
-
176
165
  def replace_options(new_options)
177
166
  return if new_options.nil? || new_options.empty?
178
167
  options_copy = @options.dup
@@ -201,15 +190,6 @@ module ElasticAPM
201
190
  metrics_interval > 0
202
191
  end
203
192
 
204
- # DEPRECATED: Remove this in next major version
205
- def sanitize_field_names=(value)
206
- list = WildcardPatternList.new.call(value)
207
- defaults = WildcardPatternList.new.call(SANITIZE_FIELD_NAMES_DEFAULT)
208
- # use regex pattern for comparisons
209
- get(:sanitize_field_names).value =
210
- defaults.concat(list).uniq(&:pattern)
211
- end
212
-
213
193
  def span_frames_min_duration?
214
194
  span_frames_min_duration != 0
215
195
  end
@@ -248,72 +228,6 @@ module ElasticAPM
248
228
  super.split.first + '>'
249
229
  end
250
230
 
251
- # Deprecations
252
-
253
- def default_tags=(value)
254
- warn '[DEPRECATED] The option default_tags has been renamed to ' \
255
- 'default_labels.'
256
- self.default_labels = value
257
- end
258
-
259
- def ignore_url_patterns=(value)
260
- unless value == self.class.schema[:ignore_url_patterns][:default]
261
- warn '[DEPRECATED] The option ignore_url_patterns is being removed. ' \
262
- 'Consider using transaction_ignore_urls instead.'
263
- end
264
-
265
- set(:ignore_url_patterns, value)
266
- end
267
-
268
- def custom_key_filters=(value)
269
- unless value == self.class.schema[:custom_key_filters][:default]
270
- warn '[DEPRECATED] The option custom_key_filters is being removed. ' \
271
- 'See sanitize_field_names for an alternative.'
272
- end
273
-
274
- set(:custom_key_filters, value)
275
- end
276
-
277
- def disabled_instrumentations
278
- disable_instrumentations
279
- end
280
-
281
- def active
282
- enabled
283
- end
284
- alias active? active
285
-
286
- def server_ca_cert
287
- server_ca_cert_file
288
- end
289
-
290
- def disabled_instrumentations=(value)
291
- warn '[DEPRECATED] The option disabled_instrumentations has been ' \
292
- 'renamed to disable_instrumentations to align with other agents.'
293
- self.disable_instrumentations = value
294
- end
295
-
296
- def use_experimental_sql_parser=(_value)
297
- warn '[DEPRECATED] The new SQL parser is now the default. To use the ' \
298
- 'old one, use use_legacy_sql_parser and please report why you ' \
299
- 'wish to do so.'
300
- end
301
-
302
- def active=(value)
303
- warn '[DEPRECATED] The option active has been renamed to enabled ' \
304
- 'to align with other agents and with the remote config.'
305
- self.enabled = value
306
- end
307
-
308
- def server_ca_cert=(value)
309
- unless value == self.class.schema[:server_ca_cert_file][:default]
310
- warn '[DEPRECATED] The option server_ca_cert has been ' \
311
- 'renamed to server_ca_cert_file to align with other agents.'
312
- end
313
-
314
- self.server_ca_cert_file = value
315
- end
316
-
317
231
  private
318
232
 
319
233
  def load_config_file
@@ -23,7 +23,7 @@ module ElasticAPM
23
23
  class RegexpList
24
24
  def call(value)
25
25
  value = value.is_a?(String) ? value.split(',') : Array(value)
26
- value.map(&Regexp.method(:new))
26
+ value.map { |p| Regexp.new(p) }
27
27
  end
28
28
  end
29
29
  end
@@ -59,7 +59,7 @@ module ElasticAPM
59
59
 
60
60
  def call(value)
61
61
  value = value.is_a?(String) ? value.split(',') : Array(value)
62
- value.map(&WildcardPattern.method(:new))
62
+ value.map { |p| WildcardPattern.new(p) }
63
63
  end
64
64
  end
65
65
  end
@@ -38,9 +38,7 @@ module ElasticAPM
38
38
  attr_reader :headers
39
39
 
40
40
  def headers=(headers)
41
- @headers = headers&.each_with_object({}) do |(k, v), hsh|
42
- hsh[k] = v.to_s
43
- end
41
+ @headers = headers&.transform_values { |v| v.to_s }
44
42
  end
45
43
  end
46
44
  end
@@ -0,0 +1,88 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+ #
18
+ # frozen_string_literal: true
19
+
20
+ module ElasticAPM
21
+ # An interface for creating simple, value holding objects that correspond to
22
+ # object fields in the API.
23
+ #
24
+ # Example:
25
+ # class MyThing
26
+ # include Fields
27
+ # field :name
28
+ # field :address, optional: true
29
+ # end
30
+ #
31
+ # MyThing.new(name: 'AJ').to_h
32
+ # # => { name: 'AJ' }
33
+ # MyThing.new().empty?
34
+ # # => true
35
+ module Fields
36
+ module InstanceMethods
37
+ def initialize(**attrs)
38
+ attrs.each do |key, value|
39
+ self.send(:"#{key}=", value)
40
+ end
41
+
42
+ super()
43
+ end
44
+
45
+ def empty?
46
+ self.class.fields.each do |key|
47
+ next if send(key)
48
+ next if optionals.include?(key)
49
+
50
+ return true
51
+ end
52
+
53
+ false
54
+ end
55
+
56
+ def to_h
57
+ self.class.fields.each_with_object({}) do |key, fields|
58
+ fields[key] = send(key)
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def optionals
65
+ self.class.optionals
66
+ end
67
+ end
68
+
69
+ module ClassMethods
70
+ def field(key, optional: false)
71
+ attr_accessor(key)
72
+
73
+ fields.push(key)
74
+ optionals.push(key) if optional
75
+ end
76
+
77
+ attr_reader :fields, :optionals
78
+ end
79
+
80
+ def self.included(cls)
81
+ cls.extend(ClassMethods)
82
+ cls.include(InstanceMethods)
83
+
84
+ cls.instance_variable_set(:@fields, [])
85
+ cls.instance_variable_set(:@optionals, [])
86
+ end
87
+ end
88
+ end
@@ -50,11 +50,9 @@ module ElasticAPM
50
50
 
51
51
  split_peer = URI.split(peer)
52
52
  destination = ElasticAPM::Span::Context::Destination.new(
53
- type: TYPE,
54
- name: SUBTYPE,
55
- resource: peer,
56
53
  address: split_peer[0],
57
- port: split_peer[6]
54
+ port: split_peer[6],
55
+ service: { type: TYPE, name: SUBTYPE, resource: peer }
58
56
  )
59
57
  ElasticAPM::Span::Context.new(destination: destination)
60
58
  end
@@ -89,7 +89,7 @@ module ElasticAPM
89
89
  end
90
90
 
91
91
  def subscriber=(subscriber)
92
- debug 'Registering subscriber'
92
+ debug 'Registering ActiveSupport::Notifications subscriber'
93
93
  @subscriber = subscriber
94
94
  @subscriber.register!
95
95
  end
@@ -57,9 +57,9 @@ module ElasticAPM
57
57
  when "gcp"
58
58
  fetch_gcp
59
59
  when "azure"
60
- fetch_azure
60
+ fetch_azure || read_azure_app_services
61
61
  when "auto"
62
- fetch_aws || fetch_gcp || fetch_azure
62
+ fetch_aws || fetch_gcp || fetch_azure || read_azure_app_services
63
63
  when "none"
64
64
  nil
65
65
  else
@@ -76,7 +76,7 @@ module ElasticAPM
76
76
  resp = @client.get(AWS_URI)
77
77
 
78
78
  return unless resp.status == 200
79
- return unless (metadata = JSON.parse(resp.body))
79
+ return unless (metadata = JSON.parse(resp.body.to_s))
80
80
 
81
81
  self.provider = "aws"
82
82
  self.account_id = metadata["accountId"]
@@ -92,7 +92,7 @@ module ElasticAPM
92
92
  resp = @client.headers("Metadata-Flavor" => "Google").get(GCP_URI)
93
93
 
94
94
  return unless resp.status == 200
95
- return unless (metadata = JSON.parse(resp.body))
95
+ return unless (metadata = JSON.parse(resp.body.to_s))
96
96
 
97
97
  zone = metadata["instance"]["zone"]&.split("/")&.at(-1)
98
98
 
@@ -112,7 +112,7 @@ module ElasticAPM
112
112
  resp = @client.headers("Metadata" => "true").get(AZURE_URI)
113
113
 
114
114
  return unless resp.status == 200
115
- return unless (metadata = JSON.parse(resp.body))
115
+ return unless (metadata = JSON.parse(resp.body.to_s))
116
116
 
117
117
  self.provider = 'azure'
118
118
  self.account_id = metadata["subscriptionId"]
@@ -125,6 +125,33 @@ module ElasticAPM
125
125
  rescue HTTP::TimeoutError, HTTP::ConnectionError
126
126
  nil
127
127
  end
128
+
129
+ def read_azure_app_services
130
+ owner_name, instance_id, site_name, resource_group =
131
+ ENV.values_at(
132
+ 'WEBSITE_OWNER_NAME',
133
+ 'WEBSITE_INSTANCE_ID',
134
+ 'WEBSITE_SITE_NAME',
135
+ 'WEBSITE_RESOURCE_GROUP'
136
+ )
137
+
138
+ return unless owner_name && instance_id && site_name && resource_group
139
+
140
+ self.provider = 'azure'
141
+ self.instance_id = instance_id
142
+ self.instance_name = site_name
143
+ self.project_name = resource_group
144
+ self.account_id, self.region = parse_azure_app_services_owner_name(owner_name)
145
+ end
146
+
147
+ private
148
+
149
+ def parse_azure_app_services_owner_name(owner_name)
150
+ id, rest = owner_name.split('+')
151
+ *_, region = rest.split('-')
152
+ region.gsub!(/webspace.*$/, '')
153
+ [id, region]
154
+ end
128
155
  end
129
156
  end
130
157
  end