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