sentry-ruby 5.28.1 → 6.5.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 +26 -2
- data/README.md +3 -3
- data/lib/sentry/background_worker.rb +1 -4
- data/lib/sentry/backtrace/line.rb +99 -0
- data/lib/sentry/backtrace.rb +44 -76
- data/lib/sentry/baggage.rb +2 -2
- 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 +57 -135
- data/lib/sentry/configuration.rb +155 -75
- 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/dsn.rb +33 -1
- data/lib/sentry/envelope/item.rb +3 -3
- data/lib/sentry/error_event.rb +3 -3
- data/lib/sentry/event.rb +4 -10
- data/lib/sentry/exceptions.rb +3 -0
- data/lib/sentry/hub.rb +26 -4
- 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 +4 -4
- 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 +24 -142
- data/lib/sentry/log_event_buffer.rb +13 -60
- data/lib/sentry/metric_event.rb +49 -0
- data/lib/sentry/metric_event_buffer.rb +28 -0
- data/lib/sentry/metrics.rb +47 -54
- data/lib/sentry/profiler.rb +4 -5
- data/lib/sentry/propagation_context.rb +48 -8
- data/lib/sentry/rack/capture_exceptions.rb +90 -2
- data/lib/sentry/release_detector.rb +1 -1
- data/lib/sentry/rspec.rb +1 -1
- data/lib/sentry/scope.rb +51 -18
- data/lib/sentry/sequel.rb +35 -0
- data/lib/sentry/span.rb +5 -17
- data/lib/sentry/std_lib_logger.rb +4 -0
- data/lib/sentry/telemetry_event_buffer.rb +130 -0
- data/lib/sentry/test_helper.rb +8 -0
- data/lib/sentry/transaction.rb +53 -103
- data/lib/sentry/transaction_event.rb +4 -9
- data/lib/sentry/transport/http_transport.rb +7 -11
- data/lib/sentry/transport.rb +9 -7
- data/lib/sentry/utils/encoding_helper.rb +6 -0
- data/lib/sentry/utils/logging_helper.rb +25 -9
- data/lib/sentry/utils/telemetry_attributes.rb +30 -0
- data/lib/sentry/vernier/profiler.rb +4 -3
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +53 -30
- data/sentry-ruby-core.gemspec +1 -1
- data/sentry-ruby.gemspec +2 -1
- metadata +27 -16
- data/lib/sentry/metrics/aggregator.rb +0 -248
- data/lib/sentry/metrics/configuration.rb +0 -57
- 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/profiler.rb
CHANGED
|
@@ -10,8 +10,6 @@ module Sentry
|
|
|
10
10
|
|
|
11
11
|
VERSION = "1"
|
|
12
12
|
PLATFORM = "ruby"
|
|
13
|
-
# 101 Hz in microseconds
|
|
14
|
-
DEFAULT_INTERVAL = 1e6 / 101
|
|
15
13
|
MICRO_TO_NANO_SECONDS = 1e3
|
|
16
14
|
MIN_SAMPLES_REQUIRED = 2
|
|
17
15
|
|
|
@@ -24,6 +22,7 @@ module Sentry
|
|
|
24
22
|
|
|
25
23
|
@profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
|
|
26
24
|
@profiles_sample_rate = configuration.profiles_sample_rate
|
|
25
|
+
@profiles_sample_interval = configuration.profiles_sample_interval
|
|
27
26
|
@project_root = configuration.project_root
|
|
28
27
|
@app_dirs_pattern = configuration.app_dirs_pattern
|
|
29
28
|
@in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
|
|
@@ -32,7 +31,7 @@ module Sentry
|
|
|
32
31
|
def start
|
|
33
32
|
return unless @sampled
|
|
34
33
|
|
|
35
|
-
@started = StackProf.start(interval:
|
|
34
|
+
@started = StackProf.start(interval: @profiles_sample_interval,
|
|
36
35
|
mode: :wall,
|
|
37
36
|
raw: true,
|
|
38
37
|
aggregate: false)
|
|
@@ -81,9 +80,9 @@ module Sentry
|
|
|
81
80
|
log("Discarding profile due to sampling decision") unless @sampled
|
|
82
81
|
end
|
|
83
82
|
|
|
84
|
-
def
|
|
83
|
+
def to_h
|
|
85
84
|
unless @sampled
|
|
86
|
-
record_lost_event(:sample_rate)
|
|
85
|
+
record_lost_event(:sample_rate) if @profiling_enabled
|
|
87
86
|
return {}
|
|
88
87
|
end
|
|
89
88
|
|
|
@@ -53,6 +53,44 @@ module Sentry
|
|
|
53
53
|
[trace_id, parent_span_id, parent_sampled]
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
# Determines whether we should continue an incoming trace based on org_id matching
|
|
57
|
+
# and the strict_trace_continuation configuration option.
|
|
58
|
+
#
|
|
59
|
+
# @param incoming_baggage [Baggage] the baggage from the incoming request
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def self.should_continue_trace?(incoming_baggage)
|
|
62
|
+
return true unless Sentry.initialized?
|
|
63
|
+
|
|
64
|
+
configuration = Sentry.configuration
|
|
65
|
+
sdk_org_id = configuration.effective_org_id
|
|
66
|
+
baggage_org_id = incoming_baggage.items["org_id"]
|
|
67
|
+
|
|
68
|
+
# Mismatched org IDs always start a new trace regardless of strict mode
|
|
69
|
+
if sdk_org_id && baggage_org_id && sdk_org_id != baggage_org_id
|
|
70
|
+
Sentry.sdk_logger.debug(LOGGER_PROGNAME) do
|
|
71
|
+
"Starting a new trace because org IDs don't match (incoming baggage org_id: #{baggage_org_id}, SDK org_id: #{sdk_org_id})"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
return false
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
return true unless configuration.strict_trace_continuation
|
|
78
|
+
|
|
79
|
+
# In strict mode, both must be present and match (unless both are missing)
|
|
80
|
+
if sdk_org_id.nil? && baggage_org_id.nil?
|
|
81
|
+
true
|
|
82
|
+
elsif sdk_org_id.nil? || baggage_org_id.nil?
|
|
83
|
+
Sentry.sdk_logger.debug(LOGGER_PROGNAME) do
|
|
84
|
+
"Starting a new trace because strict trace continuation is enabled and one org ID is missing " \
|
|
85
|
+
"(incoming baggage org_id: #{baggage_org_id.inspect}, SDK org_id: #{sdk_org_id.inspect})"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
false
|
|
89
|
+
else
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
56
94
|
def self.extract_sample_rand_from_baggage(baggage, trace_id = nil)
|
|
57
95
|
return unless baggage&.items
|
|
58
96
|
|
|
@@ -96,9 +134,7 @@ module Sentry
|
|
|
96
134
|
sentry_trace_data = self.class.extract_sentry_trace(sentry_trace_header)
|
|
97
135
|
|
|
98
136
|
if sentry_trace_data
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@baggage =
|
|
137
|
+
incoming_baggage =
|
|
102
138
|
if baggage_header && !baggage_header.empty?
|
|
103
139
|
Baggage.from_incoming_header(baggage_header)
|
|
104
140
|
else
|
|
@@ -108,10 +144,13 @@ module Sentry
|
|
|
108
144
|
Baggage.new({})
|
|
109
145
|
end
|
|
110
146
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
147
|
+
if self.class.should_continue_trace?(incoming_baggage)
|
|
148
|
+
@trace_id, @parent_span_id, @parent_sampled = sentry_trace_data
|
|
149
|
+
@baggage = incoming_baggage
|
|
150
|
+
@sample_rand = self.class.extract_sample_rand_from_baggage(@baggage, @trace_id)
|
|
151
|
+
@baggage.freeze!
|
|
152
|
+
@incoming_trace = true
|
|
153
|
+
end
|
|
115
154
|
end
|
|
116
155
|
end
|
|
117
156
|
end
|
|
@@ -162,7 +201,8 @@ module Sentry
|
|
|
162
201
|
"sample_rand" => Utils::SampleRand.format(@sample_rand),
|
|
163
202
|
"environment" => configuration.environment,
|
|
164
203
|
"release" => configuration.release,
|
|
165
|
-
"public_key" => configuration.dsn&.public_key
|
|
204
|
+
"public_key" => configuration.dsn&.public_key,
|
|
205
|
+
"org_id" => configuration.effective_org_id
|
|
166
206
|
}
|
|
167
207
|
|
|
168
208
|
items.compact!
|
|
@@ -33,7 +33,7 @@ module Sentry
|
|
|
33
33
|
raise # Don't capture Sentry errors
|
|
34
34
|
rescue Exception => e
|
|
35
35
|
capture_exception(e, env)
|
|
36
|
-
finish_transaction(transaction,
|
|
36
|
+
finish_transaction(transaction, status_code_for_exception(e))
|
|
37
37
|
raise
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -72,7 +72,16 @@ module Sentry
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
transaction = Sentry.continue_trace(env, **options)
|
|
75
|
-
Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
|
|
75
|
+
transaction = Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
|
|
76
|
+
attach_queue_time(transaction, env)
|
|
77
|
+
transaction
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def attach_queue_time(transaction, env)
|
|
81
|
+
return unless transaction
|
|
82
|
+
return unless (queue_time = extract_queue_time(env))
|
|
83
|
+
|
|
84
|
+
transaction.set_data(Span::DataConventions::HTTP_QUEUE_TIME_MS, queue_time)
|
|
76
85
|
end
|
|
77
86
|
|
|
78
87
|
|
|
@@ -86,6 +95,85 @@ module Sentry
|
|
|
86
95
|
def mechanism
|
|
87
96
|
Sentry::Mechanism.new(type: MECHANISM_TYPE, handled: false)
|
|
88
97
|
end
|
|
98
|
+
|
|
99
|
+
# Extracts queue time from the request environment.
|
|
100
|
+
# Calculates the time (in milliseconds) the request spent waiting in the
|
|
101
|
+
# web server queue before processing began.
|
|
102
|
+
#
|
|
103
|
+
# Subtracts puma.request_body_wait to account for time spent waiting for
|
|
104
|
+
# slow clients to send the request body, isolating actual queue time.
|
|
105
|
+
# See: https://github.com/puma/puma/blob/master/docs/architecture.md
|
|
106
|
+
#
|
|
107
|
+
# @param env [Hash] Rack env
|
|
108
|
+
# @return [Float, nil] queue time in milliseconds or nil
|
|
109
|
+
def extract_queue_time(env)
|
|
110
|
+
return unless Sentry.configuration&.capture_queue_time
|
|
111
|
+
|
|
112
|
+
header_value = env["HTTP_X_REQUEST_START"]
|
|
113
|
+
return unless header_value
|
|
114
|
+
|
|
115
|
+
request_start = parse_request_start_header(header_value)
|
|
116
|
+
return unless request_start
|
|
117
|
+
|
|
118
|
+
total_time_ms = ((Time.now.to_f - request_start) * 1000).round(2)
|
|
119
|
+
|
|
120
|
+
# reject negative (clock skew between proxy & app server)
|
|
121
|
+
return unless total_time_ms >= 0
|
|
122
|
+
|
|
123
|
+
puma_wait_ms = env["puma.request_body_wait"]
|
|
124
|
+
puma_wait_ms = puma_wait_ms.to_f if puma_wait_ms.is_a?(String)
|
|
125
|
+
|
|
126
|
+
if puma_wait_ms && puma_wait_ms > 0
|
|
127
|
+
queue_time_ms = total_time_ms - puma_wait_ms
|
|
128
|
+
queue_time_ms >= 0 ? queue_time_ms : 0.0 # more sanity check
|
|
129
|
+
else
|
|
130
|
+
total_time_ms
|
|
131
|
+
end
|
|
132
|
+
rescue StandardError
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Parses X-Request-Start header value to extract a timestamp.
|
|
136
|
+
# Supports multiple formats:
|
|
137
|
+
# - Nginx: "t=1234567890.123" (seconds with decimal)
|
|
138
|
+
# - Heroku, HAProxy 1.9+: "t=1234567890123456" (microseconds)
|
|
139
|
+
# - HAProxy < 1.9: "t=1234567890" (seconds)
|
|
140
|
+
# - Generic: "1234567890.123" (raw timestamp)
|
|
141
|
+
#
|
|
142
|
+
# @param header_value [String] The X-Request-Start header value
|
|
143
|
+
# @return [Float, nil] Timestamp in seconds since epoch or nil
|
|
144
|
+
def parse_request_start_header(header_value)
|
|
145
|
+
return unless header_value
|
|
146
|
+
|
|
147
|
+
# Take the first value if comma-separated (multiple headers collapsed by a proxy)
|
|
148
|
+
# and strip surrounding whitespace from each token
|
|
149
|
+
raw = header_value.split(",").first.to_s.strip
|
|
150
|
+
|
|
151
|
+
timestamp = if raw.start_with?("t=")
|
|
152
|
+
value = raw[2..-1].strip
|
|
153
|
+
return nil unless value.match?(/\A\d+(?:\.\d+)?\z/)
|
|
154
|
+
value.to_f
|
|
155
|
+
elsif raw.match?(/\A\d+(?:\.\d+)?\z/)
|
|
156
|
+
raw.to_f
|
|
157
|
+
else
|
|
158
|
+
return
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# normalize: timestamps can be in seconds, milliseconds or microseconds
|
|
162
|
+
# any timestamp > 10 trillion = microseconds
|
|
163
|
+
if timestamp > 10_000_000_000_000
|
|
164
|
+
timestamp / 1_000_000.0
|
|
165
|
+
# timestamp > 10 billion & < 10 trillion = milliseconds
|
|
166
|
+
elsif timestamp > 10_000_000_000
|
|
167
|
+
timestamp / 1_000.0
|
|
168
|
+
else
|
|
169
|
+
timestamp # assume seconds
|
|
170
|
+
end
|
|
171
|
+
rescue StandardError
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def status_code_for_exception(exception)
|
|
175
|
+
500
|
|
176
|
+
end
|
|
89
177
|
end
|
|
90
178
|
end
|
|
91
179
|
end
|
data/lib/sentry/rspec.rb
CHANGED
|
@@ -70,7 +70,7 @@ RSpec::Matchers.define :include_sentry_event do |event_message = "", **opts|
|
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def dump_events(sentry_events)
|
|
73
|
-
sentry_events.map(
|
|
73
|
+
sentry_events.map(&:to_h).map do |hash|
|
|
74
74
|
hash.select { |k, _| [:message, :contexts, :tags, :exception].include?(k) }
|
|
75
75
|
end.map do |hash|
|
|
76
76
|
JSON.pretty_generate(hash)
|
data/lib/sentry/scope.rb
CHANGED
|
@@ -46,7 +46,7 @@ module Sentry
|
|
|
46
46
|
# @param hint [Hash] the hint data that'll be passed to event processors.
|
|
47
47
|
# @return [Event]
|
|
48
48
|
def apply_to_event(event, hint = nil)
|
|
49
|
-
unless event.is_a?(CheckInEvent)
|
|
49
|
+
unless event.is_a?(CheckInEvent)
|
|
50
50
|
event.tags = tags.merge(event.tags)
|
|
51
51
|
event.user = user.merge(event.user)
|
|
52
52
|
event.extra = extra.merge(event.extra)
|
|
@@ -60,23 +60,10 @@ module Sentry
|
|
|
60
60
|
event.attachments = attachments
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if span
|
|
68
|
-
event.contexts[:trace] ||= span.get_trace_context
|
|
69
|
-
|
|
70
|
-
if event.respond_to?(:dynamic_sampling_context)
|
|
71
|
-
event.dynamic_sampling_context ||= span.get_dynamic_sampling_context
|
|
72
|
-
end
|
|
73
|
-
else
|
|
74
|
-
event.contexts[:trace] ||= propagation_context.get_trace_context
|
|
75
|
-
|
|
76
|
-
if event.respond_to?(:dynamic_sampling_context)
|
|
77
|
-
event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
|
|
78
|
-
end
|
|
79
|
-
end
|
|
63
|
+
trace_context = get_trace_context
|
|
64
|
+
dynamic_sampling_context = trace_context.delete(:dynamic_sampling_context)
|
|
65
|
+
event.contexts[:trace] ||= trace_context
|
|
66
|
+
event.dynamic_sampling_context ||= dynamic_sampling_context
|
|
80
67
|
|
|
81
68
|
all_event_processors = self.class.global_event_processors + @event_processors
|
|
82
69
|
|
|
@@ -89,6 +76,37 @@ module Sentry
|
|
|
89
76
|
event
|
|
90
77
|
end
|
|
91
78
|
|
|
79
|
+
# A leaner version of apply_to_event that applies to
|
|
80
|
+
# lightweight payloads like Logs and Metrics.
|
|
81
|
+
#
|
|
82
|
+
# Adds trace_id, span_id, user from the scope and default attributes from configuration.
|
|
83
|
+
#
|
|
84
|
+
# @param telemetry [MetricEvent, LogEvent] the telemetry event to apply scope context to
|
|
85
|
+
# @return [MetricEvent, LogEvent] the telemetry event with scope context applied
|
|
86
|
+
def apply_to_telemetry(telemetry)
|
|
87
|
+
# TODO-neel when new scope set_attribute api is added: add them here
|
|
88
|
+
trace_context = get_trace_context
|
|
89
|
+
telemetry.trace_id = trace_context[:trace_id]
|
|
90
|
+
telemetry.span_id = trace_context[:span_id]
|
|
91
|
+
|
|
92
|
+
configuration = Sentry.configuration
|
|
93
|
+
return telemetry unless configuration
|
|
94
|
+
|
|
95
|
+
telemetry.attributes["sentry.sdk.name"] ||= Sentry.sdk_meta["name"]
|
|
96
|
+
telemetry.attributes["sentry.sdk.version"] ||= Sentry.sdk_meta["version"]
|
|
97
|
+
telemetry.attributes["sentry.environment"] ||= configuration.environment if configuration.environment
|
|
98
|
+
telemetry.attributes["sentry.release"] ||= configuration.release if configuration.release
|
|
99
|
+
telemetry.attributes["server.address"] ||= configuration.server_name if configuration.server_name
|
|
100
|
+
|
|
101
|
+
unless user.empty?
|
|
102
|
+
telemetry.attributes["user.id"] ||= user[:id] if user[:id]
|
|
103
|
+
telemetry.attributes["user.name"] ||= user[:username] if user[:username]
|
|
104
|
+
telemetry.attributes["user.email"] ||= user[:email] if user[:email]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
telemetry
|
|
108
|
+
end
|
|
109
|
+
|
|
92
110
|
# Adds the breadcrumb to the scope's breadcrumbs buffer.
|
|
93
111
|
# @param breadcrumb [Breadcrumb]
|
|
94
112
|
# @return [void]
|
|
@@ -117,6 +135,7 @@ module Sentry
|
|
|
117
135
|
copy.session = session.deep_dup
|
|
118
136
|
copy.propagation_context = propagation_context.deep_dup
|
|
119
137
|
copy.attachments = attachments.dup
|
|
138
|
+
copy.event_processors = event_processors.dup
|
|
120
139
|
copy
|
|
121
140
|
end
|
|
122
141
|
|
|
@@ -278,6 +297,20 @@ module Sentry
|
|
|
278
297
|
span
|
|
279
298
|
end
|
|
280
299
|
|
|
300
|
+
# Returns the trace context for this scope.
|
|
301
|
+
# Prioritizes external propagation context (from OTel) over local propagation context.
|
|
302
|
+
# @return [Hash]
|
|
303
|
+
def get_trace_context
|
|
304
|
+
if span
|
|
305
|
+
span.get_trace_context.merge(dynamic_sampling_context: span.get_dynamic_sampling_context)
|
|
306
|
+
elsif (external_context = Sentry.get_external_propagation_context)
|
|
307
|
+
trace_id, span_id = external_context
|
|
308
|
+
{ trace_id: trace_id, span_id: span_id }
|
|
309
|
+
else
|
|
310
|
+
propagation_context.get_trace_context.merge(dynamic_sampling_context: propagation_context.get_dynamic_sampling_context)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
281
314
|
# Sets the scope's fingerprint attribute.
|
|
282
315
|
# @param fingerprint [Array]
|
|
283
316
|
# @return [Array]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sentry
|
|
4
|
+
module Sequel
|
|
5
|
+
OP_NAME = "db.sql.sequel"
|
|
6
|
+
SPAN_ORIGIN = "auto.db.sequel"
|
|
7
|
+
|
|
8
|
+
# Sequel Database extension module that instruments queries
|
|
9
|
+
module DatabaseExtension
|
|
10
|
+
def log_connection_yield(sql, conn, args = nil)
|
|
11
|
+
return super unless Sentry.initialized?
|
|
12
|
+
|
|
13
|
+
Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f, origin: SPAN_ORIGIN) do |span|
|
|
14
|
+
result = super
|
|
15
|
+
|
|
16
|
+
if span
|
|
17
|
+
span.set_description(sql)
|
|
18
|
+
span.set_data(Span::DataConventions::DB_SYSTEM, database_type.to_s)
|
|
19
|
+
span.set_data(Span::DataConventions::DB_NAME, opts[:database]) if opts[:database]
|
|
20
|
+
span.set_data(Span::DataConventions::SERVER_ADDRESS, opts[:host]) if opts[:host]
|
|
21
|
+
span.set_data(Span::DataConventions::SERVER_PORT, opts[:port]) if opts[:port]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
result
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
::Sequel::Database.register_extension(:sentry, Sentry::Sequel::DatabaseExtension)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Sentry.register_patch(:sequel) do
|
|
34
|
+
::Sequel::Database.extension(:sentry) if defined?(::Sequel::Database)
|
|
35
|
+
end
|
data/lib/sentry/span.rb
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "securerandom"
|
|
4
|
-
require "sentry/metrics/local_aggregator"
|
|
5
4
|
require "sentry/utils/uuid"
|
|
6
5
|
|
|
7
6
|
module Sentry
|
|
@@ -50,6 +49,9 @@ module Sentry
|
|
|
50
49
|
MESSAGING_DESTINATION_NAME = "messaging.destination.name"
|
|
51
50
|
MESSAGING_MESSAGE_RECEIVE_LATENCY = "messaging.message.receive.latency"
|
|
52
51
|
MESSAGING_MESSAGE_RETRY_COUNT = "messaging.message.retry.count"
|
|
52
|
+
|
|
53
|
+
# Time in ms the request spent in the server queue before processing began.
|
|
54
|
+
HTTP_QUEUE_TIME_MS = "http.server.request.time_in_queue"
|
|
53
55
|
end
|
|
54
56
|
|
|
55
57
|
STATUS_MAP = {
|
|
@@ -173,8 +175,8 @@ module Sentry
|
|
|
173
175
|
end
|
|
174
176
|
|
|
175
177
|
# @return [Hash]
|
|
176
|
-
def
|
|
177
|
-
|
|
178
|
+
def to_h
|
|
179
|
+
{
|
|
178
180
|
trace_id: @trace_id,
|
|
179
181
|
span_id: @span_id,
|
|
180
182
|
parent_span_id: @parent_span_id,
|
|
@@ -187,11 +189,6 @@ module Sentry
|
|
|
187
189
|
data: @data,
|
|
188
190
|
origin: @origin
|
|
189
191
|
}
|
|
190
|
-
|
|
191
|
-
summary = metrics_summary
|
|
192
|
-
hash[:_metrics_summary] = summary if summary
|
|
193
|
-
|
|
194
|
-
hash
|
|
195
192
|
end
|
|
196
193
|
|
|
197
194
|
# Returns the span's context that can be used to embed in an Event.
|
|
@@ -307,14 +304,5 @@ module Sentry
|
|
|
307
304
|
def set_origin(origin)
|
|
308
305
|
@origin = origin
|
|
309
306
|
end
|
|
310
|
-
|
|
311
|
-
# Collects gauge metrics on the span for metric summaries.
|
|
312
|
-
def metrics_local_aggregator
|
|
313
|
-
@metrics_local_aggregator ||= Sentry::Metrics::LocalAggregator.new
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
def metrics_summary
|
|
317
|
-
@metrics_local_aggregator&.to_hash
|
|
318
|
-
end
|
|
319
307
|
end
|
|
320
308
|
end
|
|
@@ -37,6 +37,10 @@ module Sentry
|
|
|
37
37
|
message = message.to_s.strip
|
|
38
38
|
|
|
39
39
|
if !message.nil? && message != Sentry::Logger::PROGNAME && method = SEVERITY_MAP[severity]
|
|
40
|
+
if (filter = Sentry.configuration.std_lib_logger_filter) && !filter.call(self, message, method)
|
|
41
|
+
return result
|
|
42
|
+
end
|
|
43
|
+
|
|
40
44
|
Sentry.logger.send(method, message, origin: ORIGIN)
|
|
41
45
|
end
|
|
42
46
|
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "sentry/threaded_periodic_worker"
|
|
4
|
+
require "sentry/envelope"
|
|
5
|
+
|
|
6
|
+
module Sentry
|
|
7
|
+
# TelemetryEventBuffer is a base class for buffering telemetry events (logs, metrics, etc.)
|
|
8
|
+
# and sending them to Sentry in a single envelope.
|
|
9
|
+
#
|
|
10
|
+
# This is used internally by the `Sentry::Client`.
|
|
11
|
+
#
|
|
12
|
+
# @!visibility private
|
|
13
|
+
class TelemetryEventBuffer < ThreadedPeriodicWorker
|
|
14
|
+
FLUSH_INTERVAL = 5 # seconds
|
|
15
|
+
|
|
16
|
+
# @!visibility private
|
|
17
|
+
attr_reader :pending_items, :envelope_type, :data_category, :thread
|
|
18
|
+
|
|
19
|
+
def initialize(configuration, client, event_class:, max_items:, max_items_before_drop:, envelope_type:, envelope_content_type:, before_send:)
|
|
20
|
+
super(configuration.sdk_logger, FLUSH_INTERVAL)
|
|
21
|
+
|
|
22
|
+
@client = client
|
|
23
|
+
@dsn = configuration.dsn
|
|
24
|
+
@debug = configuration.debug
|
|
25
|
+
@event_class = event_class
|
|
26
|
+
@max_items = max_items
|
|
27
|
+
@max_items_before_drop = max_items_before_drop
|
|
28
|
+
@envelope_type = envelope_type
|
|
29
|
+
@data_category = Sentry::Envelope::Item.data_category(@envelope_type)
|
|
30
|
+
@envelope_content_type = envelope_content_type
|
|
31
|
+
@before_send = before_send
|
|
32
|
+
|
|
33
|
+
@pending_items = []
|
|
34
|
+
@mutex = Mutex.new
|
|
35
|
+
|
|
36
|
+
log_debug("[#{self.class}] Initialized buffer with max_items=#{@max_items}, flush_interval=#{FLUSH_INTERVAL}s")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def flush
|
|
40
|
+
@mutex.synchronize do
|
|
41
|
+
return if empty?
|
|
42
|
+
|
|
43
|
+
log_debug("[#{self.class}] flushing #{size} #{@event_class}")
|
|
44
|
+
|
|
45
|
+
send_items
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
alias_method :run, :flush
|
|
51
|
+
|
|
52
|
+
def add_item(item)
|
|
53
|
+
@mutex.synchronize do
|
|
54
|
+
return unless ensure_thread
|
|
55
|
+
|
|
56
|
+
if size >= @max_items_before_drop
|
|
57
|
+
log_debug("[#{self.class}] exceeded max capacity, dropping event")
|
|
58
|
+
@client.transport.record_lost_event(:queue_overflow, @data_category)
|
|
59
|
+
else
|
|
60
|
+
@pending_items << item
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
send_items if size >= @max_items
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def empty?
|
|
70
|
+
@pending_items.empty?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def size
|
|
74
|
+
@pending_items.size
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def clear!
|
|
78
|
+
@pending_items.clear
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def send_items
|
|
84
|
+
envelope = Envelope.new(
|
|
85
|
+
event_id: Sentry::Utils.uuid,
|
|
86
|
+
sent_at: Sentry.utc_now.iso8601,
|
|
87
|
+
dsn: @dsn,
|
|
88
|
+
sdk: Sentry.sdk_meta
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
discarded_count = 0
|
|
92
|
+
envelope_items = []
|
|
93
|
+
|
|
94
|
+
if @before_send
|
|
95
|
+
@pending_items.each do |item|
|
|
96
|
+
processed_item = @before_send.call(item)
|
|
97
|
+
|
|
98
|
+
if processed_item
|
|
99
|
+
envelope_items << processed_item.to_h
|
|
100
|
+
else
|
|
101
|
+
discarded_count += 1
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
else
|
|
105
|
+
envelope_items = @pending_items.map(&:to_h)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
unless discarded_count.zero?
|
|
109
|
+
@client.transport.record_lost_event(:before_send, @data_category, num: discarded_count)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
return if envelope_items.empty?
|
|
113
|
+
|
|
114
|
+
envelope.add_item(
|
|
115
|
+
{
|
|
116
|
+
type: @envelope_type,
|
|
117
|
+
item_count: envelope_items.size,
|
|
118
|
+
content_type: @envelope_content_type
|
|
119
|
+
},
|
|
120
|
+
{ items: envelope_items }
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
@client.send_envelope(envelope)
|
|
124
|
+
rescue => e
|
|
125
|
+
log_error("[#{self.class}] Failed to send #{@event_class}", e, debug: @debug)
|
|
126
|
+
ensure
|
|
127
|
+
clear!
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
data/lib/sentry/test_helper.rb
CHANGED
|
@@ -27,6 +27,7 @@ module Sentry
|
|
|
27
27
|
# set transport to DummyTransport, so we can easily intercept the captured events
|
|
28
28
|
dummy_config.transport.transport_class = Sentry::DummyTransport
|
|
29
29
|
# make sure SDK allows sending under the current environment
|
|
30
|
+
dummy_config.enabled_environments ||= []
|
|
30
31
|
dummy_config.enabled_environments += [dummy_config.environment] unless dummy_config.enabled_environments.include?(dummy_config.environment)
|
|
31
32
|
# disble async event sending
|
|
32
33
|
dummy_config.background_worker_threads = 0
|
|
@@ -101,6 +102,13 @@ module Sentry
|
|
|
101
102
|
.flat_map { |item| item.payload[:items] }
|
|
102
103
|
end
|
|
103
104
|
|
|
105
|
+
def sentry_metrics
|
|
106
|
+
sentry_envelopes
|
|
107
|
+
.flat_map(&:items)
|
|
108
|
+
.select { |item| item.headers[:type] == "trace_metric" }
|
|
109
|
+
.flat_map { |item| item.payload[:items] }
|
|
110
|
+
end
|
|
111
|
+
|
|
104
112
|
# Returns the last captured event object.
|
|
105
113
|
# @return [Event, nil]
|
|
106
114
|
def last_sentry_event
|