elastic-apm 3.10.1 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a6db74de4a0201f5b5dd7e9f96b7ec383ce3276a56d7f9d57e64617dd7cf1a28
4
- data.tar.gz: '0319a664e07b2411428134e24ed58f21049acf856cf163c6434fb66934e4cd1f'
3
+ metadata.gz: bae79ddc57bfa8955a50f062b2305db04ed9618fa40011ec78341f62083487a3
4
+ data.tar.gz: 176267b31455a0f491aaff4326e1274db3684aa878bf811b60b7f5195613a228
5
5
  SHA512:
6
- metadata.gz: 7bc1dfb374ea6107f07f690d764205f44c00a588eed767d7621b42640bf00f322d5e8b7c6a3d4958b0753b4755722836a05577fcee5b1a15207a355860a1f71b
7
- data.tar.gz: 780227ecdd4592d5f3aaa87b54824b4ed36dc9686052b51314815ebbf3152855dc5ff186c69deb88b86f2eab1d7aa9e4170bfd1454b2fdca3ab82d513eb0280a
6
+ metadata.gz: '089b563ba5cba6732bf75f6b29218e1b2cdf29f251c21c4c0abd577b4492fcea2c7ee24d5d616c6e8cec92f051cbbf551219e580846db48689fcf5c77c857b71'
7
+ data.tar.gz: 28c4160d29e5af26bd388dfda7feacdbb0d484f522080e57b5aafc10041ed07b8c62b6c799a8a6676bb59fca3db643a9319fcb7afbe661788c37558454bd924c
@@ -35,6 +35,28 @@ endif::[]
35
35
  [[release-notes-3.x]]
36
36
  === Ruby Agent version 3.x
37
37
 
38
+ [[release-notes-3.11.0]]
39
+ ==== 3.11.0 (2020-10-27)
40
+
41
+ [float]
42
+ ===== Added
43
+
44
+ - Add and read sampling info from Tracestate headers {pull}858[#858]
45
+ - Add information about cloud hosting environment if available {pull}871[#871]
46
+
47
+ [float]
48
+ ===== Changed
49
+
50
+ - Align the default value of `sanitize_field_names` with other agents {pull}867[#867]
51
+ - Ensure max 4 digits of precision for `sample_rate` as per agent spec {pull}880[#880]
52
+
53
+ [float]
54
+ ===== Fixed
55
+
56
+ - Fix Delayed::Job class names when used through ActiveJob {pull}869[#869]
57
+ - Fix Delayed::Job when run without the agent running {pull}874[#874]
58
+ - Fix Kubernetes related metadata {pull}882[#882]
59
+
38
60
  [[release-notes-3.10.1]]
39
61
  ==== 3.10.1 (2020-08-26)
40
62
 
@@ -42,6 +64,7 @@ endif::[]
42
64
  ===== Fixed
43
65
 
44
66
  - Remove secrets from cookies in errors {pull}863[#863]
67
+ - Silence deprecation warning when setting `ignore_url_patterns` to default {pull}865[#865]
45
68
 
46
69
  [[release-notes-3.10.0]]
47
70
  ==== 3.10.0 (2020-08-26)
data/Gemfile CHANGED
@@ -28,8 +28,6 @@ gem 'pry'
28
28
  gem 'rack-test'
29
29
  gem 'rspec', '~> 3'
30
30
  gem 'rspec-its'
31
- gem 'rubocop', require: nil
32
- gem 'rubocop-performance', require: nil
33
31
  gem 'timecop'
34
32
  gem 'webmock'
35
33
 
@@ -853,6 +853,7 @@ To reduce overhead and storage requirements, you can set the sample rate to a va
853
853
  between `0.0` and `1.0`.
854
854
  We still record overall time and the result for unsampled transactions, but no
855
855
  context information, tags, or spans.
856
+ Note that the sample rate will be rounded to 4 digits of precision.
856
857
 
857
858
  [float]
858
859
  [[config-use-experimental-sql-parser]]
@@ -17,9 +17,10 @@
17
17
 
18
18
  # frozen_string_literal: true
19
19
 
20
- require 'elastic_apm/config/options'
21
- require 'elastic_apm/config/duration'
22
20
  require 'elastic_apm/config/bytes'
21
+ require 'elastic_apm/config/duration'
22
+ require 'elastic_apm/config/options'
23
+ require 'elastic_apm/config/round_float'
23
24
  require 'elastic_apm/config/regexp_list'
24
25
  require 'elastic_apm/config/wildcard_pattern_list'
25
26
 
@@ -30,6 +31,11 @@ module ElasticAPM
30
31
 
31
32
  DEPRECATED_OPTIONS = %i[].freeze
32
33
 
34
+ # DEPRECATED: To align with other agents, change on next major bump to:
35
+ # "password, passwd, pwd, secret, *key, *token*, *session*, *credit*, *card*, authorization, set-cookie"
36
+ SANITIZE_FIELD_NAMES_DEFAULT =
37
+ %w[*password* *passwd* *pwd* *secret* *key* *token* *session* *credit* *card* *authorization* *set-cookie*]
38
+
33
39
  # rubocop:disable Metrics/LineLength, Layout/ExtraSpacing
34
40
  option :config_file, type: :string, default: 'config/elastic_apm.yml'
35
41
  option :server_url, type: :url, default: 'http://localhost:8200'
@@ -45,6 +51,7 @@ module ElasticAPM
45
51
  option :capture_elasticsearch_queries, type: :bool, default: false
46
52
  option :capture_env, type: :bool, default: true
47
53
  option :central_config, type: :bool, default: true
54
+ option :cloud_provider, type: :string, default: 'auto'
48
55
  option :current_user_email_method, type: :string, default: 'email'
49
56
  option :current_user_id_method, type: :string, default: 'id'
50
57
  option :current_user_username_method, type: :string, default: 'username'
@@ -76,7 +83,8 @@ module ElasticAPM
76
83
  option :proxy_port, type: :int
77
84
  option :proxy_username, type: :string
78
85
  option :recording, type: :bool, default: true
79
- option :sanitize_field_names, type: :list, default: [], converter: WildcardPatternList.new
86
+ option :sanitize_field_names, type: :list,
87
+ default: SANITIZE_FIELD_NAMES_DEFAULT, converter: WildcardPatternList.new
80
88
  option :server_ca_cert, type: :string
81
89
  option :service_name, type: :string
82
90
  option :service_node_name, type: :string
@@ -89,7 +97,7 @@ module ElasticAPM
89
97
  option :stack_trace_limit, type: :int, default: 999_999
90
98
  option :transaction_ignore_urls, type: :list, default: [], converter: WildcardPatternList.new
91
99
  option :transaction_max_spans, type: :int, default: 500
92
- option :transaction_sample_rate, type: :float, default: 1.0
100
+ option :transaction_sample_rate, type: :float, default: 1.0, converter: RoundFloat.new
93
101
  option :use_elastic_traceparent_header, type: :bool, default: true
94
102
  option :use_legacy_sql_parser, type: :bool, default: false
95
103
  option :verify_server_cert, type: :bool, default: true
@@ -189,6 +197,14 @@ module ElasticAPM
189
197
  metrics_interval > 0
190
198
  end
191
199
 
200
+ # DEPRECATED: Remove this in next major version
201
+ def sanitize_field_names=(value)
202
+ list = WildcardPatternList.new.call(value)
203
+ defaults = WildcardPatternList.new.call(SANITIZE_FIELD_NAMES_DEFAULT)
204
+ get(:sanitize_field_names).value =
205
+ defaults.concat(list).uniq(&:pattern) # use regex pattern for comparisons
206
+ end
207
+
192
208
  def span_frames_min_duration?
193
209
  span_frames_min_duration != 0
194
210
  end
@@ -236,8 +252,10 @@ module ElasticAPM
236
252
  end
237
253
 
238
254
  def ignore_url_patterns=(value)
239
- warn '[DEPRECATED] The option ignore_url_patterns is being removed. ' \
240
- 'Consider using transaction_ignore_urls instead.'
255
+ unless value == self.class.schema[:ignore_url_patterns][:default]
256
+ warn '[DEPRECATED] The option ignore_url_patterns is being removed. ' \
257
+ 'Consider using transaction_ignore_urls instead.'
258
+ end
241
259
 
242
260
  set(:ignore_url_patterns, value)
243
261
  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
@@ -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
 
@@ -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,128 @@
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(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
+ def fetch!
53
+ case config.cloud_provider
54
+ when "aws"
55
+ fetch_aws
56
+ when "gcp"
57
+ fetch_gcp
58
+ when "azure"
59
+ fetch_azure
60
+ when "auto"
61
+ fetch_aws || fetch_gcp || fetch_azure
62
+ when "none"
63
+ nil
64
+ else
65
+ error("Unknown setting for cloud_provider '#{config.cloud_provider}'")
66
+ end
67
+
68
+ self
69
+ end
70
+
71
+ private
72
+
73
+ def fetch_aws
74
+ resp = @client.get(AWS_URI)
75
+
76
+ return unless resp.status === 200
77
+ return unless (metadata = JSON.parse(resp.body))
78
+
79
+ self.provider = "aws"
80
+ self.account_id = metadata["accountId"]
81
+ self.instance_id = metadata["instanceId"]
82
+ self.availability_zone = metadata["availabilityZone"]
83
+ self.machine_type = metadata["instanceType"]
84
+ self.region = metadata["region"]
85
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
86
+ nil
87
+ end
88
+
89
+ def fetch_gcp
90
+ resp = @client.headers("Metadata-Flavor" => "Google").get(GCP_URI)
91
+
92
+ return unless resp.status === 200
93
+ return unless (metadata = JSON.parse(resp.body))
94
+
95
+ zone = metadata["instance"]["zone"]&.split("/")&.at(-1)
96
+
97
+ self.provider = "gcp"
98
+ self.instance_id = metadata["instance"]["id"]
99
+ self.instance_name = metadata["instance"]["name"]
100
+ self.project_id = metadata["project"]["numericProjectId"]
101
+ self.project_name = metadata["project"]["projectId"]
102
+ self.availability_zone = zone
103
+ self.region = zone.split("-")[0..-2].join("-")
104
+ self.machine_type = metadata["instance"]["machineType"]
105
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
106
+ nil
107
+ end
108
+
109
+ def fetch_azure
110
+ resp = @client.headers("Metadata" => "true").get(AZURE_URI)
111
+
112
+ return unless resp.status === 200
113
+ return unless (metadata = JSON.parse(resp.body))
114
+
115
+ self.provider = 'azure'
116
+ self.account_id = metadata["subscriptionId"]
117
+ self.instance_id = metadata["vmId"]
118
+ self.instance_name = metadata["name"]
119
+ self.project_name = metadata["resourceGroupName"]
120
+ self.availability_zone = metadata["zone"]
121
+ self.machine_type = metadata["vmSize"]
122
+ self.region = metadata["location"]
123
+ rescue HTTP::TimeoutError, HTTP::ConnectionError
124
+ nil
125
+ end
126
+ end
127
+ end
128
+ end
@@ -38,7 +38,8 @@ module ElasticAPM
38
38
  action: nil,
39
39
  context: nil,
40
40
  stacktrace_builder: nil,
41
- sync: nil
41
+ sync: nil,
42
+ sample_rate: nil
42
43
  )
43
44
  @name = name
44
45
 
@@ -53,6 +54,7 @@ module ElasticAPM
53
54
  @transaction = transaction
54
55
  @parent = parent
55
56
  @trace_context = trace_context || parent.trace_context.child
57
+ @sample_rate = transaction.sample_rate
56
58
 
57
59
  @context = context || Span::Context.new(sync: sync)
58
60
  @stacktrace_builder = stacktrace_builder
@@ -73,6 +75,7 @@ module ElasticAPM
73
75
  :context,
74
76
  :duration,
75
77
  :parent,
78
+ :sample_rate,
76
79
  :self_time,
77
80
  :stacktrace,
78
81
  :timestamp,
@@ -41,10 +41,10 @@ module ElasticAPM
41
41
  job_name = name_from_payload(job.payload_object)
42
42
  transaction = ElasticAPM.start_transaction(job_name, TYPE)
43
43
  job.invoke_job_without_apm(*args, &block)
44
- transaction.done 'success'
44
+ transaction&.done 'success'
45
45
  rescue ::Exception => e
46
46
  ElasticAPM.report(e, handled: false)
47
- transaction.done 'error'
47
+ transaction&.done 'error'
48
48
  raise
49
49
  ensure
50
50
  ElasticAPM.end_transaction
@@ -53,6 +53,8 @@ module ElasticAPM
53
53
  def self.name_from_payload(payload_object)
54
54
  if payload_object.is_a?(::Delayed::PerformableMethod)
55
55
  performable_method_name(payload_object)
56
+ elsif payload_object.class.name == 'ActiveJob::QueueAdapters::DelayedJobAdapter::JobWrapper'
57
+ payload_object.job_data['job_class']
56
58
  else
57
59
  payload_object.class.name
58
60
  end
@@ -27,7 +27,13 @@ module ElasticAPM
27
27
  SUBTYPE = 'elasticsearch'
28
28
 
29
29
  def self.sanitizer
30
- @sanitizer ||= ElasticAPM::Transport::Filters::HashSanitizer.new
30
+ @sanitizer ||=
31
+ begin
32
+ config = ElasticAPM.agent.config
33
+ ElasticAPM::Transport::Filters::HashSanitizer.new(
34
+ key_patterns: config.custom_key_filters + config.sanitize_field_names
35
+ )
36
+ end
31
37
  end
32
38
 
33
39
  def install
@@ -33,7 +33,7 @@ module ElasticAPM
33
33
  **legacy_traceparent_attrs
34
34
  )
35
35
  @traceparent = traceparent || Traceparent.new(**legacy_traceparent_attrs)
36
- @tracestate = tracestate
36
+ @tracestate = tracestate || Tracestate.new
37
37
  end
38
38
 
39
39
  attr_accessor :traceparent, :tracestate
@@ -33,8 +33,7 @@ module ElasticAPM
33
33
  trace_id: nil,
34
34
  span_id: nil,
35
35
  id: nil,
36
- recorded: true,
37
- tracestate: nil
36
+ recorded: true
38
37
  )
39
38
  @version = version
40
39
  @trace_id = trace_id || hex(TRACE_ID_LENGTH)
@@ -42,11 +41,10 @@ module ElasticAPM
42
41
  @parent_id = span_id
43
42
  @id = id || hex(ID_LENGTH)
44
43
  @recorded = recorded
45
- @tracestate = tracestate
46
44
  end
47
45
  # rubocop:enable Metrics/ParameterLists
48
46
 
49
- attr_accessor :version, :id, :trace_id, :parent_id, :recorded, :tracestate
47
+ attr_accessor :version, :id, :trace_id, :parent_id, :recorded
50
48
 
51
49
  alias :recorded? :recorded
52
50
 
@@ -17,26 +17,129 @@
17
17
 
18
18
  # frozen_string_literal: true
19
19
 
20
+ require 'elastic_apm/util/precision_validator'
21
+
20
22
  module ElasticAPM
21
23
  class TraceContext
22
24
  # @api private
23
25
  class Tracestate
24
- def initialize(values = [])
25
- @values = values
26
+ # @api private
27
+ class Entry
28
+ def initialize(key, value)
29
+ @key = key
30
+ @value = value
31
+ end
32
+
33
+ attr_reader :key, :value
34
+
35
+ def to_s
36
+ "#{key}=#{value}"
37
+ end
26
38
  end
27
39
 
28
- attr_accessor :values
40
+ class EsEntry
41
+ ASSIGN = ':'
42
+ SPLIT = ';'
43
+
44
+ SHORT_TO_LONG = { 's' => 'sample_rate' }
45
+ LONG_TO_SHORT = { 'sample_rate' => 's' }
46
+
47
+ def initialize(values = nil)
48
+ parse(values)
49
+ end
50
+
51
+ attr_reader :sample_rate
52
+
53
+ def key
54
+ 'es'
55
+ end
56
+
57
+ def value
58
+ LONG_TO_SHORT.map do |l, s|
59
+ "#{s}#{ASSIGN}#{send(l)}"
60
+ end.join(SPLIT)
61
+ end
62
+
63
+ def empty?
64
+ !sample_rate
65
+ end
66
+
67
+ def sample_rate=(val)
68
+ @sample_rate = Util::PrecisionValidator.validate(
69
+ val, precision: 4, minimum: 0.0001
70
+ )
71
+ end
72
+
73
+ def to_s
74
+ return nil if empty?
75
+
76
+ "es=#{value}"
77
+ end
78
+
79
+ private
80
+
81
+ def parse(values)
82
+ return unless values
83
+
84
+ values.split(SPLIT).map do |kv|
85
+ k, v = kv.split(ASSIGN)
86
+ next unless SHORT_TO_LONG.keys.include?(k)
87
+ send("#{SHORT_TO_LONG[k]}=", v)
88
+ end
89
+ end
90
+ end
91
+
92
+ extend Forwardable
93
+
94
+ def initialize(entries: {}, sample_rate: nil)
95
+ @entries = entries
96
+
97
+ self.sample_rate = sample_rate if sample_rate
98
+ end
99
+
100
+ attr_accessor :entries
101
+
102
+ def_delegators :es_entry, :sample_rate, :sample_rate=
29
103
 
30
104
  def self.parse(header)
31
- # HTTP allows multiple headers with the same name, eg. multiple
32
- # Set-Cookie headers per response.
33
- # Rack handles this by joining the headers under the same key, separated
34
- # by newlines, see https://www.rubydoc.info/github/rack/rack/file/SPEC
35
- new(String(header).split("\n"))
105
+ entries =
106
+ split_by_nl_and_comma(header)
107
+ .each_with_object({}) do |entry, hsh|
108
+ k, v = entry.split('=')
109
+
110
+ hsh[k] =
111
+ case k
112
+ when 'es' then EsEntry.new(v)
113
+ else Entry.new(k, v)
114
+ end
115
+ end
116
+
117
+ new(entries: entries)
36
118
  end
37
119
 
38
120
  def to_header
39
- values.join(',')
121
+ return "" unless entries.any?
122
+
123
+ entries.values.map(&:to_s).join(',')
124
+ end
125
+
126
+ private
127
+
128
+ def es_entry
129
+ # lazy generate this so we only add it if necessary
130
+ entries['es'] ||= EsEntry.new
131
+ end
132
+
133
+ class << self
134
+ private
135
+
136
+ def split_by_nl_and_comma(str)
137
+ # HTTP allows multiple headers with the same name, eg. multiple
138
+ # Set-Cookie headers per response.
139
+ # Rack handles this by joining the headers under the same key, separated
140
+ # by newlines, see https://www.rubydoc.info/github/rack/rack/file/SPEC
141
+ String(str).split("\n").map { |s| s.split(',') }.flatten
142
+ end
40
143
  end
41
144
  end
42
145
  end
@@ -34,6 +34,7 @@ module ElasticAPM
34
34
  name = nil,
35
35
  type = nil,
36
36
  sampled: true,
37
+ sample_rate: 1,
37
38
  context: nil,
38
39
  config:,
39
40
  trace_context: nil
@@ -52,13 +53,19 @@ module ElasticAPM
52
53
  @default_labels = config.default_labels
53
54
 
54
55
  @sampled = sampled
56
+ @sample_rate = sample_rate
55
57
 
56
58
  @context = context || Context.new # TODO: Lazy generate this?
57
59
  if @default_labels
58
60
  Util.reverse_merge!(@context.labels, @default_labels)
59
61
  end
60
62
 
61
- @trace_context = trace_context || TraceContext.new(recorded: sampled)
63
+ unless (@trace_context = trace_context)
64
+ @trace_context = TraceContext.new(
65
+ traceparent: TraceContext::Traceparent.new(recorded: sampled),
66
+ tracestate: TraceContext::Tracestate.new(sample_rate: sampled ? sample_rate : 0)
67
+ )
68
+ end
62
69
 
63
70
  @started_spans = 0
64
71
  @dropped_spans = 0
@@ -69,10 +76,24 @@ module ElasticAPM
69
76
 
70
77
  attr_accessor :name, :type, :result
71
78
 
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
79
+ attr_reader(
80
+ :breakdown_metrics,
81
+ :collect_metrics,
82
+ :context,
83
+ :dropped_spans,
84
+ :duration,
85
+ :framework_name,
86
+ :notifications,
87
+ :self_time,
88
+ :sample_rate,
89
+ :span_frames_min_duration,
90
+ :started_spans,
91
+ :timestamp,
92
+ :trace_context,
93
+ :transaction_max_spans
94
+ )
95
+
96
+ alias :collect_metrics? :collect_metrics
76
97
 
77
98
  def sampled?
78
99
  @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,20 +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)
39
- @sanitizer.strip_from! payload.dig(:error, :context, :request, :headers)
40
41
  @sanitizer.strip_from! payload.dig(:error, :context, :request, :cookies)
42
+ @sanitizer.strip_from! payload.dig(:error, :context, :request, :headers)
41
43
  @sanitizer.strip_from! payload.dig(:error, :context, :response, :headers)
42
- @sanitizer.strip_from! payload.dig(:transaction, :context, :request, :body)
43
44
 
44
45
  payload
45
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,6 +88,27 @@ module ElasticAPM
83
88
  }
84
89
  end
85
90
 
91
+ def build_cloud(cloud)
92
+ {
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
@@ -42,7 +42,8 @@ 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
46
47
  }
47
48
  }
48
49
  end
@@ -38,6 +38,7 @@ module ElasticAPM
38
38
  duration: ms(transaction.duration),
39
39
  timestamp: transaction.timestamp,
40
40
  sampled: transaction.sampled?,
41
+ sample_rate: transaction.sample_rate,
41
42
  context: context_serializer.build(transaction.context),
42
43
  span_count: {
43
44
  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
@@ -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.10.1'
21
+ VERSION = '3.11.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.10.1
4
+ version: 3.11.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-26 00:00:00.000000000 Z
11
+ date: 2020-10-27 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.1.4
261
264
  signing_key:
262
265
  specification_version: 4
263
266
  summary: The official Elastic APM agent for Ruby