elastic-apm 3.10.1 → 3.13.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/labeler-config.yml +3 -0
  3. data/.github/workflows/labeler.yml +16 -0
  4. data/.rubocop.yml +33 -4
  5. data/CHANGELOG.asciidoc +67 -0
  6. data/Gemfile +5 -6
  7. data/Rakefile +10 -10
  8. data/docs/api.asciidoc +2 -1
  9. data/docs/configuration.asciidoc +4 -3
  10. data/lib/elastic_apm.rb +14 -2
  11. data/lib/elastic_apm/central_config.rb +7 -2
  12. data/lib/elastic_apm/config.rb +51 -19
  13. data/lib/elastic_apm/config/log_level_map.rb +47 -0
  14. data/lib/elastic_apm/config/options.rb +2 -1
  15. data/lib/elastic_apm/config/round_float.rb +31 -0
  16. data/lib/elastic_apm/config/wildcard_pattern_list.rb +2 -0
  17. data/lib/elastic_apm/context.rb +2 -8
  18. data/lib/elastic_apm/graphql.rb +2 -0
  19. data/lib/elastic_apm/grpc.rb +3 -3
  20. data/lib/elastic_apm/instrumenter.rb +11 -4
  21. data/lib/elastic_apm/metadata.rb +3 -1
  22. data/lib/elastic_apm/metadata/cloud_info.rb +130 -0
  23. data/lib/elastic_apm/metadata/service_info.rb +2 -2
  24. data/lib/elastic_apm/metadata/system_info/container_info.rb +16 -5
  25. data/lib/elastic_apm/metrics.rb +1 -0
  26. data/lib/elastic_apm/metrics/cpu_mem_set.rb +1 -0
  27. data/lib/elastic_apm/middleware.rb +8 -3
  28. data/lib/elastic_apm/opentracing.rb +2 -1
  29. data/lib/elastic_apm/span.rb +14 -0
  30. data/lib/elastic_apm/span/context/db.rb +1 -1
  31. data/lib/elastic_apm/spies/delayed_job.rb +8 -2
  32. data/lib/elastic_apm/spies/dynamo_db.rb +8 -1
  33. data/lib/elastic_apm/spies/elasticsearch.rb +10 -2
  34. data/lib/elastic_apm/spies/faraday.rb +5 -2
  35. data/lib/elastic_apm/spies/http.rb +1 -0
  36. data/lib/elastic_apm/spies/mongo.rb +6 -2
  37. data/lib/elastic_apm/spies/net_http.rb +3 -0
  38. data/lib/elastic_apm/spies/rake.rb +4 -2
  39. data/lib/elastic_apm/spies/resque.rb +4 -2
  40. data/lib/elastic_apm/spies/sequel.rb +12 -1
  41. data/lib/elastic_apm/spies/shoryuken.rb +2 -0
  42. data/lib/elastic_apm/spies/sidekiq.rb +2 -0
  43. data/lib/elastic_apm/spies/sneakers.rb +2 -0
  44. data/lib/elastic_apm/spies/sucker_punch.rb +2 -0
  45. data/lib/elastic_apm/sql/signature.rb +4 -2
  46. data/lib/elastic_apm/sql/tokenizer.rb +2 -2
  47. data/lib/elastic_apm/stacktrace/frame.rb +1 -0
  48. data/lib/elastic_apm/stacktrace_builder.rb +2 -4
  49. data/lib/elastic_apm/trace_context.rb +1 -3
  50. data/lib/elastic_apm/trace_context/traceparent.rb +2 -6
  51. data/lib/elastic_apm/trace_context/tracestate.rb +114 -9
  52. data/lib/elastic_apm/transaction.rb +41 -7
  53. data/lib/elastic_apm/transport/connection.rb +2 -1
  54. data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +16 -16
  55. data/lib/elastic_apm/transport/filters/secrets_filter.rb +35 -12
  56. data/lib/elastic_apm/transport/serializers.rb +9 -6
  57. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +44 -4
  58. data/lib/elastic_apm/transport/serializers/span_serializer.rb +3 -3
  59. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +2 -0
  60. data/lib/elastic_apm/transport/user_agent.rb +3 -3
  61. data/lib/elastic_apm/transport/worker.rb +1 -0
  62. data/lib/elastic_apm/util.rb +2 -0
  63. data/lib/elastic_apm/util/deep_dup.rb +65 -0
  64. data/lib/elastic_apm/util/precision_validator.rb +46 -0
  65. data/lib/elastic_apm/version.rb +1 -1
  66. metadata +10 -3
@@ -0,0 +1,47 @@
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
+ class Config
22
+ # @api private
23
+ class LogLevelMap
24
+ LEVELS = {
25
+ debug: Logger::DEBUG,
26
+ info: Logger::INFO,
27
+ warn: Logger::WARN,
28
+ error: Logger::ERROR,
29
+ fatal: Logger::FATAL,
30
+ trace: Logger::DEBUG,
31
+ warning: Logger::WARN,
32
+ critical: Logger::FATAL,
33
+ off: Logger::FATAL
34
+ }.freeze
35
+
36
+ DEFAULT = Logger::INFO
37
+
38
+ def call(value)
39
+ if value.is_a?(Integer)
40
+ LEVELS.value?(value) ? value : DEFAULT
41
+ else
42
+ LEVELS.fetch(value.to_sym, DEFAULT)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -38,7 +38,8 @@ module ElasticAPM
38
38
  set(value || default)
39
39
  end
40
40
 
41
- attr_reader :key, :value, :default, :type
41
+ attr_reader :key, :default, :type, :converter
42
+ attr_accessor :value
42
43
 
43
44
  def set(value)
44
45
  @value = normalize(value)
@@ -0,0 +1,31 @@
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
+ require 'elastic_apm/util/precision_validator'
21
+
22
+ module ElasticAPM
23
+ class Config
24
+ # @api private
25
+ class RoundFloat
26
+ def call(value)
27
+ Util::PrecisionValidator.validate(value, precision: 4, minimum: 0.0001)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -27,6 +27,8 @@ module ElasticAPM
27
27
  @pattern = convert(str)
28
28
  end
29
29
 
30
+ attr_reader :pattern
31
+
30
32
  def match?(other)
31
33
  !!@pattern.match(other)
32
34
  end
@@ -36,14 +36,9 @@ module ElasticAPM
36
36
  Service = Struct.new(:framework)
37
37
  Framework = Struct.new(:name, :version)
38
38
 
39
- attr_accessor :request
40
- attr_accessor :response
41
- attr_accessor :user
42
- attr_reader :custom
43
- attr_reader :labels
44
- attr_reader :service
39
+ attr_accessor :request, :response, :user
40
+ attr_reader :custom, :labels, :service
45
41
 
46
- # rubocop:disable Metrics/CyclomaticComplexity
47
42
  def empty?
48
43
  return false if labels.any?
49
44
  return false if custom.any?
@@ -53,7 +48,6 @@ module ElasticAPM
53
48
 
54
49
  true
55
50
  end
56
- # rubocop:enable Metrics/CyclomaticComplexity
57
51
 
58
52
  def set_service(framework_name: nil, framework_version: nil)
59
53
  @service = Service.new(
@@ -48,6 +48,7 @@ module ElasticAPM
48
48
  # "authorized" => "graphql.authorized",
49
49
  }.freeze
50
50
 
51
+ # rubocop:disable Style/ExplicitBlockArgument
51
52
  def self.trace(key, data)
52
53
  return yield unless KEYS_TO_NAME.key?(key)
53
54
  return yield unless (transaction = ElasticAPM.current_transaction)
@@ -69,6 +70,7 @@ module ElasticAPM
69
70
 
70
71
  results
71
72
  end
73
+ # rubocop:enable Style/ExplicitBlockArgument
72
74
 
73
75
  class << self
74
76
  private
@@ -25,7 +25,7 @@ module ElasticAPM
25
25
  TYPE = 'external'
26
26
  SUBTYPE = 'grpc'
27
27
 
28
- # rubocop:disable Lint/UnusedMethodArgument
28
+ # rubocop:disable Lint/UnusedMethodArgument, Style/ExplicitBlockArgument
29
29
  def request_response(request:, call:, method:, metadata:)
30
30
  return yield unless (transaction = ElasticAPM.current_transaction)
31
31
  if (trace_context = transaction.trace_context)
@@ -40,7 +40,7 @@ module ElasticAPM
40
40
  yield
41
41
  end
42
42
  end
43
- # rubocop:enable Lint/UnusedMethodArgument
43
+ # rubocop:enable Lint/UnusedMethodArgument, Style/ExplicitBlockArgument
44
44
 
45
45
  private
46
46
 
@@ -71,7 +71,7 @@ module ElasticAPM
71
71
  transaction.done 'success'
72
72
  rescue ::Exception => e
73
73
  ElasticAPM.report(e, handled: false)
74
- transaction.done 'error' if transaction
74
+ transaction&.done 'error'
75
75
  raise
76
76
  ensure
77
77
  ElasticAPM.end_transaction
@@ -119,7 +119,13 @@ module ElasticAPM
119
119
  "Already inside #{transaction.inspect}"
120
120
  end
121
121
 
122
- sampled = trace_context ? trace_context.recorded? : random_sample?(config)
122
+ if trace_context
123
+ sampled = trace_context.recorded?
124
+ sample_rate = trace_context.tracestate.sample_rate
125
+ else
126
+ sampled = random_sample?(config)
127
+ sample_rate = config.transaction_sample_rate
128
+ end
123
129
 
124
130
  transaction =
125
131
  Transaction.new(
@@ -128,6 +134,7 @@ module ElasticAPM
128
134
  context: context,
129
135
  trace_context: trace_context,
130
136
  sampled: sampled,
137
+ sample_rate: sample_rate,
131
138
  config: config
132
139
  )
133
140
 
@@ -232,7 +239,7 @@ module ElasticAPM
232
239
  def set_label(key, value)
233
240
  return unless current_transaction
234
241
 
235
- key = key.to_s.gsub(/[\."\*]/, '_').to_sym
242
+ key = key.to_s.gsub(/[."*]/, '_').to_sym
236
243
  current_transaction.context.labels[key] = value
237
244
  end
238
245
 
@@ -259,7 +266,7 @@ module ElasticAPM
259
266
  end
260
267
 
261
268
  def update_transaction_metrics(transaction)
262
- return unless transaction.collect_metrics
269
+ return unless transaction.collect_metrics?
263
270
 
264
271
  tags = {
265
272
  'transaction.name': transaction.name,
@@ -298,7 +305,7 @@ module ElasticAPM
298
305
  end
299
306
 
300
307
  def update_span_metrics(span)
301
- return unless span.transaction.breakdown_metrics
308
+ return unless span.transaction.collect_metrics?
302
309
 
303
310
  tags = {
304
311
  'span.type': span.type,
@@ -25,12 +25,14 @@ module ElasticAPM
25
25
  @process = ProcessInfo.new(config)
26
26
  @system = SystemInfo.new(config)
27
27
  @labels = config.global_labels
28
+ @cloud = CloudInfo.new(config).fetch!
28
29
  end
29
30
 
30
- attr_reader :service, :process, :system, :labels
31
+ attr_reader :service, :process, :system, :cloud, :labels
31
32
  end
32
33
  end
33
34
 
34
35
  require 'elastic_apm/metadata/service_info'
35
36
  require 'elastic_apm/metadata/system_info'
36
37
  require 'elastic_apm/metadata/process_info'
38
+ require 'elastic_apm/metadata/cloud_info'
@@ -0,0 +1,130 @@
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
+ require "http"
21
+
22
+ module ElasticAPM
23
+ class Metadata
24
+ # @api private
25
+ class CloudInfo
26
+ include Logging
27
+
28
+ AWS_URI = "http://169.254.169.254/latest/dynamic/instance-identity/document"
29
+ GCP_URI = "http://metadata.google.internal/computeMetadata/v1/?recursive=true"
30
+ AZURE_URI = "http://169.254.169.254/metadata/instance/compute?api-version=2019-08-15"
31
+
32
+ def initialize(config)
33
+ @config = config
34
+ @client = HTTP.timeout(connect: 0.1, read: 0.1)
35
+ end
36
+
37
+ attr_reader :config
38
+
39
+ attr_accessor(
40
+ :account_id,
41
+ :account_name,
42
+ :instance_id,
43
+ :instance_name,
44
+ :machine_type,
45
+ :project_id,
46
+ :project_name,
47
+ :availability_zone,
48
+ :provider,
49
+ :region
50
+ )
51
+
52
+ # rubocop:disable Metrics/CyclomaticComplexity
53
+ def fetch!
54
+ case config.cloud_provider
55
+ when "aws"
56
+ fetch_aws
57
+ when "gcp"
58
+ fetch_gcp
59
+ when "azure"
60
+ fetch_azure
61
+ when "auto"
62
+ fetch_aws || fetch_gcp || fetch_azure
63
+ when "none"
64
+ nil
65
+ else
66
+ error("Unknown setting for cloud_provider '#{config.cloud_provider}'")
67
+ end
68
+
69
+ self
70
+ end
71
+ # rubocop:enable Metrics/CyclomaticComplexity
72
+
73
+ private
74
+
75
+ def fetch_aws
76
+ resp = @client.get(AWS_URI)
77
+
78
+ return unless resp.status == 200
79
+ return unless (metadata = JSON.parse(resp.body))
80
+
81
+ self.provider = "aws"
82
+ self.account_id = metadata["accountId"]
83
+ self.instance_id = metadata["instanceId"]
84
+ self.availability_zone = metadata["availabilityZone"]
85
+ self.machine_type = metadata["instanceType"]
86
+ self.region = metadata["region"]
87
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
88
+ nil
89
+ end
90
+
91
+ def fetch_gcp
92
+ resp = @client.headers("Metadata-Flavor" => "Google").get(GCP_URI)
93
+
94
+ return unless resp.status == 200
95
+ return unless (metadata = JSON.parse(resp.body))
96
+
97
+ zone = metadata["instance"]["zone"]&.split("/")&.at(-1)
98
+
99
+ self.provider = "gcp"
100
+ self.instance_id = metadata["instance"]["id"]
101
+ self.instance_name = metadata["instance"]["name"]
102
+ self.project_id = metadata["project"]["numericProjectId"]
103
+ self.project_name = metadata["project"]["projectId"]
104
+ self.availability_zone = zone
105
+ self.region = zone.split("-")[0..-2].join("-")
106
+ self.machine_type = metadata["instance"]["machineType"]
107
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
108
+ nil
109
+ end
110
+
111
+ def fetch_azure
112
+ resp = @client.headers("Metadata" => "true").get(AZURE_URI)
113
+
114
+ return unless resp.status == 200
115
+ return unless (metadata = JSON.parse(resp.body))
116
+
117
+ self.provider = 'azure'
118
+ self.account_id = metadata["subscriptionId"]
119
+ self.instance_id = metadata["vmId"]
120
+ self.instance_name = metadata["name"]
121
+ self.project_name = metadata["resourceGroupName"]
122
+ self.availability_zone = metadata["zone"]
123
+ self.machine_type = metadata["vmSize"]
124
+ self.region = metadata["location"]
125
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
126
+ nil
127
+ end
128
+ end
129
+ end
130
+ end
@@ -52,8 +52,8 @@ module ElasticAPM
52
52
  @version = @config.service_version || Util.git_sha
53
53
  end
54
54
 
55
- attr_reader :name, :node_name, :environment, :agent, :framework, :language,
56
- :runtime, :version
55
+ attr_reader :name, :node_name, :environment, :agent, :framework,
56
+ :language, :runtime, :version
57
57
 
58
58
  private
59
59
 
@@ -81,15 +81,23 @@ module ElasticAPM
81
81
  ENV.fetch('KUBERNETES_POD_UID', kubernetes_pod_uid)
82
82
  end
83
83
 
84
+ # rubocop:disable Style/RegexpLiteral
84
85
  CONTAINER_ID_REGEXES = [
85
86
  %r{^[[:xdigit:]]{64}$},
86
- %r{^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4,}$}
87
- ]
87
+ %r{
88
+ ^[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]
89
+ {4}-[[:xdigit:]]{4}-[[:xdigit:]]{4,}$
90
+ }x
91
+ ].freeze
88
92
  KUBEPODS_REGEXES = [
89
93
  %r{(?:^/kubepods[^\s]*/pod([^/]+)$)},
90
- %r{(?:^/kubepods\.slice/kubepods-[^/]+\.slice/kubepods-[^/]+-pod([^/]+)\.slice$)}
91
- ]
94
+ %r{
95
+ (?:^/kubepods\.slice/(kubepods-[^/]+\.slice/)?
96
+ kubepods[^/]*-pod([^/]+)\.slice$)
97
+ }x
98
+ ].freeze
92
99
  SYSTEMD_SCOPE_SUFFIX = '.scope'
100
+ # rubocop:enable Style/RegexpLiteral
93
101
 
94
102
  # rubocop:disable Metrics/PerceivedComplexity
95
103
  # rubocop:disable Metrics/CyclomaticComplexity
@@ -125,7 +133,10 @@ module ElasticAPM
125
133
  end
126
134
 
127
135
  if (kubepods_match = match_kubepods(directory))
128
- pod_id = kubepods_match[1] || kubepods_match[2]
136
+ unless (pod_id = kubepods_match[1])
137
+ pod_id = kubepods_match[2]
138
+ pod_id&.tr!('_', '-')
139
+ end
129
140
 
130
141
  self.container_id = container_id
131
142
  self.kubernetes_pod_uid = pod_id
@@ -40,6 +40,7 @@ module ElasticAPM
40
40
  end
41
41
 
42
42
  attr_reader :config, :sets, :callback
43
+
43
44
  def start
44
45
  unless config.collect_metrics?
45
46
  debug 'Skipping metrics'
@@ -214,6 +214,7 @@ module ElasticAPM
214
214
  # @api private
215
215
  class Meminfo
216
216
  attr_reader :total, :available, :page_size
217
+
217
218
  # rubocop:disable Metrics/PerceivedComplexity
218
219
  # rubocop:disable Metrics/CyclomaticComplexity
219
220
  def read!
@@ -41,9 +41,14 @@ module ElasticAPM
41
41
  ElasticAPM.report(e, context: context, handled: false)
42
42
  raise
43
43
  ensure
44
- if resp && transaction
45
- status, headers, _body = resp
46
- transaction.add_response(status, headers: headers.dup)
44
+ if transaction
45
+ if resp
46
+ status, headers, _body = resp
47
+ transaction.add_response(status, headers: headers.dup)
48
+ transaction&.outcome = Transaction::Outcome.from_http_status(status)
49
+ else
50
+ transaction&.outcome = Transaction::Outcome::FAILURE
51
+ end
47
52
  end
48
53
 
49
54
  ElasticAPM.end_transaction http_result(status)