sentry-ruby 5.16.1 → 5.17.2
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/README.md +1 -1
- data/Rakefile +1 -1
- data/lib/sentry/background_worker.rb +1 -1
- data/lib/sentry/backtrace.rb +7 -3
- data/lib/sentry/check_in_event.rb +1 -1
- data/lib/sentry/client.rb +4 -3
- data/lib/sentry/configuration.rb +19 -11
- data/lib/sentry/cron/monitor_schedule.rb +1 -1
- data/lib/sentry/dsn.rb +1 -1
- data/lib/sentry/envelope.rb +1 -1
- data/lib/sentry/error_event.rb +2 -2
- data/lib/sentry/event.rb +8 -8
- data/lib/sentry/hub.rb +1 -1
- data/lib/sentry/integrable.rb +4 -0
- data/lib/sentry/interface.rb +1 -0
- data/lib/sentry/interfaces/exception.rb +5 -3
- data/lib/sentry/interfaces/mechanism.rb +20 -0
- data/lib/sentry/interfaces/request.rb +2 -2
- data/lib/sentry/interfaces/single_exception.rb +6 -4
- data/lib/sentry/interfaces/stacktrace_builder.rb +8 -0
- data/lib/sentry/metrics/aggregator.rb +260 -0
- data/lib/sentry/metrics/configuration.rb +47 -0
- data/lib/sentry/metrics/counter_metric.rb +25 -0
- data/lib/sentry/metrics/distribution_metric.rb +25 -0
- data/lib/sentry/metrics/gauge_metric.rb +35 -0
- data/lib/sentry/metrics/local_aggregator.rb +53 -0
- data/lib/sentry/metrics/metric.rb +19 -0
- data/lib/sentry/metrics/set_metric.rb +28 -0
- data/lib/sentry/metrics/timing.rb +43 -0
- data/lib/sentry/metrics.rb +55 -0
- data/lib/sentry/propagation_context.rb +9 -8
- data/lib/sentry/puma.rb +1 -1
- data/lib/sentry/rack/capture_exceptions.rb +6 -1
- data/lib/sentry/rake.rb +3 -1
- data/lib/sentry/scope.rb +7 -2
- data/lib/sentry/session.rb +2 -2
- data/lib/sentry/session_flusher.rb +0 -1
- data/lib/sentry/span.rb +16 -2
- data/lib/sentry/transaction.rb +15 -14
- data/lib/sentry/transaction_event.rb +5 -0
- data/lib/sentry/transport/configuration.rb +0 -1
- data/lib/sentry/transport.rb +0 -1
- data/lib/sentry/utils/argument_checking_helper.rb +6 -0
- data/lib/sentry/utils/real_ip.rb +1 -1
- data/lib/sentry/utils/request_id.rb +1 -1
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +14 -2
- data/sentry-ruby.gemspec +1 -0
- metadata +27 -2
@@ -0,0 +1,260 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class Aggregator
|
6
|
+
include LoggingHelper
|
7
|
+
|
8
|
+
FLUSH_INTERVAL = 5
|
9
|
+
ROLLUP_IN_SECONDS = 10
|
10
|
+
|
11
|
+
# this is how far removed from user code in the backtrace we are
|
12
|
+
# when we record code locations
|
13
|
+
DEFAULT_STACKLEVEL = 4
|
14
|
+
|
15
|
+
KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\/.-]+/
|
16
|
+
VALUE_SANITIZATION_REGEX = /[^[[:word:]][[:digit:]][[:space:]]_:\/@\.{}\[\]$-]+/
|
17
|
+
|
18
|
+
METRIC_TYPES = {
|
19
|
+
c: CounterMetric,
|
20
|
+
d: DistributionMetric,
|
21
|
+
g: GaugeMetric,
|
22
|
+
s: SetMetric
|
23
|
+
}
|
24
|
+
|
25
|
+
# exposed only for testing
|
26
|
+
attr_reader :thread, :buckets, :flush_shift, :code_locations
|
27
|
+
|
28
|
+
def initialize(configuration, client)
|
29
|
+
@client = client
|
30
|
+
@logger = configuration.logger
|
31
|
+
@before_emit = configuration.metrics.before_emit
|
32
|
+
@enable_code_locations = configuration.metrics.enable_code_locations
|
33
|
+
@stacktrace_builder = configuration.stacktrace_builder
|
34
|
+
|
35
|
+
@default_tags = {}
|
36
|
+
@default_tags['release'] = configuration.release if configuration.release
|
37
|
+
@default_tags['environment'] = configuration.environment if configuration.environment
|
38
|
+
|
39
|
+
@thread = nil
|
40
|
+
@exited = false
|
41
|
+
@mutex = Mutex.new
|
42
|
+
|
43
|
+
# a nested hash of timestamp -> bucket keys -> Metric instance
|
44
|
+
@buckets = {}
|
45
|
+
|
46
|
+
# the flush interval needs to be shifted once per startup to create jittering
|
47
|
+
@flush_shift = Random.rand * ROLLUP_IN_SECONDS
|
48
|
+
|
49
|
+
# a nested hash of timestamp (start of day) -> meta keys -> frame
|
50
|
+
@code_locations = {}
|
51
|
+
end
|
52
|
+
|
53
|
+
def add(type,
|
54
|
+
key,
|
55
|
+
value,
|
56
|
+
unit: 'none',
|
57
|
+
tags: {},
|
58
|
+
timestamp: nil,
|
59
|
+
stacklevel: nil)
|
60
|
+
return unless ensure_thread
|
61
|
+
return unless METRIC_TYPES.keys.include?(type)
|
62
|
+
|
63
|
+
updated_tags = get_updated_tags(tags)
|
64
|
+
return if @before_emit && !@before_emit.call(key, updated_tags)
|
65
|
+
|
66
|
+
timestamp ||= Sentry.utc_now
|
67
|
+
|
68
|
+
# this is integer division and thus takes the floor of the division
|
69
|
+
# and buckets into 10 second intervals
|
70
|
+
bucket_timestamp = (timestamp.to_i / ROLLUP_IN_SECONDS) * ROLLUP_IN_SECONDS
|
71
|
+
|
72
|
+
serialized_tags = serialize_tags(updated_tags)
|
73
|
+
bucket_key = [type, key, unit, serialized_tags]
|
74
|
+
|
75
|
+
added = @mutex.synchronize do
|
76
|
+
record_code_location(type, key, unit, timestamp, stacklevel: stacklevel) if @enable_code_locations
|
77
|
+
process_bucket(bucket_timestamp, bucket_key, type, value)
|
78
|
+
end
|
79
|
+
|
80
|
+
# for sets, we pass on if there was a new entry to the local gauge
|
81
|
+
local_value = type == :s ? added : value
|
82
|
+
process_span_aggregator(bucket_key, local_value)
|
83
|
+
end
|
84
|
+
|
85
|
+
def flush(force: false)
|
86
|
+
flushable_buckets = get_flushable_buckets!(force)
|
87
|
+
code_locations = get_code_locations!
|
88
|
+
return if flushable_buckets.empty? && code_locations.empty?
|
89
|
+
|
90
|
+
envelope = Envelope.new
|
91
|
+
|
92
|
+
unless flushable_buckets.empty?
|
93
|
+
payload = serialize_buckets(flushable_buckets)
|
94
|
+
envelope.add_item(
|
95
|
+
{ type: 'statsd', length: payload.bytesize },
|
96
|
+
payload
|
97
|
+
)
|
98
|
+
end
|
99
|
+
|
100
|
+
unless code_locations.empty?
|
101
|
+
code_locations.each do |timestamp, locations|
|
102
|
+
payload = serialize_locations(timestamp, locations)
|
103
|
+
envelope.add_item(
|
104
|
+
{ type: 'metric_meta', content_type: 'application/json' },
|
105
|
+
payload
|
106
|
+
)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
Sentry.background_worker.perform do
|
111
|
+
@client.transport.send_envelope(envelope)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def kill
|
116
|
+
log_debug('[Metrics::Aggregator] killing thread')
|
117
|
+
|
118
|
+
@exited = true
|
119
|
+
@thread&.kill
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
def ensure_thread
|
125
|
+
return false if @exited
|
126
|
+
return true if @thread&.alive?
|
127
|
+
|
128
|
+
@thread = Thread.new do
|
129
|
+
loop do
|
130
|
+
# TODO-neel-metrics use event for force flush later
|
131
|
+
sleep(FLUSH_INTERVAL)
|
132
|
+
flush
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
true
|
137
|
+
rescue ThreadError
|
138
|
+
log_debug('[Metrics::Aggregator] thread creation failed')
|
139
|
+
@exited = true
|
140
|
+
false
|
141
|
+
end
|
142
|
+
|
143
|
+
# important to sort for key consistency
|
144
|
+
def serialize_tags(tags)
|
145
|
+
tags.flat_map do |k, v|
|
146
|
+
if v.is_a?(Array)
|
147
|
+
v.map { |x| [k.to_s, x.to_s] }
|
148
|
+
else
|
149
|
+
[[k.to_s, v.to_s]]
|
150
|
+
end
|
151
|
+
end.sort
|
152
|
+
end
|
153
|
+
|
154
|
+
def get_flushable_buckets!(force)
|
155
|
+
@mutex.synchronize do
|
156
|
+
flushable_buckets = {}
|
157
|
+
|
158
|
+
if force
|
159
|
+
flushable_buckets = @buckets
|
160
|
+
@buckets = {}
|
161
|
+
else
|
162
|
+
cutoff = Sentry.utc_now.to_i - ROLLUP_IN_SECONDS - @flush_shift
|
163
|
+
flushable_buckets = @buckets.select { |k, _| k <= cutoff }
|
164
|
+
@buckets.reject! { |k, _| k <= cutoff }
|
165
|
+
end
|
166
|
+
|
167
|
+
flushable_buckets
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def get_code_locations!
|
172
|
+
@mutex.synchronize do
|
173
|
+
code_locations = @code_locations
|
174
|
+
@code_locations = {}
|
175
|
+
code_locations
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# serialize buckets to statsd format
|
180
|
+
def serialize_buckets(buckets)
|
181
|
+
buckets.map do |timestamp, timestamp_buckets|
|
182
|
+
timestamp_buckets.map do |metric_key, metric|
|
183
|
+
type, key, unit, tags = metric_key
|
184
|
+
values = metric.serialize.join(':')
|
185
|
+
sanitized_tags = tags.map { |k, v| "#{sanitize_key(k)}:#{sanitize_value(v)}" }.join(',')
|
186
|
+
|
187
|
+
"#{sanitize_key(key)}@#{unit}:#{values}|#{type}|\##{sanitized_tags}|T#{timestamp}"
|
188
|
+
end
|
189
|
+
end.flatten.join("\n")
|
190
|
+
end
|
191
|
+
|
192
|
+
def serialize_locations(timestamp, locations)
|
193
|
+
mapping = locations.map do |meta_key, location|
|
194
|
+
type, key, unit = meta_key
|
195
|
+
mri = "#{type}:#{sanitize_key(key)}@#{unit}"
|
196
|
+
|
197
|
+
# note this needs to be an array but it really doesn't serve a purpose right now
|
198
|
+
[mri, [location.merge(type: 'location')]]
|
199
|
+
end.to_h
|
200
|
+
|
201
|
+
{ timestamp: timestamp, mapping: mapping }
|
202
|
+
end
|
203
|
+
|
204
|
+
def sanitize_key(key)
|
205
|
+
key.gsub(KEY_SANITIZATION_REGEX, '_')
|
206
|
+
end
|
207
|
+
|
208
|
+
def sanitize_value(value)
|
209
|
+
value.gsub(VALUE_SANITIZATION_REGEX, '')
|
210
|
+
end
|
211
|
+
|
212
|
+
def get_transaction_name
|
213
|
+
scope = Sentry.get_current_scope
|
214
|
+
return nil unless scope && scope.transaction_name
|
215
|
+
return nil if scope.transaction_source_low_quality?
|
216
|
+
|
217
|
+
scope.transaction_name
|
218
|
+
end
|
219
|
+
|
220
|
+
def get_updated_tags(tags)
|
221
|
+
updated_tags = @default_tags.merge(tags)
|
222
|
+
|
223
|
+
transaction_name = get_transaction_name
|
224
|
+
updated_tags['transaction'] = transaction_name if transaction_name
|
225
|
+
|
226
|
+
updated_tags
|
227
|
+
end
|
228
|
+
|
229
|
+
def process_span_aggregator(key, value)
|
230
|
+
scope = Sentry.get_current_scope
|
231
|
+
return nil unless scope && scope.span
|
232
|
+
return nil if scope.transaction_source_low_quality?
|
233
|
+
|
234
|
+
scope.span.metrics_local_aggregator.add(key, value)
|
235
|
+
end
|
236
|
+
|
237
|
+
def process_bucket(timestamp, key, type, value)
|
238
|
+
@buckets[timestamp] ||= {}
|
239
|
+
|
240
|
+
if (metric = @buckets[timestamp][key])
|
241
|
+
old_weight = metric.weight
|
242
|
+
metric.add(value)
|
243
|
+
metric.weight - old_weight
|
244
|
+
else
|
245
|
+
metric = METRIC_TYPES[type].new(value)
|
246
|
+
@buckets[timestamp][key] = metric
|
247
|
+
metric.weight
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def record_code_location(type, key, unit, timestamp, stacklevel: nil)
|
252
|
+
meta_key = [type, key, unit]
|
253
|
+
start_of_day = Time.utc(timestamp.year, timestamp.month, timestamp.day).to_i
|
254
|
+
|
255
|
+
@code_locations[start_of_day] ||= {}
|
256
|
+
@code_locations[start_of_day][meta_key] ||= @stacktrace_builder.metrics_code_location(caller[stacklevel || DEFAULT_STACKLEVEL])
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class Configuration
|
6
|
+
include ArgumentCheckingHelper
|
7
|
+
|
8
|
+
# Enable metrics usage.
|
9
|
+
# Starts a new {Sentry::Metrics::Aggregator} instance to aggregate metrics
|
10
|
+
# and a thread to aggregate flush every 5 seconds.
|
11
|
+
# @return [Boolean]
|
12
|
+
attr_accessor :enabled
|
13
|
+
|
14
|
+
# Enable code location reporting.
|
15
|
+
# Will be sent once per day.
|
16
|
+
# True by default.
|
17
|
+
# @return [Boolean]
|
18
|
+
attr_accessor :enable_code_locations
|
19
|
+
|
20
|
+
# Optional Proc, called before emitting a metric to the aggregator.
|
21
|
+
# Use it to filter keys (return false/nil) or update tags.
|
22
|
+
# Make sure to return true at the end.
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# config.metrics.before_emit = lambda do |key, tags|
|
26
|
+
# return nil if key == 'foo'
|
27
|
+
# tags[:bar] = 42
|
28
|
+
# tags.delete(:baz)
|
29
|
+
# true
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# @return [Proc, nil]
|
33
|
+
attr_reader :before_emit
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@enabled = false
|
37
|
+
@enable_code_locations = true
|
38
|
+
end
|
39
|
+
|
40
|
+
def before_emit=(value)
|
41
|
+
check_callable!("metrics.before_emit", value)
|
42
|
+
|
43
|
+
@before_emit = value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class CounterMetric < Metric
|
6
|
+
attr_reader :value
|
7
|
+
|
8
|
+
def initialize(value)
|
9
|
+
@value = value.to_f
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(value)
|
13
|
+
@value += value.to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialize
|
17
|
+
[value]
|
18
|
+
end
|
19
|
+
|
20
|
+
def weight
|
21
|
+
1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class DistributionMetric < Metric
|
6
|
+
attr_reader :value
|
7
|
+
|
8
|
+
def initialize(value)
|
9
|
+
@value = [value.to_f]
|
10
|
+
end
|
11
|
+
|
12
|
+
def add(value)
|
13
|
+
@value << value.to_f
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialize
|
17
|
+
value
|
18
|
+
end
|
19
|
+
|
20
|
+
def weight
|
21
|
+
value.size
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class GaugeMetric < Metric
|
6
|
+
attr_reader :last, :min, :max, :sum, :count
|
7
|
+
|
8
|
+
def initialize(value)
|
9
|
+
value = value.to_f
|
10
|
+
@last = value
|
11
|
+
@min = value
|
12
|
+
@max = value
|
13
|
+
@sum = value
|
14
|
+
@count = 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def add(value)
|
18
|
+
value = value.to_f
|
19
|
+
@last = value
|
20
|
+
@min = [@min, value].min
|
21
|
+
@max = [@max, value].max
|
22
|
+
@sum += value
|
23
|
+
@count += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
def serialize
|
27
|
+
[last, min, max, sum, count]
|
28
|
+
end
|
29
|
+
|
30
|
+
def weight
|
31
|
+
5
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class LocalAggregator
|
6
|
+
# exposed only for testing
|
7
|
+
attr_reader :buckets
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@buckets = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(key, value)
|
14
|
+
if @buckets[key]
|
15
|
+
@buckets[key].add(value)
|
16
|
+
else
|
17
|
+
@buckets[key] = GaugeMetric.new(value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_hash
|
22
|
+
return nil if @buckets.empty?
|
23
|
+
|
24
|
+
@buckets.map do |bucket_key, metric|
|
25
|
+
type, key, unit, tags = bucket_key
|
26
|
+
|
27
|
+
payload_key = "#{type}:#{key}@#{unit}"
|
28
|
+
payload_value = {
|
29
|
+
tags: deserialize_tags(tags),
|
30
|
+
min: metric.min,
|
31
|
+
max: metric.max,
|
32
|
+
count: metric.count,
|
33
|
+
sum: metric.sum
|
34
|
+
}
|
35
|
+
|
36
|
+
[payload_key, payload_value]
|
37
|
+
end.to_h
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def deserialize_tags(tags)
|
43
|
+
tags.inject({}) do |h, tag|
|
44
|
+
k, v = tag
|
45
|
+
old = h[k]
|
46
|
+
# make it an array if key repeats
|
47
|
+
h[k] = old ? (old.is_a?(Array) ? old << v : [old, v]) : v
|
48
|
+
h
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
class Metric
|
6
|
+
def add(value)
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def weight
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'zlib'
|
5
|
+
|
6
|
+
module Sentry
|
7
|
+
module Metrics
|
8
|
+
class SetMetric < Metric
|
9
|
+
attr_reader :value
|
10
|
+
|
11
|
+
def initialize(value)
|
12
|
+
@value = Set[value]
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(value)
|
16
|
+
@value << value
|
17
|
+
end
|
18
|
+
|
19
|
+
def serialize
|
20
|
+
value.map { |x| x.is_a?(String) ? Zlib.crc32(x) : x.to_i }
|
21
|
+
end
|
22
|
+
|
23
|
+
def weight
|
24
|
+
value.size
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
module Metrics
|
5
|
+
module Timing
|
6
|
+
class << self
|
7
|
+
def nanosecond
|
8
|
+
time = Sentry.utc_now
|
9
|
+
time.to_i * (10 ** 9) + time.nsec
|
10
|
+
end
|
11
|
+
|
12
|
+
def microsecond
|
13
|
+
time = Sentry.utc_now
|
14
|
+
time.to_i * (10 ** 6) + time.usec
|
15
|
+
end
|
16
|
+
|
17
|
+
def millisecond
|
18
|
+
Sentry.utc_now.to_i * (10 ** 3)
|
19
|
+
end
|
20
|
+
|
21
|
+
def second
|
22
|
+
Sentry.utc_now.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def minute
|
26
|
+
Sentry.utc_now.to_i / 60.0
|
27
|
+
end
|
28
|
+
|
29
|
+
def hour
|
30
|
+
Sentry.utc_now.to_i / 3600.0
|
31
|
+
end
|
32
|
+
|
33
|
+
def day
|
34
|
+
Sentry.utc_now.to_i / (3600.0 * 24.0)
|
35
|
+
end
|
36
|
+
|
37
|
+
def week
|
38
|
+
Sentry.utc_now.to_i / (3600.0 * 24.0 * 7.0)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sentry/metrics/metric'
|
4
|
+
require 'sentry/metrics/counter_metric'
|
5
|
+
require 'sentry/metrics/distribution_metric'
|
6
|
+
require 'sentry/metrics/gauge_metric'
|
7
|
+
require 'sentry/metrics/set_metric'
|
8
|
+
require 'sentry/metrics/timing'
|
9
|
+
require 'sentry/metrics/aggregator'
|
10
|
+
|
11
|
+
module Sentry
|
12
|
+
module Metrics
|
13
|
+
DURATION_UNITS = %w[nanosecond microsecond millisecond second minute hour day week]
|
14
|
+
INFORMATION_UNITS = %w[bit byte kilobyte kibibyte megabyte mebibyte gigabyte gibibyte terabyte tebibyte petabyte pebibyte exabyte exbibyte]
|
15
|
+
FRACTIONAL_UNITS = %w[ratio percent]
|
16
|
+
|
17
|
+
OP_NAME = 'metric.timing'
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def increment(key, value = 1.0, unit: 'none', tags: {}, timestamp: nil)
|
21
|
+
Sentry.metrics_aggregator&.add(:c, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
22
|
+
end
|
23
|
+
|
24
|
+
def distribution(key, value, unit: 'none', tags: {}, timestamp: nil)
|
25
|
+
Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
26
|
+
end
|
27
|
+
|
28
|
+
def set(key, value, unit: 'none', tags: {}, timestamp: nil)
|
29
|
+
Sentry.metrics_aggregator&.add(:s, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
30
|
+
end
|
31
|
+
|
32
|
+
def gauge(key, value, unit: 'none', tags: {}, timestamp: nil)
|
33
|
+
Sentry.metrics_aggregator&.add(:g, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
34
|
+
end
|
35
|
+
|
36
|
+
def timing(key, unit: 'second', tags: {}, timestamp: nil, &block)
|
37
|
+
return unless block_given?
|
38
|
+
return yield unless DURATION_UNITS.include?(unit)
|
39
|
+
|
40
|
+
result, value = Sentry.with_child_span(op: OP_NAME, description: key) do |span|
|
41
|
+
tags.each { |k, v| span.set_tag(k, v.is_a?(Array) ? v.join(', ') : v.to_s) } if span
|
42
|
+
|
43
|
+
start = Timing.send(unit.to_sym)
|
44
|
+
result = yield
|
45
|
+
value = Timing.send(unit.to_sym) - start
|
46
|
+
|
47
|
+
[result, value]
|
48
|
+
end
|
49
|
+
|
50
|
+
Sentry.metrics_aggregator&.add(:d, key, value, unit: unit, tags: tags, timestamp: timestamp)
|
51
|
+
result
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -50,14 +50,15 @@ module Sentry
|
|
50
50
|
if sentry_trace_data
|
51
51
|
@trace_id, @parent_span_id, @parent_sampled = sentry_trace_data
|
52
52
|
|
53
|
-
@baggage =
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
53
|
+
@baggage =
|
54
|
+
if baggage_header && !baggage_header.empty?
|
55
|
+
Baggage.from_incoming_header(baggage_header)
|
56
|
+
else
|
57
|
+
# If there's an incoming sentry-trace but no incoming baggage header,
|
58
|
+
# for instance in traces coming from older SDKs,
|
59
|
+
# baggage will be empty and frozen and won't be populated as head SDK.
|
60
|
+
Baggage.new({})
|
61
|
+
end
|
61
62
|
|
62
63
|
@baggage.freeze!
|
63
64
|
@incoming_trace = true
|
data/lib/sentry/puma.rb
CHANGED
@@ -4,6 +4,7 @@ module Sentry
|
|
4
4
|
module Rack
|
5
5
|
class CaptureExceptions
|
6
6
|
ERROR_EVENT_ID_KEY = "sentry.error_event_id"
|
7
|
+
MECHANISM_TYPE = "rack"
|
7
8
|
|
8
9
|
def initialize(app)
|
9
10
|
@app = app
|
@@ -56,7 +57,7 @@ module Sentry
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def capture_exception(exception, env)
|
59
|
-
Sentry.capture_exception(exception).tap do |event|
|
60
|
+
Sentry.capture_exception(exception, hint: { mechanism: mechanism }).tap do |event|
|
60
61
|
env[ERROR_EVENT_ID_KEY] = event.event_id if event
|
61
62
|
end
|
62
63
|
end
|
@@ -74,6 +75,10 @@ module Sentry
|
|
74
75
|
transaction.set_http_status(status_code)
|
75
76
|
transaction.finish
|
76
77
|
end
|
78
|
+
|
79
|
+
def mechanism
|
80
|
+
Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
|
81
|
+
end
|
77
82
|
end
|
78
83
|
end
|
79
84
|
end
|
data/lib/sentry/rake.rb
CHANGED
@@ -8,7 +8,9 @@ module Sentry
|
|
8
8
|
module Application
|
9
9
|
# @api private
|
10
10
|
def display_error_message(ex)
|
11
|
-
Sentry.
|
11
|
+
mechanism = Sentry::Mechanism.new(type: 'rake', handled: false)
|
12
|
+
|
13
|
+
Sentry.capture_exception(ex, hint: { mechanism: mechanism }) do |scope|
|
12
14
|
task_name = top_level_tasks.join(' ')
|
13
15
|
scope.set_transaction_name(task_name, source: :task)
|
14
16
|
scope.set_tag("rake_task", task_name)
|
data/lib/sentry/scope.rb
CHANGED
@@ -252,6 +252,12 @@ module Sentry
|
|
252
252
|
@transaction_sources.last
|
253
253
|
end
|
254
254
|
|
255
|
+
# These are high cardinality and thus bad.
|
256
|
+
# @return [Boolean]
|
257
|
+
def transaction_source_low_quality?
|
258
|
+
transaction_source == :url
|
259
|
+
end
|
260
|
+
|
255
261
|
# Returns the associated Transaction object.
|
256
262
|
# @return [Transaction, nil]
|
257
263
|
def get_transaction
|
@@ -295,7 +301,7 @@ module Sentry
|
|
295
301
|
private
|
296
302
|
|
297
303
|
def set_default_value
|
298
|
-
@contexts = { :
|
304
|
+
@contexts = { os: self.class.os_context, runtime: self.class.runtime_context }
|
299
305
|
@extra = {}
|
300
306
|
@tags = {}
|
301
307
|
@user = {}
|
@@ -355,6 +361,5 @@ module Sentry
|
|
355
361
|
global_event_processors << block
|
356
362
|
end
|
357
363
|
end
|
358
|
-
|
359
364
|
end
|
360
365
|
end
|