sentry-ruby 5.26.0 → 6.1.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/Gemfile +2 -4
- data/lib/sentry/background_worker.rb +1 -4
- data/lib/sentry/breadcrumb.rb +1 -1
- data/lib/sentry/breadcrumb_buffer.rb +2 -2
- data/lib/sentry/check_in_event.rb +2 -2
- data/lib/sentry/client.rb +29 -89
- data/lib/sentry/configuration.rb +125 -78
- data/lib/sentry/cron/monitor_check_ins.rb +3 -3
- data/lib/sentry/cron/monitor_config.rb +2 -2
- data/lib/sentry/cron/monitor_schedule.rb +2 -2
- data/lib/sentry/debug_structured_logger.rb +94 -0
- data/lib/sentry/dsn.rb +32 -0
- data/lib/sentry/envelope/item.rb +1 -2
- data/lib/sentry/error_event.rb +3 -3
- data/lib/sentry/event.rb +4 -10
- data/lib/sentry/graphql.rb +1 -1
- data/lib/sentry/hub.rb +6 -5
- data/lib/sentry/interface.rb +1 -1
- data/lib/sentry/interfaces/exception.rb +2 -2
- data/lib/sentry/interfaces/request.rb +2 -0
- data/lib/sentry/interfaces/single_exception.rb +3 -3
- data/lib/sentry/interfaces/stacktrace.rb +3 -3
- data/lib/sentry/interfaces/stacktrace_builder.rb +0 -8
- data/lib/sentry/interfaces/threads.rb +2 -2
- data/lib/sentry/log_event.rb +19 -6
- data/lib/sentry/profiler.rb +4 -5
- data/lib/sentry/propagation_context.rb +55 -18
- data/lib/sentry/rspec.rb +1 -1
- data/lib/sentry/span.rb +2 -17
- data/lib/sentry/std_lib_logger.rb +6 -1
- data/lib/sentry/test_helper.rb +23 -0
- data/lib/sentry/transaction.rb +72 -95
- data/lib/sentry/transaction_event.rb +4 -9
- data/lib/sentry/transport/debug_transport.rb +70 -0
- data/lib/sentry/transport/dummy_transport.rb +1 -0
- data/lib/sentry/transport/http_transport.rb +9 -5
- data/lib/sentry/transport.rb +3 -5
- data/lib/sentry/utils/logging_helper.rb +8 -6
- data/lib/sentry/utils/sample_rand.rb +97 -0
- data/lib/sentry/vernier/profiler.rb +4 -3
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +6 -30
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +1 -1
- metadata +11 -18
- data/lib/sentry/metrics/aggregator.rb +0 -248
- data/lib/sentry/metrics/configuration.rb +0 -47
- data/lib/sentry/metrics/counter_metric.rb +0 -25
- data/lib/sentry/metrics/distribution_metric.rb +0 -25
- data/lib/sentry/metrics/gauge_metric.rb +0 -35
- data/lib/sentry/metrics/local_aggregator.rb +0 -53
- data/lib/sentry/metrics/metric.rb +0 -19
- data/lib/sentry/metrics/set_metric.rb +0 -28
- data/lib/sentry/metrics/timing.rb +0 -51
- data/lib/sentry/metrics.rb +0 -56
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
module Utils
|
|
5
|
+
class SampleRand
|
|
6
|
+
PRECISION = 1_000_000.0
|
|
7
|
+
FORMAT_PRECISION = 6
|
|
8
|
+
|
|
9
|
+
attr_reader :trace_id
|
|
10
|
+
|
|
11
|
+
def self.valid?(value)
|
|
12
|
+
return false unless value
|
|
13
|
+
value >= 0.0 && value < 1.0
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.format(value)
|
|
17
|
+
return unless value
|
|
18
|
+
|
|
19
|
+
truncated = (value * PRECISION).floor / PRECISION
|
|
20
|
+
"%.#{FORMAT_PRECISION}f" % truncated
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(trace_id: nil)
|
|
24
|
+
@trace_id = trace_id
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def generate_from_trace_id
|
|
28
|
+
(random_from_trace_id * PRECISION).floor / PRECISION
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def generate_from_sampling_decision(sampled, sample_rate)
|
|
32
|
+
if invalid_sample_rate?(sample_rate)
|
|
33
|
+
fallback_generation
|
|
34
|
+
else
|
|
35
|
+
generate_based_on_sampling(sampled, sample_rate)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def generate_from_value(sample_rand_value)
|
|
40
|
+
parsed_value = parse_value(sample_rand_value)
|
|
41
|
+
|
|
42
|
+
if self.class.valid?(parsed_value)
|
|
43
|
+
parsed_value
|
|
44
|
+
else
|
|
45
|
+
fallback_generation
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def random_from_trace_id
|
|
52
|
+
if @trace_id
|
|
53
|
+
Random.new(@trace_id[0, 16].to_i(16))
|
|
54
|
+
else
|
|
55
|
+
Random.new
|
|
56
|
+
end.rand(1.0)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def invalid_sample_rate?(sample_rate)
|
|
60
|
+
sample_rate.nil? || sample_rate <= 0.0 || sample_rate > 1.0
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def fallback_generation
|
|
64
|
+
if @trace_id
|
|
65
|
+
(random_from_trace_id * PRECISION).floor / PRECISION
|
|
66
|
+
else
|
|
67
|
+
format_random(Random.rand(1.0))
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def generate_based_on_sampling(sampled, sample_rate)
|
|
72
|
+
random = random_from_trace_id
|
|
73
|
+
|
|
74
|
+
result = if sampled
|
|
75
|
+
random * sample_rate
|
|
76
|
+
elsif sample_rate == 1.0
|
|
77
|
+
random
|
|
78
|
+
else
|
|
79
|
+
sample_rate + random * (1.0 - sample_rate)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
format_random(result)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def format_random(value)
|
|
86
|
+
truncated = (value * PRECISION).floor / PRECISION
|
|
87
|
+
("%.#{FORMAT_PRECISION}f" % truncated).to_f
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def parse_value(sample_rand_value)
|
|
91
|
+
Float(sample_rand_value)
|
|
92
|
+
rescue ArgumentError
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -20,6 +20,7 @@ module Sentry
|
|
|
20
20
|
|
|
21
21
|
@profiling_enabled = defined?(Vernier) && configuration.profiling_enabled?
|
|
22
22
|
@profiles_sample_rate = configuration.profiles_sample_rate
|
|
23
|
+
@profiles_sample_interval = configuration.profiles_sample_interval
|
|
23
24
|
@project_root = configuration.project_root
|
|
24
25
|
@app_dirs_pattern = configuration.app_dirs_pattern
|
|
25
26
|
@in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
|
|
@@ -56,7 +57,7 @@ module Sentry
|
|
|
56
57
|
return unless @sampled
|
|
57
58
|
return if @started
|
|
58
59
|
|
|
59
|
-
@started = ::Vernier.start_profile
|
|
60
|
+
@started = ::Vernier.start_profile(interval: @profiles_sample_interval)
|
|
60
61
|
|
|
61
62
|
log("Started")
|
|
62
63
|
|
|
@@ -90,9 +91,9 @@ module Sentry
|
|
|
90
91
|
Thread.current.object_id
|
|
91
92
|
end
|
|
92
93
|
|
|
93
|
-
def
|
|
94
|
+
def to_h
|
|
94
95
|
unless @sampled
|
|
95
|
-
record_lost_event(:sample_rate)
|
|
96
|
+
record_lost_event(:sample_rate) if @profiling_enabled
|
|
96
97
|
return EMPTY_RESULT
|
|
97
98
|
end
|
|
98
99
|
|
data/lib/sentry/version.rb
CHANGED
data/lib/sentry-ruby.rb
CHANGED
|
@@ -10,8 +10,10 @@ require "sentry/core_ext/object/deep_dup"
|
|
|
10
10
|
require "sentry/utils/argument_checking_helper"
|
|
11
11
|
require "sentry/utils/encoding_helper"
|
|
12
12
|
require "sentry/utils/logging_helper"
|
|
13
|
+
require "sentry/utils/sample_rand"
|
|
13
14
|
require "sentry/configuration"
|
|
14
15
|
require "sentry/structured_logger"
|
|
16
|
+
require "sentry/debug_structured_logger"
|
|
15
17
|
require "sentry/event"
|
|
16
18
|
require "sentry/error_event"
|
|
17
19
|
require "sentry/transaction_event"
|
|
@@ -24,7 +26,6 @@ require "sentry/threaded_periodic_worker"
|
|
|
24
26
|
require "sentry/session_flusher"
|
|
25
27
|
require "sentry/backpressure_monitor"
|
|
26
28
|
require "sentry/cron/monitor_check_ins"
|
|
27
|
-
require "sentry/metrics"
|
|
28
29
|
require "sentry/vernier/profiler"
|
|
29
30
|
|
|
30
31
|
[
|
|
@@ -57,7 +58,6 @@ module Sentry
|
|
|
57
58
|
logger
|
|
58
59
|
session_flusher
|
|
59
60
|
backpressure_monitor
|
|
60
|
-
metrics_aggregator
|
|
61
61
|
exception_locals_tp
|
|
62
62
|
].freeze
|
|
63
63
|
|
|
@@ -91,10 +91,6 @@ module Sentry
|
|
|
91
91
|
# @return [BackpressureMonitor, nil]
|
|
92
92
|
attr_reader :backpressure_monitor
|
|
93
93
|
|
|
94
|
-
# @!attribute [r] metrics_aggregator
|
|
95
|
-
# @return [Metrics::Aggregator, nil]
|
|
96
|
-
attr_reader :metrics_aggregator
|
|
97
|
-
|
|
98
94
|
##### Patch Registration #####
|
|
99
95
|
|
|
100
96
|
# @!visibility private
|
|
@@ -237,8 +233,7 @@ module Sentry
|
|
|
237
233
|
# @yieldparam config [Configuration]
|
|
238
234
|
# @return [void]
|
|
239
235
|
def init(&block)
|
|
240
|
-
config = Configuration.new
|
|
241
|
-
yield(config) if block_given?
|
|
236
|
+
config = Configuration.new(&block)
|
|
242
237
|
|
|
243
238
|
config.detect_release
|
|
244
239
|
apply_patches(config)
|
|
@@ -251,7 +246,6 @@ module Sentry
|
|
|
251
246
|
@background_worker = Sentry::BackgroundWorker.new(config)
|
|
252
247
|
@session_flusher = config.session_tracking? ? Sentry::SessionFlusher.new(config, client) : nil
|
|
253
248
|
@backpressure_monitor = config.enable_backpressure_handling ? Sentry::BackpressureMonitor.new(config, client) : nil
|
|
254
|
-
@metrics_aggregator = config.metrics.enabled ? Sentry::Metrics::Aggregator.new(config, client) : nil
|
|
255
249
|
exception_locals_tp.enable if config.include_local_variables
|
|
256
250
|
at_exit { close }
|
|
257
251
|
end
|
|
@@ -272,12 +266,6 @@ module Sentry
|
|
|
272
266
|
@backpressure_monitor = nil
|
|
273
267
|
end
|
|
274
268
|
|
|
275
|
-
if @metrics_aggregator
|
|
276
|
-
@metrics_aggregator.flush(force: true)
|
|
277
|
-
@metrics_aggregator.kill
|
|
278
|
-
@metrics_aggregator = nil
|
|
279
|
-
end
|
|
280
|
-
|
|
281
269
|
if client = get_current_client
|
|
282
270
|
client.flush
|
|
283
271
|
|
|
@@ -498,6 +486,7 @@ module Sentry
|
|
|
498
486
|
# @param [Hash] options Extra log event options
|
|
499
487
|
# @option options [Symbol] level The log level (:trace, :debug, :info, :warn, :error, :fatal)
|
|
500
488
|
# @option options [Integer] severity The severity number according to the Sentry Logs Protocol
|
|
489
|
+
# @option options [String] origin The origin of the log event (e.g., "auto.db.rails", "manual")
|
|
501
490
|
# @option options [Hash] Additional attributes to include with the log
|
|
502
491
|
#
|
|
503
492
|
# @example Direct usage (prefer using Sentry.logger instead)
|
|
@@ -633,22 +622,9 @@ module Sentry
|
|
|
633
622
|
#
|
|
634
623
|
# @see https://develop.sentry.dev/sdk/telemetry/logs/ Sentry SDK Telemetry Logs Protocol
|
|
635
624
|
#
|
|
636
|
-
# @return [StructuredLogger
|
|
625
|
+
# @return [StructuredLogger] The structured logger instance or nil if logs are disabled
|
|
637
626
|
def logger
|
|
638
|
-
@logger ||=
|
|
639
|
-
if configuration.enable_logs
|
|
640
|
-
# Initialize the public-facing Structured Logger if logs are enabled
|
|
641
|
-
# This creates a StructuredLogger instance that implements Sentry's SDK telemetry logs protocol
|
|
642
|
-
# @see https://develop.sentry.dev/sdk/telemetry/logs/
|
|
643
|
-
StructuredLogger.new(configuration)
|
|
644
|
-
else
|
|
645
|
-
warn <<~STR
|
|
646
|
-
[sentry] `Sentry.logger` will no longer be used as internal SDK logger when `enable_logs` feature is turned on.
|
|
647
|
-
Use Sentry.configuration.sdk_logger for SDK-specific logging needs."
|
|
648
|
-
STR
|
|
649
|
-
|
|
650
|
-
configuration.sdk_logger
|
|
651
|
-
end
|
|
627
|
+
@logger ||= configuration.structured_logging.logger_class.new(configuration)
|
|
652
628
|
end
|
|
653
629
|
|
|
654
630
|
##### Helpers #####
|
data/sentry-ruby-core.gemspec
CHANGED
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.homepage = "https://github.com/getsentry/sentry-ruby"
|
|
13
13
|
|
|
14
14
|
spec.platform = Gem::Platform::RUBY
|
|
15
|
-
spec.required_ruby_version = '>= 2.
|
|
15
|
+
spec.required_ruby_version = '>= 2.7'
|
|
16
16
|
spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
|
|
17
17
|
spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples|\.rubocop\.yml)'`.split("\n")
|
|
18
18
|
|
data/sentry-ruby.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.license = 'MIT'
|
|
12
12
|
|
|
13
13
|
spec.platform = Gem::Platform::RUBY
|
|
14
|
-
spec.required_ruby_version = '>= 2.
|
|
14
|
+
spec.required_ruby_version = '>= 2.7'
|
|
15
15
|
spec.extra_rdoc_files = ["README.md", "LICENSE.txt"]
|
|
16
16
|
spec.files = `git ls-files | grep -Ev '^(spec|benchmarks|examples|\.rubocop\.yml)'`.split("\n")
|
|
17
17
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sentry-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 6.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sentry Team
|
|
@@ -80,6 +80,7 @@ files:
|
|
|
80
80
|
- lib/sentry/cron/monitor_check_ins.rb
|
|
81
81
|
- lib/sentry/cron/monitor_config.rb
|
|
82
82
|
- lib/sentry/cron/monitor_schedule.rb
|
|
83
|
+
- lib/sentry/debug_structured_logger.rb
|
|
83
84
|
- lib/sentry/dsn.rb
|
|
84
85
|
- lib/sentry/envelope.rb
|
|
85
86
|
- lib/sentry/envelope/item.rb
|
|
@@ -104,16 +105,6 @@ files:
|
|
|
104
105
|
- lib/sentry/log_event.rb
|
|
105
106
|
- lib/sentry/log_event_buffer.rb
|
|
106
107
|
- lib/sentry/logger.rb
|
|
107
|
-
- lib/sentry/metrics.rb
|
|
108
|
-
- lib/sentry/metrics/aggregator.rb
|
|
109
|
-
- lib/sentry/metrics/configuration.rb
|
|
110
|
-
- lib/sentry/metrics/counter_metric.rb
|
|
111
|
-
- lib/sentry/metrics/distribution_metric.rb
|
|
112
|
-
- lib/sentry/metrics/gauge_metric.rb
|
|
113
|
-
- lib/sentry/metrics/local_aggregator.rb
|
|
114
|
-
- lib/sentry/metrics/metric.rb
|
|
115
|
-
- lib/sentry/metrics/set_metric.rb
|
|
116
|
-
- lib/sentry/metrics/timing.rb
|
|
117
108
|
- lib/sentry/net/http.rb
|
|
118
109
|
- lib/sentry/profiler.rb
|
|
119
110
|
- lib/sentry/profiler/helpers.rb
|
|
@@ -137,6 +128,7 @@ files:
|
|
|
137
128
|
- lib/sentry/transaction_event.rb
|
|
138
129
|
- lib/sentry/transport.rb
|
|
139
130
|
- lib/sentry/transport/configuration.rb
|
|
131
|
+
- lib/sentry/transport/debug_transport.rb
|
|
140
132
|
- lib/sentry/transport/dummy_transport.rb
|
|
141
133
|
- lib/sentry/transport/http_transport.rb
|
|
142
134
|
- lib/sentry/transport/spotlight_transport.rb
|
|
@@ -149,21 +141,22 @@ files:
|
|
|
149
141
|
- lib/sentry/utils/logging_helper.rb
|
|
150
142
|
- lib/sentry/utils/real_ip.rb
|
|
151
143
|
- lib/sentry/utils/request_id.rb
|
|
144
|
+
- lib/sentry/utils/sample_rand.rb
|
|
152
145
|
- lib/sentry/utils/uuid.rb
|
|
153
146
|
- lib/sentry/vernier/output.rb
|
|
154
147
|
- lib/sentry/vernier/profiler.rb
|
|
155
148
|
- lib/sentry/version.rb
|
|
156
149
|
- sentry-ruby-core.gemspec
|
|
157
150
|
- sentry-ruby.gemspec
|
|
158
|
-
homepage: https://github.com/getsentry/sentry-ruby/tree/
|
|
151
|
+
homepage: https://github.com/getsentry/sentry-ruby/tree/6.1.0/sentry-ruby
|
|
159
152
|
licenses:
|
|
160
153
|
- MIT
|
|
161
154
|
metadata:
|
|
162
|
-
homepage_uri: https://github.com/getsentry/sentry-ruby/tree/
|
|
163
|
-
source_code_uri: https://github.com/getsentry/sentry-ruby/tree/
|
|
164
|
-
changelog_uri: https://github.com/getsentry/sentry-ruby/blob/
|
|
155
|
+
homepage_uri: https://github.com/getsentry/sentry-ruby/tree/6.1.0/sentry-ruby
|
|
156
|
+
source_code_uri: https://github.com/getsentry/sentry-ruby/tree/6.1.0/sentry-ruby
|
|
157
|
+
changelog_uri: https://github.com/getsentry/sentry-ruby/blob/6.1.0/CHANGELOG.md
|
|
165
158
|
bug_tracker_uri: https://github.com/getsentry/sentry-ruby/issues
|
|
166
|
-
documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/
|
|
159
|
+
documentation_uri: http://www.rubydoc.info/gems/sentry-ruby/6.1.0
|
|
167
160
|
rdoc_options: []
|
|
168
161
|
require_paths:
|
|
169
162
|
- lib
|
|
@@ -171,14 +164,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
171
164
|
requirements:
|
|
172
165
|
- - ">="
|
|
173
166
|
- !ruby/object:Gem::Version
|
|
174
|
-
version: '2.
|
|
167
|
+
version: '2.7'
|
|
175
168
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
169
|
requirements:
|
|
177
170
|
- - ">="
|
|
178
171
|
- !ruby/object:Gem::Version
|
|
179
172
|
version: '0'
|
|
180
173
|
requirements: []
|
|
181
|
-
rubygems_version: 3.6.
|
|
174
|
+
rubygems_version: 3.6.9
|
|
182
175
|
specification_version: 4
|
|
183
176
|
summary: A gem that provides a client interface for the Sentry error logger
|
|
184
177
|
test_files: []
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Sentry
|
|
4
|
-
module Metrics
|
|
5
|
-
class Aggregator < ThreadedPeriodicWorker
|
|
6
|
-
FLUSH_INTERVAL = 5
|
|
7
|
-
ROLLUP_IN_SECONDS = 10
|
|
8
|
-
|
|
9
|
-
# this is how far removed from user code in the backtrace we are
|
|
10
|
-
# when we record code locations
|
|
11
|
-
DEFAULT_STACKLEVEL = 4
|
|
12
|
-
|
|
13
|
-
KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\-.]+/
|
|
14
|
-
UNIT_SANITIZATION_REGEX = /[^a-zA-Z0-9_]+/
|
|
15
|
-
TAG_KEY_SANITIZATION_REGEX = /[^a-zA-Z0-9_\-.\/]+/
|
|
16
|
-
|
|
17
|
-
TAG_VALUE_SANITIZATION_MAP = {
|
|
18
|
-
"\n" => "\\n",
|
|
19
|
-
"\r" => "\\r",
|
|
20
|
-
"\t" => "\\t",
|
|
21
|
-
"\\" => "\\\\",
|
|
22
|
-
"|" => "\\u{7c}",
|
|
23
|
-
"," => "\\u{2c}"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
METRIC_TYPES = {
|
|
27
|
-
c: CounterMetric,
|
|
28
|
-
d: DistributionMetric,
|
|
29
|
-
g: GaugeMetric,
|
|
30
|
-
s: SetMetric
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
# exposed only for testing
|
|
34
|
-
attr_reader :client, :thread, :buckets, :flush_shift, :code_locations
|
|
35
|
-
|
|
36
|
-
def initialize(configuration, client)
|
|
37
|
-
super(configuration.sdk_logger, FLUSH_INTERVAL)
|
|
38
|
-
@client = client
|
|
39
|
-
@before_emit = configuration.metrics.before_emit
|
|
40
|
-
@enable_code_locations = configuration.metrics.enable_code_locations
|
|
41
|
-
@stacktrace_builder = configuration.stacktrace_builder
|
|
42
|
-
|
|
43
|
-
@default_tags = {}
|
|
44
|
-
@default_tags["release"] = configuration.release if configuration.release
|
|
45
|
-
@default_tags["environment"] = configuration.environment if configuration.environment
|
|
46
|
-
|
|
47
|
-
@mutex = Mutex.new
|
|
48
|
-
|
|
49
|
-
# a nested hash of timestamp -> bucket keys -> Metric instance
|
|
50
|
-
@buckets = {}
|
|
51
|
-
|
|
52
|
-
# the flush interval needs to be shifted once per startup to create jittering
|
|
53
|
-
@flush_shift = Random.rand * ROLLUP_IN_SECONDS
|
|
54
|
-
|
|
55
|
-
# a nested hash of timestamp (start of day) -> meta keys -> frame
|
|
56
|
-
@code_locations = {}
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def add(type,
|
|
60
|
-
key,
|
|
61
|
-
value,
|
|
62
|
-
unit: "none",
|
|
63
|
-
tags: {},
|
|
64
|
-
timestamp: nil,
|
|
65
|
-
stacklevel: nil)
|
|
66
|
-
return unless ensure_thread
|
|
67
|
-
return unless METRIC_TYPES.keys.include?(type)
|
|
68
|
-
|
|
69
|
-
updated_tags = get_updated_tags(tags)
|
|
70
|
-
return if @before_emit && !@before_emit.call(key, updated_tags)
|
|
71
|
-
|
|
72
|
-
timestamp ||= Sentry.utc_now
|
|
73
|
-
|
|
74
|
-
# this is integer division and thus takes the floor of the division
|
|
75
|
-
# and buckets into 10 second intervals
|
|
76
|
-
bucket_timestamp = (timestamp.to_i / ROLLUP_IN_SECONDS) * ROLLUP_IN_SECONDS
|
|
77
|
-
|
|
78
|
-
serialized_tags = serialize_tags(updated_tags)
|
|
79
|
-
bucket_key = [type, key, unit, serialized_tags]
|
|
80
|
-
|
|
81
|
-
added = @mutex.synchronize do
|
|
82
|
-
record_code_location(type, key, unit, timestamp, stacklevel: stacklevel) if @enable_code_locations
|
|
83
|
-
process_bucket(bucket_timestamp, bucket_key, type, value)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# for sets, we pass on if there was a new entry to the local gauge
|
|
87
|
-
local_value = type == :s ? added : value
|
|
88
|
-
process_span_aggregator(bucket_key, local_value)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def flush(force: false)
|
|
92
|
-
flushable_buckets = get_flushable_buckets!(force)
|
|
93
|
-
code_locations = get_code_locations!
|
|
94
|
-
return if flushable_buckets.empty? && code_locations.empty?
|
|
95
|
-
|
|
96
|
-
envelope = Envelope.new
|
|
97
|
-
|
|
98
|
-
unless flushable_buckets.empty?
|
|
99
|
-
payload = serialize_buckets(flushable_buckets)
|
|
100
|
-
envelope.add_item(
|
|
101
|
-
{ type: "statsd", length: payload.bytesize },
|
|
102
|
-
payload
|
|
103
|
-
)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
unless code_locations.empty?
|
|
107
|
-
code_locations.each do |timestamp, locations|
|
|
108
|
-
payload = serialize_locations(timestamp, locations)
|
|
109
|
-
envelope.add_item(
|
|
110
|
-
{ type: "metric_meta", content_type: "application/json" },
|
|
111
|
-
payload
|
|
112
|
-
)
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
@client.capture_envelope(envelope)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
alias_method :run, :flush
|
|
120
|
-
|
|
121
|
-
private
|
|
122
|
-
|
|
123
|
-
# important to sort for key consistency
|
|
124
|
-
def serialize_tags(tags)
|
|
125
|
-
tags.flat_map do |k, v|
|
|
126
|
-
if v.is_a?(Array)
|
|
127
|
-
v.map { |x| [k.to_s, x.to_s] }
|
|
128
|
-
else
|
|
129
|
-
[[k.to_s, v.to_s]]
|
|
130
|
-
end
|
|
131
|
-
end.sort
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def get_flushable_buckets!(force)
|
|
135
|
-
@mutex.synchronize do
|
|
136
|
-
flushable_buckets = {}
|
|
137
|
-
|
|
138
|
-
if force
|
|
139
|
-
flushable_buckets = @buckets
|
|
140
|
-
@buckets = {}
|
|
141
|
-
else
|
|
142
|
-
cutoff = Sentry.utc_now.to_i - ROLLUP_IN_SECONDS - @flush_shift
|
|
143
|
-
flushable_buckets = @buckets.select { |k, _| k <= cutoff }
|
|
144
|
-
@buckets.reject! { |k, _| k <= cutoff }
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
flushable_buckets
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def get_code_locations!
|
|
152
|
-
@mutex.synchronize do
|
|
153
|
-
code_locations = @code_locations
|
|
154
|
-
@code_locations = {}
|
|
155
|
-
code_locations
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# serialize buckets to statsd format
|
|
160
|
-
def serialize_buckets(buckets)
|
|
161
|
-
buckets.map do |timestamp, timestamp_buckets|
|
|
162
|
-
timestamp_buckets.map do |metric_key, metric|
|
|
163
|
-
type, key, unit, tags = metric_key
|
|
164
|
-
values = metric.serialize.join(":")
|
|
165
|
-
sanitized_tags = tags.map { |k, v| "#{sanitize_tag_key(k)}:#{sanitize_tag_value(v)}" }.join(",")
|
|
166
|
-
|
|
167
|
-
"#{sanitize_key(key)}@#{sanitize_unit(unit)}:#{values}|#{type}|\##{sanitized_tags}|T#{timestamp}"
|
|
168
|
-
end
|
|
169
|
-
end.flatten.join("\n")
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def serialize_locations(timestamp, locations)
|
|
173
|
-
mapping = locations.map do |meta_key, location|
|
|
174
|
-
type, key, unit = meta_key
|
|
175
|
-
mri = "#{type}:#{sanitize_key(key)}@#{sanitize_unit(unit)}"
|
|
176
|
-
|
|
177
|
-
# note this needs to be an array but it really doesn't serve a purpose right now
|
|
178
|
-
[mri, [location.merge(type: "location")]]
|
|
179
|
-
end.to_h
|
|
180
|
-
|
|
181
|
-
{ timestamp: timestamp, mapping: mapping }
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def sanitize_key(key)
|
|
185
|
-
key.gsub(KEY_SANITIZATION_REGEX, "_")
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def sanitize_unit(unit)
|
|
189
|
-
unit.gsub(UNIT_SANITIZATION_REGEX, "")
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
def sanitize_tag_key(key)
|
|
193
|
-
key.gsub(TAG_KEY_SANITIZATION_REGEX, "")
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def sanitize_tag_value(value)
|
|
197
|
-
value.chars.map { |c| TAG_VALUE_SANITIZATION_MAP[c] || c }.join
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def get_transaction_name
|
|
201
|
-
scope = Sentry.get_current_scope
|
|
202
|
-
return nil unless scope && scope.transaction_name
|
|
203
|
-
return nil if scope.transaction_source_low_quality?
|
|
204
|
-
|
|
205
|
-
scope.transaction_name
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
def get_updated_tags(tags)
|
|
209
|
-
updated_tags = @default_tags.merge(tags)
|
|
210
|
-
|
|
211
|
-
transaction_name = get_transaction_name
|
|
212
|
-
updated_tags["transaction"] = transaction_name if transaction_name
|
|
213
|
-
|
|
214
|
-
updated_tags
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def process_span_aggregator(key, value)
|
|
218
|
-
scope = Sentry.get_current_scope
|
|
219
|
-
return nil unless scope && scope.span
|
|
220
|
-
return nil if scope.transaction_source_low_quality?
|
|
221
|
-
|
|
222
|
-
scope.span.metrics_local_aggregator.add(key, value)
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def process_bucket(timestamp, key, type, value)
|
|
226
|
-
@buckets[timestamp] ||= {}
|
|
227
|
-
|
|
228
|
-
if (metric = @buckets[timestamp][key])
|
|
229
|
-
old_weight = metric.weight
|
|
230
|
-
metric.add(value)
|
|
231
|
-
metric.weight - old_weight
|
|
232
|
-
else
|
|
233
|
-
metric = METRIC_TYPES[type].new(value)
|
|
234
|
-
@buckets[timestamp][key] = metric
|
|
235
|
-
metric.weight
|
|
236
|
-
end
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
def record_code_location(type, key, unit, timestamp, stacklevel: nil)
|
|
240
|
-
meta_key = [type, key, unit]
|
|
241
|
-
start_of_day = Time.utc(timestamp.year, timestamp.month, timestamp.day).to_i
|
|
242
|
-
|
|
243
|
-
@code_locations[start_of_day] ||= {}
|
|
244
|
-
@code_locations[start_of_day][meta_key] ||= @stacktrace_builder.metrics_code_location(caller[stacklevel || DEFAULT_STACKLEVEL])
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
end
|
|
248
|
-
end
|
|
@@ -1,47 +0,0 @@
|
|
|
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
|
|
@@ -1,25 +0,0 @@
|
|
|
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
|
|
@@ -1,25 +0,0 @@
|
|
|
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
|