elastic-apm 3.9.0 → 3.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.asciidoc +66 -0
  3. data/Gemfile +1 -3
  4. data/docs/api.asciidoc +2 -1
  5. data/docs/configuration.asciidoc +1 -0
  6. data/lib/elastic_apm.rb +14 -2
  7. data/lib/elastic_apm/config.rb +31 -5
  8. data/lib/elastic_apm/config/options.rb +2 -1
  9. data/lib/elastic_apm/config/round_float.rb +31 -0
  10. data/lib/elastic_apm/config/wildcard_pattern_list.rb +13 -1
  11. data/lib/elastic_apm/context_builder.rb +1 -1
  12. data/lib/elastic_apm/instrumenter.rb +10 -3
  13. data/lib/elastic_apm/metadata.rb +3 -1
  14. data/lib/elastic_apm/metadata/cloud_info.rb +128 -0
  15. data/lib/elastic_apm/metadata/system_info.rb +5 -3
  16. data/lib/elastic_apm/metadata/system_info/container_info.rb +28 -4
  17. data/lib/elastic_apm/middleware.rb +16 -5
  18. data/lib/elastic_apm/span.rb +19 -3
  19. data/lib/elastic_apm/spies/delayed_job.rb +6 -2
  20. data/lib/elastic_apm/spies/elasticsearch.rb +7 -1
  21. data/lib/elastic_apm/spies/faraday.rb +1 -0
  22. data/lib/elastic_apm/spies/http.rb +1 -0
  23. data/lib/elastic_apm/spies/mongo.rb +6 -2
  24. data/lib/elastic_apm/spies/net_http.rb +1 -0
  25. data/lib/elastic_apm/spies/rake.rb +4 -2
  26. data/lib/elastic_apm/spies/resque.rb +4 -2
  27. data/lib/elastic_apm/spies/sequel.rb +10 -1
  28. data/lib/elastic_apm/spies/shoryuken.rb +2 -0
  29. data/lib/elastic_apm/spies/sidekiq.rb +2 -0
  30. data/lib/elastic_apm/spies/sneakers.rb +2 -0
  31. data/lib/elastic_apm/spies/sucker_punch.rb +2 -0
  32. data/lib/elastic_apm/trace_context.rb +1 -1
  33. data/lib/elastic_apm/trace_context/traceparent.rb +2 -4
  34. data/lib/elastic_apm/trace_context/tracestate.rb +112 -9
  35. data/lib/elastic_apm/transaction.rb +39 -6
  36. data/lib/elastic_apm/transport/connection.rb +1 -0
  37. data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +9 -16
  38. data/lib/elastic_apm/transport/filters/secrets_filter.rb +8 -6
  39. data/lib/elastic_apm/transport/serializers.rb +8 -6
  40. data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +43 -3
  41. data/lib/elastic_apm/transport/serializers/span_serializer.rb +3 -1
  42. data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +2 -0
  43. data/lib/elastic_apm/transport/user_agent.rb +3 -3
  44. data/lib/elastic_apm/transport/worker.rb +5 -0
  45. data/lib/elastic_apm/util.rb +2 -0
  46. data/lib/elastic_apm/util/precision_validator.rb +46 -0
  47. data/lib/elastic_apm/version.rb +1 -1
  48. metadata +6 -3
@@ -20,6 +20,18 @@
20
20
  module ElasticAPM
21
21
  # @api private
22
22
  class Transaction
23
+
24
+ # @api private
25
+ class Outcome
26
+ FAILURE = "failure"
27
+ SUCCESS = "success"
28
+ UNKNOWN = "unknown"
29
+
30
+ def self.from_http_status(code)
31
+ code.to_i >= 500 ? FAILURE : SUCCESS
32
+ end
33
+ end
34
+
23
35
  extend Forwardable
24
36
  include ChildDurations::Methods
25
37
 
@@ -34,6 +46,7 @@ module ElasticAPM
34
46
  name = nil,
35
47
  type = nil,
36
48
  sampled: true,
49
+ sample_rate: 1,
37
50
  context: nil,
38
51
  config:,
39
52
  trace_context: nil
@@ -52,13 +65,19 @@ module ElasticAPM
52
65
  @default_labels = config.default_labels
53
66
 
54
67
  @sampled = sampled
68
+ @sample_rate = sample_rate
55
69
 
56
70
  @context = context || Context.new # TODO: Lazy generate this?
57
71
  if @default_labels
58
72
  Util.reverse_merge!(@context.labels, @default_labels)
59
73
  end
60
74
 
61
- @trace_context = trace_context || TraceContext.new(recorded: sampled)
75
+ unless (@trace_context = trace_context)
76
+ @trace_context = TraceContext.new(
77
+ traceparent: TraceContext::Traceparent.new(recorded: sampled),
78
+ tracestate: TraceContext::Tracestate.new(sample_rate: sampled ? sample_rate : 0)
79
+ )
80
+ end
62
81
 
63
82
  @started_spans = 0
64
83
  @dropped_spans = 0
@@ -67,12 +86,26 @@ module ElasticAPM
67
86
  end
68
87
  # rubocop:enable Metrics/ParameterLists
69
88
 
70
- attr_accessor :name, :type, :result
89
+ attr_accessor :name, :type, :result, :outcome
90
+
91
+ attr_reader(
92
+ :breakdown_metrics,
93
+ :collect_metrics,
94
+ :context,
95
+ :dropped_spans,
96
+ :duration,
97
+ :framework_name,
98
+ :notifications,
99
+ :self_time,
100
+ :sample_rate,
101
+ :span_frames_min_duration,
102
+ :started_spans,
103
+ :timestamp,
104
+ :trace_context,
105
+ :transaction_max_spans
106
+ )
71
107
 
72
- attr_reader :context, :duration, :started_spans, :dropped_spans,
73
- :timestamp, :trace_context, :notifications, :self_time,
74
- :span_frames_min_duration, :collect_metrics, :breakdown_metrics,
75
- :framework_name, :transaction_max_spans
108
+ alias :collect_metrics? :collect_metrics
76
109
 
77
110
  def sampled?
78
111
  @sampled
@@ -47,6 +47,7 @@ module ElasticAPM
47
47
  end
48
48
 
49
49
  attr_reader :http
50
+
50
51
  def write(str)
51
52
  return false if @config.disable_send
52
53
 
@@ -23,29 +23,22 @@ module ElasticAPM
23
23
  class HashSanitizer
24
24
  FILTERED = '[FILTERED]'
25
25
 
26
- KEY_FILTERS = [
27
- /passw(or)?d/i,
28
- /auth/i,
29
- /^pw$/,
30
- /secret/i,
31
- /token/i,
32
- /api[-._]?key/i,
33
- /session[-._]?id/i,
34
- /(set[-_])?cookie/i
35
- ].freeze
26
+ # DEPRECATED: Remove these additions in next major version
27
+ LEGACY_KEY_FILTERS = [/cookie/i, /auth/i].freeze
36
28
 
29
+ # DEPRECATED: Remove this check in next major version
37
30
  VALUE_FILTERS = [
38
31
  # (probably) credit card number
39
32
  /^\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}$/
40
33
  ].freeze
41
34
 
42
- attr_accessor :key_filters
43
-
44
- def initialize
45
- @key_filters = KEY_FILTERS
35
+ def initialize(key_patterns:)
36
+ @key_patterns = key_patterns + LEGACY_KEY_FILTERS
46
37
  end
47
38
 
48
- def strip_from!(obj, key_filters = KEY_FILTERS)
39
+ attr_accessor :key_patterns
40
+
41
+ def strip_from!(obj)
49
42
  return unless obj&.is_a?(Hash)
50
43
 
51
44
  obj.each do |k, v|
@@ -65,7 +58,7 @@ module ElasticAPM
65
58
  end
66
59
 
67
60
  def filter_key?(key)
68
- @key_filters.any? { |regex| regex.match(key) }
61
+ @key_patterns.any? { |regex| regex.match(key) }
69
62
  end
70
63
 
71
64
  def filter_value?(value)
@@ -26,19 +26,21 @@ module ElasticAPM
26
26
  class SecretsFilter
27
27
  def initialize(config)
28
28
  @config = config
29
- @sanitizer = HashSanitizer.new
30
- @sanitizer.key_filters += config.custom_key_filters +
31
- config.sanitize_field_names
29
+ @sanitizer =
30
+ HashSanitizer.new(
31
+ key_patterns: config.custom_key_filters + config.sanitize_field_names
32
+ )
32
33
  end
33
34
 
34
35
  def call(payload)
35
- @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :headers)
36
- @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :env)
36
+ @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :body)
37
37
  @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :cookies)
38
+ @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :env)
39
+ @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :headers)
38
40
  @sanitizer.strip_from! payload.dig(:transaction, :context, :response, :headers)
41
+ @sanitizer.strip_from! payload.dig(:error, :context, :request, :cookies)
39
42
  @sanitizer.strip_from! payload.dig(:error, :context, :request, :headers)
40
43
  @sanitizer.strip_from! payload.dig(:error, :context, :response, :headers)
41
- @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :body)
42
44
 
43
45
  payload
44
46
  end
@@ -45,18 +45,20 @@ module ElasticAPM
45
45
  def keyword_object(hash)
46
46
  return unless hash
47
47
 
48
- hash.tap do |h|
49
- h.each { |k, v| hash[k] = keyword_field(v) }
48
+ hash.each do |k, v|
49
+ hash[k] =
50
+ case v
51
+ when Hash then keyword_object(v)
52
+ else keyword_field(v)
53
+ end
50
54
  end
51
55
  end
52
56
 
53
57
  def mixed_object(hash)
54
58
  return unless hash
55
59
 
56
- hash.tap do |h|
57
- h.each do |k, v|
58
- hash[k] = v.is_a?(String) ? keyword_field(v) : v
59
- end
60
+ hash.each do |k, v|
61
+ hash[k] = v.is_a?(String) ? keyword_field(v) : v
60
62
  end
61
63
  end
62
64
  end
@@ -23,14 +23,19 @@ module ElasticAPM
23
23
  # @api private
24
24
  class MetadataSerializer < Serializer
25
25
  def build(metadata)
26
- {
27
- metadata: {
26
+ base =
27
+ {
28
28
  service: build_service(metadata.service),
29
29
  process: build_process(metadata.process),
30
30
  system: build_system(metadata.system),
31
31
  labels: build_labels(metadata.labels)
32
32
  }
33
- }
33
+
34
+ if (metadata.cloud.provider)
35
+ base[:cloud] = build_cloud(metadata.cloud)
36
+ end
37
+
38
+ { metadata: base }
34
39
  end
35
40
 
36
41
  private
@@ -83,9 +88,44 @@ module ElasticAPM
83
88
  }
84
89
  end
85
90
 
91
+ def build_cloud(cloud)
92
+ strip_nulls!(
93
+ provider: cloud.provider,
94
+ account: {
95
+ id: keyword_field(cloud.account_id),
96
+ name: keyword_field(cloud.account_name),
97
+ },
98
+ availability_zone: keyword_field(cloud.availability_zone),
99
+ instance: {
100
+ id: keyword_field(cloud.instance_id),
101
+ name: keyword_field(cloud.instance_name),
102
+ },
103
+ machine: { type: keyword_field(cloud.machine_type) },
104
+ project: {
105
+ id: keyword_field(cloud.project_id),
106
+ name: keyword_field(cloud.project_name),
107
+ },
108
+ region: keyword_field(cloud.region)
109
+ )
110
+ end
111
+
86
112
  def build_labels(labels)
87
113
  keyword_object(labels)
88
114
  end
115
+
116
+ # A bug in APM Server 7.9 disallows null values in `cloud`
117
+ def strip_nulls!(hash)
118
+ hash.keys.each do |key|
119
+ case value = hash[key]
120
+ when Hash
121
+ strip_nulls!(value)
122
+ hash.delete(key) if value.empty?
123
+ when nil then hash.delete(key)
124
+ end
125
+ end
126
+
127
+ hash
128
+ end
89
129
  end
90
130
  end
91
131
  end
@@ -42,7 +42,9 @@ module ElasticAPM
42
42
  context: context_serializer.build(span.context),
43
43
  stacktrace: span.stacktrace.to_a,
44
44
  timestamp: span.timestamp,
45
- trace_id: span.trace_id
45
+ trace_id: span.trace_id,
46
+ sample_rate: span.sample_rate,
47
+ outcome: keyword_field(span.outcome)
46
48
  }
47
49
  }
48
50
  end
@@ -35,9 +35,11 @@ module ElasticAPM
35
35
  name: keyword_field(transaction.name),
36
36
  type: keyword_field(transaction.type),
37
37
  result: keyword_field(transaction.result.to_s),
38
+ outcome: keyword_field(transaction.outcome),
38
39
  duration: ms(transaction.duration),
39
40
  timestamp: transaction.timestamp,
40
41
  sampled: transaction.sampled?,
42
+ sample_rate: transaction.sample_rate,
41
43
  context: context_serializer.build(transaction.context),
42
44
  span_count: {
43
45
  started: transaction.started_spans,
@@ -32,14 +32,14 @@ module ElasticAPM
32
32
  private
33
33
 
34
34
  def build(config)
35
- metadata = Metadata.new(config)
35
+ service = Metadata::ServiceInfo.new(config)
36
36
 
37
37
  [
38
38
  "elastic-apm-ruby/#{VERSION}",
39
39
  HTTP::Request::USER_AGENT,
40
40
  [
41
- metadata.service.runtime.name,
42
- metadata.service.runtime.version
41
+ service.runtime.name,
42
+ service.runtime.version
43
43
  ].join('/')
44
44
  ].join(' ')
45
45
  end
@@ -53,6 +53,7 @@ module ElasticAPM
53
53
  end
54
54
 
55
55
  attr_reader :queue, :filters, :name, :connection, :serializers
56
+
56
57
  def work_forever
57
58
  while (msg = queue.pop)
58
59
  case msg
@@ -77,6 +78,10 @@ module ElasticAPM
77
78
  private
78
79
 
79
80
  def serialize_and_filter(resource)
81
+ if resource.respond_to?(:prepare_for_serialization!)
82
+ resource.prepare_for_serialization!
83
+ end
84
+
80
85
  serialized = serializers.serialize(resource)
81
86
 
82
87
  # if a filter returns nil, it means skip the event
@@ -46,6 +46,8 @@ module ElasticAPM
46
46
 
47
47
  def self.truncate(value, max_length: 1024)
48
48
  return unless value
49
+
50
+ value = String(value)
49
51
  return value if value.length <= max_length
50
52
 
51
53
  value[0...(max_length - 1)] + '…'
@@ -0,0 +1,46 @@
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
+ module Util
22
+ # @api private
23
+ # Rounds half away from zero.
24
+ # If `minimum` is provided, and the value rounds to 0 (but was not zero to
25
+ # begin with), use the minimum instead.
26
+ module PrecisionValidator
27
+ extend self
28
+
29
+ def validate(value, precision: 0, minimum: nil)
30
+ float = Float(value)
31
+ return nil unless (0.0..1.0).cover?(float)
32
+ return float if float == 0
33
+
34
+ multiplier = Float(10**precision)
35
+ rounded = (float * multiplier + 0.5).floor / multiplier
36
+ if rounded == 0 && minimum
37
+ minimum
38
+ else
39
+ rounded
40
+ end
41
+ rescue ArgumentError
42
+ nil
43
+ end
44
+ end
45
+ end
46
+ end
@@ -18,5 +18,5 @@
18
18
  # frozen_string_literal: true
19
19
 
20
20
  module ElasticAPM
21
- VERSION = '3.9.0'
21
+ VERSION = '3.12.0'
22
22
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elastic-apm
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.9.0
4
+ version: 3.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikkel Malmberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-04 00:00:00.000000000 Z
11
+ date: 2020-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -128,6 +128,7 @@ files:
128
128
  - lib/elastic_apm/config/duration.rb
129
129
  - lib/elastic_apm/config/options.rb
130
130
  - lib/elastic_apm/config/regexp_list.rb
131
+ - lib/elastic_apm/config/round_float.rb
131
132
  - lib/elastic_apm/config/wildcard_pattern_list.rb
132
133
  - lib/elastic_apm/context.rb
133
134
  - lib/elastic_apm/context/request.rb
@@ -148,6 +149,7 @@ files:
148
149
  - lib/elastic_apm/internal_error.rb
149
150
  - lib/elastic_apm/logging.rb
150
151
  - lib/elastic_apm/metadata.rb
152
+ - lib/elastic_apm/metadata/cloud_info.rb
151
153
  - lib/elastic_apm/metadata/process_info.rb
152
154
  - lib/elastic_apm/metadata/service_info.rb
153
155
  - lib/elastic_apm/metadata/system_info.rb
@@ -235,6 +237,7 @@ files:
235
237
  - lib/elastic_apm/util.rb
236
238
  - lib/elastic_apm/util/inflector.rb
237
239
  - lib/elastic_apm/util/lru_cache.rb
240
+ - lib/elastic_apm/util/precision_validator.rb
238
241
  - lib/elastic_apm/util/throttle.rb
239
242
  - lib/elastic_apm/version.rb
240
243
  homepage: https://github.com/elastic/apm-agent-ruby
@@ -257,7 +260,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
257
260
  - !ruby/object:Gem::Version
258
261
  version: '0'
259
262
  requirements: []
260
- rubygems_version: 3.1.2
263
+ rubygems_version: 3.0.6
261
264
  signing_key:
262
265
  specification_version: 4
263
266
  summary: The official Elastic APM agent for Ruby