elastic-apm 3.10.1 → 3.11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.asciidoc +23 -0
- data/Gemfile +0 -2
- data/docs/configuration.asciidoc +1 -0
- data/lib/elastic_apm/config.rb +24 -6
- data/lib/elastic_apm/config/options.rb +2 -1
- data/lib/elastic_apm/config/round_float.rb +31 -0
- data/lib/elastic_apm/config/wildcard_pattern_list.rb +2 -0
- data/lib/elastic_apm/instrumenter.rb +10 -3
- data/lib/elastic_apm/metadata.rb +3 -1
- data/lib/elastic_apm/metadata/cloud_info.rb +128 -0
- data/lib/elastic_apm/span.rb +4 -1
- data/lib/elastic_apm/spies/delayed_job.rb +4 -2
- data/lib/elastic_apm/spies/elasticsearch.rb +7 -1
- data/lib/elastic_apm/trace_context.rb +1 -1
- data/lib/elastic_apm/trace_context/traceparent.rb +2 -4
- data/lib/elastic_apm/trace_context/tracestate.rb +112 -9
- data/lib/elastic_apm/transaction.rb +26 -5
- data/lib/elastic_apm/transport/connection.rb +1 -0
- data/lib/elastic_apm/transport/filters/hash_sanitizer.rb +9 -16
- data/lib/elastic_apm/transport/filters/secrets_filter.rb +8 -7
- data/lib/elastic_apm/transport/serializers.rb +8 -6
- data/lib/elastic_apm/transport/serializers/metadata_serializer.rb +29 -3
- data/lib/elastic_apm/transport/serializers/span_serializer.rb +2 -1
- data/lib/elastic_apm/transport/serializers/transaction_serializer.rb +1 -0
- data/lib/elastic_apm/transport/user_agent.rb +3 -3
- data/lib/elastic_apm/transport/worker.rb +1 -0
- data/lib/elastic_apm/util.rb +2 -0
- data/lib/elastic_apm/util/precision_validator.rb +46 -0
- data/lib/elastic_apm/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bae79ddc57bfa8955a50f062b2305db04ed9618fa40011ec78341f62083487a3
|
4
|
+
data.tar.gz: 176267b31455a0f491aaff4326e1274db3684aa878bf811b60b7f5195613a228
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '089b563ba5cba6732bf75f6b29218e1b2cdf29f251c21c4c0abd577b4492fcea2c7ee24d5d616c6e8cec92f051cbbf551219e580846db48689fcf5c77c857b71'
|
7
|
+
data.tar.gz: 28c4160d29e5af26bd388dfda7feacdbb0d484f522080e57b5aafc10041ed07b8c62b6c799a8a6676bb59fca3db643a9319fcb7afbe661788c37558454bd924c
|
data/CHANGELOG.asciidoc
CHANGED
@@ -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
data/docs/configuration.asciidoc
CHANGED
@@ -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]]
|
data/lib/elastic_apm/config.rb
CHANGED
@@ -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,
|
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
|
-
|
240
|
-
'
|
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
|
@@ -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
|
@@ -119,7 +119,13 @@ module ElasticAPM
|
|
119
119
|
"Already inside #{transaction.inspect}"
|
120
120
|
end
|
121
121
|
|
122
|
-
|
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.
|
308
|
+
return unless span.transaction.collect_metrics?
|
302
309
|
|
303
310
|
tags = {
|
304
311
|
'span.type': span.type,
|
data/lib/elastic_apm/metadata.rb
CHANGED
@@ -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
|
data/lib/elastic_apm/span.rb
CHANGED
@@ -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
|
44
|
+
transaction&.done 'success'
|
45
45
|
rescue ::Exception => e
|
46
46
|
ElasticAPM.report(e, handled: false)
|
47
|
-
transaction
|
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 ||=
|
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,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
|
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
|
-
|
25
|
-
|
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
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
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
|
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
|
73
|
-
:
|
74
|
-
:
|
75
|
-
:
|
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
|
@@ -23,29 +23,22 @@ module ElasticAPM
|
|
23
23
|
class HashSanitizer
|
24
24
|
FILTERED = '[FILTERED]'
|
25
25
|
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
-
|
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
|
-
@
|
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 =
|
30
|
-
|
31
|
-
|
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, :
|
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.
|
49
|
-
|
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.
|
57
|
-
|
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
|
-
|
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
|
@@ -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
|
-
|
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
|
-
|
42
|
-
|
41
|
+
service.runtime.name,
|
42
|
+
service.runtime.version
|
43
43
|
].join('/')
|
44
44
|
].join(' ')
|
45
45
|
end
|
data/lib/elastic_apm/util.rb
CHANGED
@@ -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
|
data/lib/elastic_apm/version.rb
CHANGED
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.
|
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-
|
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.
|
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
|