sentry-ruby-core 5.16.1 → 5.17.1
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 +2 -2
- 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/event.rb +8 -8
- data/lib/sentry/hub.rb +1 -1
- data/lib/sentry/interfaces/exception.rb +1 -0
- data/lib/sentry/interfaces/request.rb +2 -2
- 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/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 +14 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cae396d3892787f7367cea0f73be32f9760efb811bae3a182a09353bbcf3b24
|
4
|
+
data.tar.gz: 37165f9ec11f6a8d5ab00e8638064cb4ef288f5103d511ae8d462deda44c5147
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 96df11cd0861fdc096641b4cf88f5be36b626214c5312ef7570da8567125b517394d99c6a5601f7f25f5a6871d6918cd8f3d6918b63ff54d8b732b19bb9ea39b
|
7
|
+
data.tar.gz: 6f39a9d62409c34d0f3a3d6e72614f509899c642d7303ea041f7620c5b753b38fd2acd3a84eb9b7758a33548d8055fb53e24b5d365dd997ae6ccb5054ff22fb9
|
data/README.md
CHANGED
@@ -90,7 +90,7 @@ To learn more about sampling transactions, please visit the [official documentat
|
|
90
90
|
- [Sidekiq](https://docs.sentry.io/platforms/ruby/guides/sidekiq/)
|
91
91
|
- [DelayedJob](https://docs.sentry.io/platforms/ruby/guides/delayed_job/)
|
92
92
|
- [Resque](https://docs.sentry.io/platforms/ruby/guides/resque/)
|
93
|
-
- [
|
93
|
+
- [OpenTelemetry](https://docs.sentry.io/platforms/ruby/performance/instrumentation/opentelemetry/)
|
94
94
|
|
95
95
|
### Enriching Events
|
96
96
|
|
data/Rakefile
CHANGED
@@ -31,7 +31,7 @@ module Sentry
|
|
31
31
|
log_debug("config.background_worker_threads is set to 0, all events will be sent synchronously")
|
32
32
|
Concurrent::ImmediateExecutor.new
|
33
33
|
else
|
34
|
-
log_debug("Initializing the background worker with #{@number_of_threads} threads")
|
34
|
+
log_debug("Initializing the Sentry background worker with #{@number_of_threads} threads")
|
35
35
|
|
36
36
|
executor = Concurrent::ThreadPoolExecutor.new(
|
37
37
|
min_threads: 0,
|
data/lib/sentry/backtrace.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rubygems"
|
4
|
+
|
3
5
|
module Sentry
|
4
6
|
# @api private
|
5
7
|
class Backtrace
|
@@ -10,7 +12,7 @@ module Sentry
|
|
10
12
|
RUBY_INPUT_FORMAT = /
|
11
13
|
^ \s* (?: [a-zA-Z]: | uri:classloader: )? ([^:]+ | <.*>):
|
12
14
|
(\d+)
|
13
|
-
(?: :in
|
15
|
+
(?: :in\s('|`)([^']+)')?$
|
14
16
|
/x.freeze
|
15
17
|
|
16
18
|
# org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:170)
|
@@ -33,10 +35,10 @@ module Sentry
|
|
33
35
|
# Parses a single line of a given backtrace
|
34
36
|
# @param [String] unparsed_line The raw line from +caller+ or some backtrace
|
35
37
|
# @return [Line] The parsed backtrace line
|
36
|
-
def self.parse(unparsed_line, in_app_pattern)
|
38
|
+
def self.parse(unparsed_line, in_app_pattern = nil)
|
37
39
|
ruby_match = unparsed_line.match(RUBY_INPUT_FORMAT)
|
38
40
|
if ruby_match
|
39
|
-
_, file, number, method = ruby_match.to_a
|
41
|
+
_, file, number, _, method = ruby_match.to_a
|
40
42
|
file.sub!(/\.class$/, RB_EXTENSION)
|
41
43
|
module_name = nil
|
42
44
|
else
|
@@ -55,6 +57,8 @@ module Sentry
|
|
55
57
|
end
|
56
58
|
|
57
59
|
def in_app
|
60
|
+
return false unless in_app_pattern
|
61
|
+
|
58
62
|
if file =~ in_app_pattern
|
59
63
|
true
|
60
64
|
else
|
data/lib/sentry/client.rb
CHANGED
@@ -172,8 +172,8 @@ module Sentry
|
|
172
172
|
end
|
173
173
|
end
|
174
174
|
|
175
|
-
transport.send_event(event)
|
176
|
-
spotlight_transport
|
175
|
+
transport.send_event(event) if configuration.sending_to_dsn_allowed?
|
176
|
+
spotlight_transport.send_event(event) if spotlight_transport
|
177
177
|
|
178
178
|
event
|
179
179
|
rescue => e
|
data/lib/sentry/configuration.rb
CHANGED
@@ -8,6 +8,7 @@ require "sentry/dsn"
|
|
8
8
|
require "sentry/release_detector"
|
9
9
|
require "sentry/transport/configuration"
|
10
10
|
require "sentry/cron/configuration"
|
11
|
+
require "sentry/metrics/configuration"
|
11
12
|
require "sentry/linecache"
|
12
13
|
require "sentry/interfaces/stacktrace_builder"
|
13
14
|
|
@@ -235,6 +236,10 @@ module Sentry
|
|
235
236
|
# @return [Cron::Configuration]
|
236
237
|
attr_reader :cron
|
237
238
|
|
239
|
+
# Metrics related configuration.
|
240
|
+
# @return [Metrics::Configuration]
|
241
|
+
attr_reader :metrics
|
242
|
+
|
238
243
|
# Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
|
239
244
|
# @return [Float, nil]
|
240
245
|
attr_reader :traces_sample_rate
|
@@ -311,11 +316,11 @@ module Sentry
|
|
311
316
|
'Sinatra::NotFound'
|
312
317
|
].freeze
|
313
318
|
|
314
|
-
RACK_ENV_WHITELIST_DEFAULT = %w
|
319
|
+
RACK_ENV_WHITELIST_DEFAULT = %w[
|
315
320
|
REMOTE_ADDR
|
316
321
|
SERVER_NAME
|
317
322
|
SERVER_PORT
|
318
|
-
|
323
|
+
].freeze
|
319
324
|
|
320
325
|
HEROKU_DYNO_METADATA_MESSAGE = "You are running on Heroku but haven't enabled Dyno Metadata. For Sentry's "\
|
321
326
|
"release detection to work correctly, please run `heroku labs:enable runtime-dyno-metadata`".freeze
|
@@ -328,7 +333,7 @@ module Sentry
|
|
328
333
|
|
329
334
|
PROPAGATION_TARGETS_MATCH_ALL = /.*/.freeze
|
330
335
|
|
331
|
-
DEFAULT_PATCHES = %i
|
336
|
+
DEFAULT_PATCHES = %i[redis puma http].freeze
|
332
337
|
|
333
338
|
class << self
|
334
339
|
# Post initialization callbacks are called at the end of initialization process
|
@@ -337,7 +342,7 @@ module Sentry
|
|
337
342
|
@post_initialization_callbacks ||= []
|
338
343
|
end
|
339
344
|
|
340
|
-
|
345
|
+
# allow extensions to add their hooks to the Configuration class
|
341
346
|
def add_post_initialization_callback(&block)
|
342
347
|
post_initialization_callbacks << block
|
343
348
|
end
|
@@ -386,6 +391,7 @@ module Sentry
|
|
386
391
|
|
387
392
|
@transport = Transport::Configuration.new
|
388
393
|
@cron = Cron::Configuration.new
|
394
|
+
@metrics = Metrics::Configuration.new
|
389
395
|
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
390
396
|
|
391
397
|
run_post_initialization_callbacks
|
@@ -479,9 +485,13 @@ module Sentry
|
|
479
485
|
end
|
480
486
|
|
481
487
|
def sending_allowed?
|
488
|
+
spotlight || sending_to_dsn_allowed?
|
489
|
+
end
|
490
|
+
|
491
|
+
def sending_to_dsn_allowed?
|
482
492
|
@errors = []
|
483
493
|
|
484
|
-
|
494
|
+
valid? && capture_in_environment?
|
485
495
|
end
|
486
496
|
|
487
497
|
def sample_allowed?
|
@@ -490,6 +500,10 @@ module Sentry
|
|
490
500
|
Random.rand < sample_rate
|
491
501
|
end
|
492
502
|
|
503
|
+
def session_tracking?
|
504
|
+
auto_session_tracking && enabled_in_current_env?
|
505
|
+
end
|
506
|
+
|
493
507
|
def exception_class_allowed?(exc)
|
494
508
|
if exc.is_a?(Sentry::Error)
|
495
509
|
# Try to prevent error reporting loops
|
@@ -566,12 +580,6 @@ module Sentry
|
|
566
580
|
|
567
581
|
private
|
568
582
|
|
569
|
-
def check_callable!(name, value)
|
570
|
-
unless value == nil || value.respond_to?(:call)
|
571
|
-
raise ArgumentError, "#{name} must be callable (or nil to disable)"
|
572
|
-
end
|
573
|
-
end
|
574
|
-
|
575
583
|
def init_dsn(dsn_string)
|
576
584
|
return if dsn_string.nil? || dsn_string.empty?
|
577
585
|
|
data/lib/sentry/dsn.rb
CHANGED
@@ -5,7 +5,7 @@ require "uri"
|
|
5
5
|
module Sentry
|
6
6
|
class DSN
|
7
7
|
PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
|
8
|
-
REQUIRED_ATTRIBUTES = %w
|
8
|
+
REQUIRED_ATTRIBUTES = %w[host path public_key project_id].freeze
|
9
9
|
|
10
10
|
attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
|
11
11
|
|
data/lib/sentry/envelope.rb
CHANGED
data/lib/sentry/event.rb
CHANGED
@@ -14,16 +14,16 @@ module Sentry
|
|
14
14
|
class Event
|
15
15
|
TYPE = "event"
|
16
16
|
# These are readable attributes.
|
17
|
-
SERIALIZEABLE_ATTRIBUTES = %i
|
17
|
+
SERIALIZEABLE_ATTRIBUTES = %i[
|
18
18
|
event_id level timestamp
|
19
19
|
release environment server_name modules
|
20
20
|
message user tags contexts extra
|
21
21
|
fingerprint breadcrumbs transaction transaction_info
|
22
22
|
platform sdk type
|
23
|
-
|
23
|
+
]
|
24
24
|
|
25
25
|
# These are writable attributes.
|
26
|
-
WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i
|
26
|
+
WRITER_ATTRIBUTES = SERIALIZEABLE_ATTRIBUTES - %i[type timestamp level]
|
27
27
|
|
28
28
|
MAX_MESSAGE_SIZE_IN_BYTES = 1024 * 8
|
29
29
|
|
@@ -145,11 +145,11 @@ module Sentry
|
|
145
145
|
# REMOTE_ADDR to determine the Event IP, and must use other headers instead.
|
146
146
|
def calculate_real_ip_from_rack(env)
|
147
147
|
Utils::RealIp.new(
|
148
|
-
:
|
149
|
-
:
|
150
|
-
:
|
151
|
-
:
|
152
|
-
:
|
148
|
+
remote_addr: env["REMOTE_ADDR"],
|
149
|
+
client_ip: env["HTTP_CLIENT_IP"],
|
150
|
+
real_ip: env["HTTP_X_REAL_IP"],
|
151
|
+
forwarded_for: env["HTTP_X_FORWARDED_FOR"],
|
152
|
+
trusted_proxies: @trusted_proxies
|
153
153
|
).calculate_ip
|
154
154
|
end
|
155
155
|
end
|
data/lib/sentry/hub.rb
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class RequestInterface < Interface
|
5
|
-
REQUEST_ID_HEADERS = %w
|
6
|
-
CONTENT_HEADERS = %w
|
5
|
+
REQUEST_ID_HEADERS = %w[action_dispatch.request_id HTTP_X_REQUEST_ID].freeze
|
6
|
+
CONTENT_HEADERS = %w[CONTENT_TYPE CONTENT_LENGTH].freeze
|
7
7
|
IP_HEADERS = [
|
8
8
|
"REMOTE_ADDR",
|
9
9
|
"HTTP_CLIENT_IP",
|
@@ -62,6 +62,14 @@ module Sentry
|
|
62
62
|
StacktraceInterface.new(frames: frames)
|
63
63
|
end
|
64
64
|
|
65
|
+
# Get the code location hash for a single line for where metrics where added.
|
66
|
+
# @return [Hash]
|
67
|
+
def metrics_code_location(unparsed_line)
|
68
|
+
parsed_line = Backtrace::Line.parse(unparsed_line)
|
69
|
+
frame = convert_parsed_line_into_frame(parsed_line)
|
70
|
+
frame.to_hash.reject { |k, _| %i[project_root in_app].include?(k) }
|
71
|
+
end
|
72
|
+
|
65
73
|
private
|
66
74
|
|
67
75
|
def convert_parsed_line_into_frame(line)
|
@@ -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
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
|
data/lib/sentry/session.rb
CHANGED
@@ -5,8 +5,8 @@ module Sentry
|
|
5
5
|
attr_reader :started, :status, :aggregation_key
|
6
6
|
|
7
7
|
# TODO-neel add :crashed after adding handled mechanism
|
8
|
-
STATUSES = %i
|
9
|
-
AGGREGATE_STATUSES = %i
|
8
|
+
STATUSES = %i[ok errored exited]
|
9
|
+
AGGREGATE_STATUSES = %i[errored exited]
|
10
10
|
|
11
11
|
def initialize
|
12
12
|
@started = Sentry.utc_now
|
data/lib/sentry/span.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "securerandom"
|
4
|
+
require "sentry/metrics/local_aggregator"
|
4
5
|
|
5
6
|
module Sentry
|
6
7
|
class Span
|
7
|
-
|
8
8
|
# We will try to be consistent with OpenTelemetry on this front going forward.
|
9
9
|
# https://develop.sentry.dev/sdk/performance/span-data-conventions/
|
10
10
|
module DataConventions
|
@@ -150,7 +150,7 @@ module Sentry
|
|
150
150
|
|
151
151
|
# @return [Hash]
|
152
152
|
def to_hash
|
153
|
-
{
|
153
|
+
hash = {
|
154
154
|
trace_id: @trace_id,
|
155
155
|
span_id: @span_id,
|
156
156
|
parent_span_id: @parent_span_id,
|
@@ -162,6 +162,11 @@ module Sentry
|
|
162
162
|
tags: @tags,
|
163
163
|
data: @data
|
164
164
|
}
|
165
|
+
|
166
|
+
summary = metrics_summary
|
167
|
+
hash[:_metrics_summary] = summary if summary
|
168
|
+
|
169
|
+
hash
|
165
170
|
end
|
166
171
|
|
167
172
|
# Returns the span's context that can be used to embed in an Event.
|
@@ -269,5 +274,14 @@ module Sentry
|
|
269
274
|
def set_tag(key, value)
|
270
275
|
@tags[key] = value
|
271
276
|
end
|
277
|
+
|
278
|
+
# Collects gauge metrics on the span for metric summaries.
|
279
|
+
def metrics_local_aggregator
|
280
|
+
@metrics_local_aggregator ||= Sentry::Metrics::LocalAggregator.new
|
281
|
+
end
|
282
|
+
|
283
|
+
def metrics_summary
|
284
|
+
@metrics_local_aggregator&.to_hash
|
285
|
+
end
|
272
286
|
end
|
273
287
|
end
|
data/lib/sentry/transaction.rb
CHANGED
@@ -13,7 +13,7 @@ module Sentry
|
|
13
13
|
MESSAGE_PREFIX = "[Tracing]"
|
14
14
|
|
15
15
|
# https://develop.sentry.dev/sdk/event-payloads/transaction/#transaction-annotations
|
16
|
-
SOURCES = %i
|
16
|
+
SOURCES = %i[custom url route view component task]
|
17
17
|
|
18
18
|
include LoggingHelper
|
19
19
|
|
@@ -110,14 +110,15 @@ module Sentry
|
|
110
110
|
|
111
111
|
trace_id, parent_span_id, parent_sampled = sentry_trace_data
|
112
112
|
|
113
|
-
baggage =
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
113
|
+
baggage =
|
114
|
+
if baggage && !baggage.empty?
|
115
|
+
Baggage.from_incoming_header(baggage)
|
116
|
+
else
|
117
|
+
# If there's an incoming sentry-trace but no incoming baggage header,
|
118
|
+
# for instance in traces coming from older SDKs,
|
119
|
+
# baggage will be empty and frozen and won't be populated as head SDK.
|
120
|
+
Baggage.new({})
|
121
|
+
end
|
121
122
|
|
122
123
|
baggage.freeze!
|
123
124
|
|
@@ -301,6 +302,11 @@ module Sentry
|
|
301
302
|
profiler.start
|
302
303
|
end
|
303
304
|
|
305
|
+
# These are high cardinality and thus bad
|
306
|
+
def source_low_quality?
|
307
|
+
source == :url
|
308
|
+
end
|
309
|
+
|
304
310
|
protected
|
305
311
|
|
306
312
|
def init_span_recorder(limit = 1000)
|
@@ -336,11 +342,6 @@ module Sentry
|
|
336
342
|
@baggage = Baggage.new(items, mutable: false)
|
337
343
|
end
|
338
344
|
|
339
|
-
# These are high cardinality and thus bad
|
340
|
-
def source_low_quality?
|
341
|
-
source == :url
|
342
|
-
end
|
343
|
-
|
344
345
|
class SpanRecorder
|
345
346
|
attr_reader :max_length, :spans
|
346
347
|
|
@@ -17,6 +17,9 @@ module Sentry
|
|
17
17
|
# @return [Hash, nil]
|
18
18
|
attr_accessor :profile
|
19
19
|
|
20
|
+
# @return [Hash, nil]
|
21
|
+
attr_accessor :metrics_summary
|
22
|
+
|
20
23
|
def initialize(transaction:, **options)
|
21
24
|
super(**options)
|
22
25
|
|
@@ -29,6 +32,7 @@ module Sentry
|
|
29
32
|
self.tags = transaction.tags
|
30
33
|
self.dynamic_sampling_context = transaction.get_baggage.dynamic_sampling_context
|
31
34
|
self.measurements = transaction.measurements
|
35
|
+
self.metrics_summary = transaction.metrics_summary
|
32
36
|
|
33
37
|
finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
|
34
38
|
self.spans = finished_spans.map(&:to_hash)
|
@@ -49,6 +53,7 @@ module Sentry
|
|
49
53
|
data[:spans] = @spans.map(&:to_hash) if @spans
|
50
54
|
data[:start_timestamp] = @start_timestamp
|
51
55
|
data[:measurements] = @measurements
|
56
|
+
data[:_metrics_summary] = @metrics_summary if @metrics_summary
|
52
57
|
data
|
53
58
|
end
|
54
59
|
|
data/lib/sentry/transport.rb
CHANGED
@@ -15,5 +15,11 @@ module Sentry
|
|
15
15
|
raise ArgumentError, "expect the argument to be one of #{values.map(&:inspect).join(' or ')}, got #{argument.inspect}"
|
16
16
|
end
|
17
17
|
end
|
18
|
+
|
19
|
+
def check_callable!(name, value)
|
20
|
+
unless value == nil || value.respond_to?(:call)
|
21
|
+
raise ArgumentError, "#{name} must be callable (or nil to disable)"
|
22
|
+
end
|
23
|
+
end
|
18
24
|
end
|
19
25
|
end
|
data/lib/sentry/utils/real_ip.rb
CHANGED
@@ -15,7 +15,7 @@ module Sentry
|
|
15
15
|
"fc00::/7", # private IPv6 range fc00::/7
|
16
16
|
"10.0.0.0/8", # private IPv4 range 10.x.x.x
|
17
17
|
"172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
|
18
|
-
"192.168.0.0/16"
|
18
|
+
"192.168.0.0/16" # private IPv4 range 192.168.x.x
|
19
19
|
]
|
20
20
|
|
21
21
|
attr_reader :ip
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Sentry
|
4
4
|
module Utils
|
5
5
|
module RequestId
|
6
|
-
REQUEST_ID_HEADERS = %w
|
6
|
+
REQUEST_ID_HEADERS = %w[action_dispatch.request_id HTTP_X_REQUEST_ID].freeze
|
7
7
|
|
8
8
|
# Request ID based on ActionDispatch::RequestId
|
9
9
|
def self.read_from(env)
|
data/lib/sentry/version.rb
CHANGED
data/lib/sentry-ruby.rb
CHANGED
@@ -23,10 +23,11 @@ require "sentry/background_worker"
|
|
23
23
|
require "sentry/session_flusher"
|
24
24
|
require "sentry/backpressure_monitor"
|
25
25
|
require "sentry/cron/monitor_check_ins"
|
26
|
+
require "sentry/metrics"
|
26
27
|
|
27
28
|
[
|
28
29
|
"sentry/rake",
|
29
|
-
"sentry/rack"
|
30
|
+
"sentry/rack"
|
30
31
|
].each do |lib|
|
31
32
|
begin
|
32
33
|
require lib
|
@@ -77,6 +78,10 @@ module Sentry
|
|
77
78
|
# @return [BackpressureMonitor, nil]
|
78
79
|
attr_reader :backpressure_monitor
|
79
80
|
|
81
|
+
# @!attribute [r] metrics_aggregator
|
82
|
+
# @return [Metrics::Aggregator, nil]
|
83
|
+
attr_reader :metrics_aggregator
|
84
|
+
|
80
85
|
##### Patch Registration #####
|
81
86
|
|
82
87
|
# @!visibility private
|
@@ -222,8 +227,9 @@ module Sentry
|
|
222
227
|
Thread.current.thread_variable_set(THREAD_LOCAL, hub)
|
223
228
|
@main_hub = hub
|
224
229
|
@background_worker = Sentry::BackgroundWorker.new(config)
|
225
|
-
@session_flusher = config.
|
230
|
+
@session_flusher = config.session_tracking? ? Sentry::SessionFlusher.new(config, client) : nil
|
226
231
|
@backpressure_monitor = config.enable_backpressure_handling ? Sentry::BackpressureMonitor.new(config, client) : nil
|
232
|
+
@metrics_aggregator = config.metrics.enabled ? Sentry::Metrics::Aggregator.new(config, client) : nil
|
227
233
|
exception_locals_tp.enable if config.include_local_variables
|
228
234
|
at_exit { close }
|
229
235
|
end
|
@@ -244,6 +250,12 @@ module Sentry
|
|
244
250
|
@backpressure_monitor = nil
|
245
251
|
end
|
246
252
|
|
253
|
+
if @metrics_aggregator
|
254
|
+
@metrics_aggregator.flush(force: true)
|
255
|
+
@metrics_aggregator.kill
|
256
|
+
@metrics_aggregator = nil
|
257
|
+
end
|
258
|
+
|
247
259
|
if client = get_current_client
|
248
260
|
client.transport.flush
|
249
261
|
|
data/sentry-ruby.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sentry-ruby-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.
|
4
|
+
version: 5.17.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sentry Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sentry-ruby
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 5.
|
19
|
+
version: 5.17.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 5.
|
26
|
+
version: 5.17.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: concurrent-ruby
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,6 +90,16 @@ files:
|
|
90
90
|
- lib/sentry/interfaces/threads.rb
|
91
91
|
- lib/sentry/linecache.rb
|
92
92
|
- lib/sentry/logger.rb
|
93
|
+
- lib/sentry/metrics.rb
|
94
|
+
- lib/sentry/metrics/aggregator.rb
|
95
|
+
- lib/sentry/metrics/configuration.rb
|
96
|
+
- lib/sentry/metrics/counter_metric.rb
|
97
|
+
- lib/sentry/metrics/distribution_metric.rb
|
98
|
+
- lib/sentry/metrics/gauge_metric.rb
|
99
|
+
- lib/sentry/metrics/local_aggregator.rb
|
100
|
+
- lib/sentry/metrics/metric.rb
|
101
|
+
- lib/sentry/metrics/set_metric.rb
|
102
|
+
- lib/sentry/metrics/timing.rb
|
93
103
|
- lib/sentry/net/http.rb
|
94
104
|
- lib/sentry/profiler.rb
|
95
105
|
- lib/sentry/propagation_context.rb
|