elastic-apm 3.11.0 → 3.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/.ci/.jenkins_codecov.yml +1 -1
  3. data/.ci/.jenkins_exclude.yml +46 -61
  4. data/.ci/.jenkins_framework.yml +3 -4
  5. data/.ci/.jenkins_master_framework.yml +1 -1
  6. data/.ci/.jenkins_ruby.yml +1 -3
  7. data/.ci/Jenkinsfile +22 -2
  8. data/.ci/docker/jruby/11-jdk/Dockerfile +2 -1
  9. data/.ci/docker/jruby/12-jdk/Dockerfile +2 -1
  10. data/.ci/docker/jruby/13-jdk/Dockerfile +2 -1
  11. data/.ci/docker/jruby/7-jdk/Dockerfile +2 -1
  12. data/.ci/docker/jruby/8-jdk/Dockerfile +2 -1
  13. data/.github/labeler-config.yml +3 -0
  14. data/.github/workflows/addToProject.yml +29 -0
  15. data/.github/workflows/labeler.yml +16 -0
  16. data/.rubocop.yml +33 -4
  17. data/CHANGELOG.asciidoc +58 -0
  18. data/Gemfile +23 -9
  19. data/Rakefile +10 -10
  20. data/docs/api.asciidoc +2 -1
  21. data/docs/configuration.asciidoc +20 -3
  22. data/docs/supported-technologies.asciidoc +2 -0
  23. data/lib/elastic_apm.rb +14 -2
  24. data/lib/elastic_apm/central_config.rb +7 -2
  25. data/lib/elastic_apm/config.rb +35 -18
  26. data/lib/elastic_apm/config/log_level_map.rb +47 -0
  27. data/lib/elastic_apm/context.rb +2 -8
  28. data/lib/elastic_apm/graphql.rb +2 -0
  29. data/lib/elastic_apm/grpc.rb +3 -3
  30. data/lib/elastic_apm/instrumenter.rb +1 -1
  31. data/lib/elastic_apm/metadata/cloud_info.rb +6 -4
  32. data/lib/elastic_apm/metadata/service_info.rb +2 -2
  33. data/lib/elastic_apm/metadata/system_info/container_info.rb +16 -5
  34. data/lib/elastic_apm/metrics.rb +1 -0
  35. data/lib/elastic_apm/metrics/cpu_mem_set.rb +1 -0
  36. data/lib/elastic_apm/middleware.rb +8 -3
  37. data/lib/elastic_apm/normalizers/rails/active_record.rb +16 -4
  38. data/lib/elastic_apm/opentracing.rb +2 -1
  39. data/lib/elastic_apm/span.rb +13 -2
  40. data/lib/elastic_apm/span/context/db.rb +1 -1
  41. data/lib/elastic_apm/span/context/http.rb +2 -0
  42. data/lib/elastic_apm/spies/delayed_job.rb +11 -3
  43. data/lib/elastic_apm/spies/dynamo_db.rb +8 -1
  44. data/lib/elastic_apm/spies/elasticsearch.rb +4 -2
  45. data/lib/elastic_apm/spies/faraday.rb +19 -11
  46. data/lib/elastic_apm/spies/http.rb +1 -0
  47. data/lib/elastic_apm/spies/mongo.rb +10 -2
  48. data/lib/elastic_apm/spies/net_http.rb +4 -1
  49. data/lib/elastic_apm/spies/rake.rb +4 -2
  50. data/lib/elastic_apm/spies/resque.rb +4 -2
  51. data/lib/elastic_apm/spies/sequel.rb +12 -1
  52. data/lib/elastic_apm/spies/shoryuken.rb +2 -0
  53. data/lib/elastic_apm/spies/sidekiq.rb +2 -0
  54. data/lib/elastic_apm/spies/sneakers.rb +2 -0
  55. data/lib/elastic_apm/spies/sucker_punch.rb +2 -0
  56. data/lib/elastic_apm/sql/signature.rb +4 -2
  57. data/lib/elastic_apm/sql/tokenizer.rb +2 -2
  58. data/lib/elastic_apm/stacktrace/frame.rb +1 -0
  59. data/lib/elastic_apm/stacktrace_builder.rb +2 -4
  60. data/lib/elastic_apm/trace_context.rb +0 -2
  61. data/lib/elastic_apm/trace_context/traceparent.rb +0 -2
  62. data/lib/elastic_apm/trace_context/tracestate.rb +7 -5
  63. data/lib/elastic_apm/transaction.rb +17 -4
  64. data/lib/elastic_apm/transport/connection.rb +1 -1
  65. data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +8 -1
  66. data/lib/elastic_apm/transport/filters/secrets_filter.rb +32 -10
  67. data/lib/elastic_apm/transport/serializers.rb +1 -0
  68. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +23 -8
  69. data/lib/elastic_apm/transport/serializers/span_serializer.rb +2 -3
  70. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +1 -0
  71. data/lib/elastic_apm/util/deep_dup.rb +65 -0
  72. data/lib/elastic_apm/util/precision_validator.rb +1 -1
  73. data/lib/elastic_apm/version.rb +1 -1
  74. metadata +7 -2
@@ -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
@@ -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
@@ -239,7 +239,7 @@ module ElasticAPM
239
239
  def set_label(key, value)
240
240
  return unless current_transaction
241
241
 
242
- key = key.to_s.gsub(/[\."\*]/, '_').to_sym
242
+ key = key.to_s.gsub(/[."*]/, '_').to_sym
243
243
  current_transaction.context.labels[key] = value
244
244
  end
245
245
 
@@ -31,7 +31,7 @@ module ElasticAPM
31
31
 
32
32
  def initialize(config)
33
33
  @config = config
34
- @client = HTTP.timeout(0.1)
34
+ @client = HTTP.timeout(connect: 0.1, read: 0.1)
35
35
  end
36
36
 
37
37
  attr_reader :config
@@ -49,6 +49,7 @@ module ElasticAPM
49
49
  :region
50
50
  )
51
51
 
52
+ # rubocop:disable Metrics/CyclomaticComplexity
52
53
  def fetch!
53
54
  case config.cloud_provider
54
55
  when "aws"
@@ -67,13 +68,14 @@ module ElasticAPM
67
68
 
68
69
  self
69
70
  end
71
+ # rubocop:enable Metrics/CyclomaticComplexity
70
72
 
71
73
  private
72
74
 
73
75
  def fetch_aws
74
76
  resp = @client.get(AWS_URI)
75
77
 
76
- return unless resp.status === 200
78
+ return unless resp.status == 200
77
79
  return unless (metadata = JSON.parse(resp.body))
78
80
 
79
81
  self.provider = "aws"
@@ -89,7 +91,7 @@ module ElasticAPM
89
91
  def fetch_gcp
90
92
  resp = @client.headers("Metadata-Flavor" => "Google").get(GCP_URI)
91
93
 
92
- return unless resp.status === 200
94
+ return unless resp.status == 200
93
95
  return unless (metadata = JSON.parse(resp.body))
94
96
 
95
97
  zone = metadata["instance"]["zone"]&.split("/")&.at(-1)
@@ -109,7 +111,7 @@ module ElasticAPM
109
111
  def fetch_azure
110
112
  resp = @client.headers("Metadata" => "true").get(AZURE_URI)
111
113
 
112
- return unless resp.status === 200
114
+ return unless resp.status == 200
113
115
  return unless (metadata = JSON.parse(resp.body))
114
116
 
115
117
  self.provider = 'azure'
@@ -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)
@@ -57,10 +57,17 @@ module ElasticAPM
57
57
  private
58
58
 
59
59
  def subtype_for(payload)
60
- cached_adapter_name(
61
- payload[:connection]&.adapter_name ||
62
- ::ActiveRecord::Base.connection_config[:adapter]
63
- )
60
+ return cached_adapter_name(payload[:connection].adapter_name) if payload[:connection]
61
+
62
+ if can_attempt_connection_id_lookup?(payload)
63
+ begin
64
+ loaded_object = ObjectSpace._id2ref(payload[:connection_id])
65
+ return cached_adapter_name(loaded_object.adapter_name) if loaded_object.respond_to?(:adapter_name)
66
+ rescue RangeError # if connection object has somehow been garbage collected
67
+ end
68
+ end
69
+
70
+ cached_adapter_name(::ActiveRecord::Base.connection_config[:adapter])
64
71
  end
65
72
 
66
73
  def summarize(sql)
@@ -69,11 +76,16 @@ module ElasticAPM
69
76
 
70
77
  def cached_adapter_name(adapter_name)
71
78
  return UNKNOWN if adapter_name.nil? || adapter_name.empty?
79
+
72
80
  @adapters[adapter_name] ||
73
81
  (@adapters[adapter_name] = adapter_name.downcase)
74
82
  rescue StandardError
75
83
  nil
76
84
  end
85
+
86
+ def can_attempt_connection_id_lookup?(payload)
87
+ RUBY_ENGINE == "ruby" && payload[:connection_id] && ObjectSpace.respond_to?(:_id2ref)
88
+ end
77
89
  end
78
90
  end
79
91
  end
@@ -123,6 +123,7 @@ module ElasticAPM
123
123
  end
124
124
 
125
125
  attr_accessor :trace_context
126
+
126
127
  def_delegators :trace_context, :trace_id, :id, :parent_id
127
128
 
128
129
  def self.from_header(header)
@@ -345,7 +346,7 @@ module ElasticAPM
345
346
  context = context_from_child_of(child_of) ||
346
347
  context_from_references(references) ||
347
348
  context_from_active_scope(ignore_active_scope)
348
- return context.child if context&.respond_to?(:child)
349
+ return context.child if context.respond_to?(:child)
349
350
 
350
351
  context
351
352
  end
@@ -25,6 +25,17 @@ module ElasticAPM
25
25
  extend Forwardable
26
26
  include ChildDurations::Methods
27
27
 
28
+ # @api private
29
+ class Outcome
30
+ FAILURE = "failure"
31
+ SUCCESS = "success"
32
+ UNKNOWN = "unknown"
33
+
34
+ def self.from_http_status(code)
35
+ code.to_i >= 400 ? FAILURE : SUCCESS
36
+ end
37
+ end
38
+
28
39
  DEFAULT_TYPE = 'custom'
29
40
 
30
41
  # rubocop:disable Metrics/ParameterLists
@@ -38,8 +49,7 @@ module ElasticAPM
38
49
  action: nil,
39
50
  context: nil,
40
51
  stacktrace_builder: nil,
41
- sync: nil,
42
- sample_rate: nil
52
+ sync: nil
43
53
  )
44
54
  @name = name
45
55
 
@@ -67,6 +77,7 @@ module ElasticAPM
67
77
  :action,
68
78
  :name,
69
79
  :original_backtrace,
80
+ :outcome,
70
81
  :subtype,
71
82
  :trace_context,
72
83
  :type
@@ -30,7 +30,7 @@ module ElasticAPM
30
30
  rows_affected: nil
31
31
  )
32
32
  @instance = instance
33
- @statement = statement
33
+ @statement = statement&.encode('utf-8', invalid: :replace, undef: :replace)
34
34
  @type = type
35
35
  @user = user
36
36
  @rows_affected = rows_affected
@@ -33,6 +33,8 @@ module ElasticAPM
33
33
  private
34
34
 
35
35
  def sanitize_url(uri_or_str)
36
+ return unless uri_or_str
37
+
36
38
  uri = uri_or_str.is_a?(URI) ? uri_or_str.dup : URI(uri_or_str)
37
39
  uri.password = nil
38
40
  uri.to_s
@@ -38,26 +38,34 @@ module ElasticAPM
38
38
  end
39
39
 
40
40
  def self.invoke_job(job, *args, &block)
41
- job_name = name_from_payload(job.payload_object)
41
+ job_name = job_name(job)
42
42
  transaction = ElasticAPM.start_transaction(job_name, TYPE)
43
43
  job.invoke_job_without_apm(*args, &block)
44
44
  transaction&.done 'success'
45
+ transaction&.outcome = Transaction::Outcome::SUCCESS
45
46
  rescue ::Exception => e
46
47
  ElasticAPM.report(e, handled: false)
47
48
  transaction&.done 'error'
49
+ transaction&.outcome = Transaction::Outcome::FAILURE
48
50
  raise
49
51
  ensure
50
52
  ElasticAPM.end_transaction
51
53
  end
52
54
 
53
- def self.name_from_payload(payload_object)
55
+ def self.job_name(job)
56
+ payload_object = job.payload_object
57
+
54
58
  if payload_object.is_a?(::Delayed::PerformableMethod)
55
59
  performable_method_name(payload_object)
56
- elsif payload_object.class.name == 'ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper'
60
+ elsif payload_object.instance_of?(
61
+ ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper
62
+ )
57
63
  payload_object.job_data['job_class']
58
64
  else
59
65
  payload_object.class.name
60
66
  end
67
+ rescue
68
+ job.name
61
69
  end
62
70
 
63
71
  def self.performable_method_name(payload_object)
@@ -25,9 +25,11 @@ module ElasticAPM
25
25
  def self.without_net_http
26
26
  return yield unless defined?(NetHTTPSpy)
27
27
 
28
+ # rubocop:disable Style/ExplicitBlockArgument
28
29
  ElasticAPM::Spies::NetHTTPSpy.disable_in do
29
30
  yield
30
31
  end
32
+ # rubocop:enable Style/ExplicitBlockArgument
31
33
  end
32
34
 
33
35
  def install
@@ -37,7 +39,12 @@ module ElasticAPM
37
39
  alias :"#{operation_name}_without_apm" :"#{operation_name}"
38
40
 
39
41
  define_method(operation_name) do |params = {}, options = {}|
40
- ElasticAPM.with_span(operation_name, 'db', subtype: 'dynamodb', action: operation_name) do
42
+ ElasticAPM.with_span(
43
+ operation_name,
44
+ 'db',
45
+ subtype: 'dynamodb',
46
+ action: operation_name
47
+ ) do
41
48
  ElasticAPM::Spies::DynamoDBSpy.without_net_http do
42
49
  original_method = method("#{operation_name}_without_apm")
43
50
  original_method.call(params, options)