sentry-ruby 5.18.2 → 5.28.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/.rspec +3 -1
- data/Gemfile +9 -3
- data/README.md +12 -7
- data/Rakefile +9 -11
- data/bin/console +1 -0
- data/lib/sentry/attachment.rb +40 -0
- data/lib/sentry/background_worker.rb +2 -3
- data/lib/sentry/backpressure_monitor.rb +1 -1
- data/lib/sentry/backtrace.rb +7 -8
- data/lib/sentry/baggage.rb +7 -7
- data/lib/sentry/breadcrumb/sentry_logger.rb +6 -6
- data/lib/sentry/breadcrumb.rb +5 -4
- data/lib/sentry/check_in_event.rb +5 -4
- data/lib/sentry/client.rb +121 -19
- data/lib/sentry/configuration.rb +207 -38
- data/lib/sentry/core_ext/object/deep_dup.rb +1 -1
- data/lib/sentry/cron/monitor_check_ins.rb +6 -4
- data/lib/sentry/cron/monitor_config.rb +1 -1
- data/lib/sentry/debug_structured_logger.rb +94 -0
- data/lib/sentry/dsn.rb +35 -3
- data/lib/sentry/envelope/item.rb +88 -0
- data/lib/sentry/envelope.rb +2 -85
- data/lib/sentry/event.rb +13 -8
- data/lib/sentry/excon/middleware.rb +77 -0
- data/lib/sentry/excon.rb +10 -0
- data/lib/sentry/faraday.rb +77 -0
- data/lib/sentry/graphql.rb +1 -1
- data/lib/sentry/hub.rb +57 -4
- data/lib/sentry/interfaces/mechanism.rb +1 -1
- data/lib/sentry/interfaces/request.rb +6 -6
- data/lib/sentry/interfaces/single_exception.rb +3 -3
- data/lib/sentry/interfaces/stacktrace.rb +3 -1
- data/lib/sentry/interfaces/stacktrace_builder.rb +15 -2
- data/lib/sentry/linecache.rb +3 -3
- data/lib/sentry/log_event.rb +219 -0
- data/lib/sentry/log_event_buffer.rb +75 -0
- data/lib/sentry/logger.rb +1 -1
- data/lib/sentry/metrics/aggregator.rb +13 -13
- data/lib/sentry/metrics/configuration.rb +12 -2
- data/lib/sentry/metrics/set_metric.rb +2 -2
- data/lib/sentry/metrics/timing.rb +8 -0
- data/lib/sentry/metrics.rb +27 -15
- data/lib/sentry/net/http.rb +17 -38
- data/lib/sentry/profiler/helpers.rb +46 -0
- data/lib/sentry/profiler.rb +27 -57
- data/lib/sentry/propagation_context.rb +60 -23
- data/lib/sentry/rack/capture_exceptions.rb +2 -2
- data/lib/sentry/rack.rb +2 -2
- data/lib/sentry/rake.rb +2 -2
- data/lib/sentry/release_detector.rb +4 -4
- data/lib/sentry/rspec.rb +91 -0
- data/lib/sentry/scope.rb +27 -2
- data/lib/sentry/session_flusher.rb +10 -6
- data/lib/sentry/span.rb +17 -4
- data/lib/sentry/std_lib_logger.rb +55 -0
- data/lib/sentry/structured_logger.rb +138 -0
- data/lib/sentry/test_helper.rb +45 -1
- data/lib/sentry/threaded_periodic_worker.rb +3 -3
- data/lib/sentry/transaction.rb +41 -13
- data/lib/sentry/transaction_event.rb +5 -3
- 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 +21 -18
- data/lib/sentry/transport.rb +26 -11
- data/lib/sentry/utils/env_helper.rb +21 -0
- data/lib/sentry/utils/http_tracing.rb +74 -0
- data/lib/sentry/utils/logging_helper.rb +10 -3
- data/lib/sentry/utils/real_ip.rb +1 -1
- data/lib/sentry/utils/sample_rand.rb +97 -0
- data/lib/sentry/utils/uuid.rb +13 -0
- data/lib/sentry/vernier/output.rb +89 -0
- data/lib/sentry/vernier/profiler.rb +132 -0
- data/lib/sentry/version.rb +1 -1
- data/lib/sentry-ruby.rb +106 -12
- data/sentry-ruby-core.gemspec +3 -1
- data/sentry-ruby.gemspec +4 -2
- metadata +28 -12
data/lib/sentry/profiler.rb
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "securerandom"
|
|
4
|
+
require_relative "profiler/helpers"
|
|
5
|
+
require "sentry/utils/uuid"
|
|
4
6
|
|
|
5
7
|
module Sentry
|
|
6
8
|
class Profiler
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
include Profiler::Helpers
|
|
10
|
+
|
|
11
|
+
VERSION = "1"
|
|
12
|
+
PLATFORM = "ruby"
|
|
9
13
|
# 101 Hz in microseconds
|
|
10
14
|
DEFAULT_INTERVAL = 1e6 / 101
|
|
11
15
|
MICRO_TO_NANO_SECONDS = 1e3
|
|
@@ -14,14 +18,14 @@ module Sentry
|
|
|
14
18
|
attr_reader :sampled, :started, :event_id
|
|
15
19
|
|
|
16
20
|
def initialize(configuration)
|
|
17
|
-
@event_id =
|
|
21
|
+
@event_id = Utils.uuid
|
|
18
22
|
@started = false
|
|
19
23
|
@sampled = nil
|
|
20
24
|
|
|
21
25
|
@profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
|
|
22
26
|
@profiles_sample_rate = configuration.profiles_sample_rate
|
|
23
27
|
@project_root = configuration.project_root
|
|
24
|
-
@app_dirs_pattern = configuration.app_dirs_pattern
|
|
28
|
+
@app_dirs_pattern = configuration.app_dirs_pattern
|
|
25
29
|
@in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
|
|
26
30
|
end
|
|
27
31
|
|
|
@@ -33,7 +37,7 @@ module Sentry
|
|
|
33
37
|
raw: true,
|
|
34
38
|
aggregate: false)
|
|
35
39
|
|
|
36
|
-
@started ? log(
|
|
40
|
+
@started ? log("Started") : log("Not started since running elsewhere")
|
|
37
41
|
end
|
|
38
42
|
|
|
39
43
|
def stop
|
|
@@ -41,7 +45,11 @@ module Sentry
|
|
|
41
45
|
return unless @started
|
|
42
46
|
|
|
43
47
|
StackProf.stop
|
|
44
|
-
log(
|
|
48
|
+
log("Stopped")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def active_thread_id
|
|
52
|
+
"0"
|
|
45
53
|
end
|
|
46
54
|
|
|
47
55
|
# Sets initial sampling decision of the profile.
|
|
@@ -54,14 +62,14 @@ module Sentry
|
|
|
54
62
|
|
|
55
63
|
unless transaction_sampled
|
|
56
64
|
@sampled = false
|
|
57
|
-
log(
|
|
65
|
+
log("Discarding profile because transaction not sampled")
|
|
58
66
|
return
|
|
59
67
|
end
|
|
60
68
|
|
|
61
69
|
case @profiles_sample_rate
|
|
62
70
|
when 0.0
|
|
63
71
|
@sampled = false
|
|
64
|
-
log(
|
|
72
|
+
log("Discarding profile because sample_rate is 0")
|
|
65
73
|
return
|
|
66
74
|
when 1.0
|
|
67
75
|
@sampled = true
|
|
@@ -70,7 +78,7 @@ module Sentry
|
|
|
70
78
|
@sampled = Random.rand < @profiles_sample_rate
|
|
71
79
|
end
|
|
72
80
|
|
|
73
|
-
log(
|
|
81
|
+
log("Discarding profile due to sampling decision") unless @sampled
|
|
74
82
|
end
|
|
75
83
|
|
|
76
84
|
def to_hash
|
|
@@ -90,13 +98,12 @@ module Sentry
|
|
|
90
98
|
|
|
91
99
|
frame_map = {}
|
|
92
100
|
|
|
93
|
-
frames = results[:frames].
|
|
94
|
-
frame_id, frame_data = frame
|
|
95
|
-
|
|
101
|
+
frames = results[:frames].map.with_index do |(frame_id, frame_data), idx|
|
|
96
102
|
# need to map over stackprof frame ids to ours
|
|
97
103
|
frame_map[frame_id] = idx
|
|
98
104
|
|
|
99
105
|
file_path = frame_data[:file]
|
|
106
|
+
lineno = frame_data[:line]
|
|
100
107
|
in_app = in_app?(file_path)
|
|
101
108
|
filename = compute_filename(file_path, in_app)
|
|
102
109
|
function, mod = split_module(frame_data[:name])
|
|
@@ -109,7 +116,7 @@ module Sentry
|
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
frame_hash[:module] = mod if mod
|
|
112
|
-
frame_hash[:lineno] =
|
|
119
|
+
frame_hash[:lineno] = lineno if lineno && lineno >= 0
|
|
113
120
|
|
|
114
121
|
frame_hash
|
|
115
122
|
end
|
|
@@ -130,7 +137,7 @@ module Sentry
|
|
|
130
137
|
num_seen << results[:raw][idx + len]
|
|
131
138
|
idx += len + 1
|
|
132
139
|
|
|
133
|
-
log(
|
|
140
|
+
log("Unknown frame in stack") if stack.size != len
|
|
134
141
|
end
|
|
135
142
|
|
|
136
143
|
idx = 0
|
|
@@ -155,16 +162,16 @@ module Sentry
|
|
|
155
162
|
# Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
|
|
156
163
|
# we're profiling is idle/sleeping/waiting for IO etc.
|
|
157
164
|
# https://bugs.ruby-lang.org/issues/10602
|
|
158
|
-
thread_id:
|
|
165
|
+
thread_id: "0",
|
|
159
166
|
elapsed_since_start_ns: elapsed_since_start_ns.to_s
|
|
160
167
|
}
|
|
161
168
|
end
|
|
162
169
|
end
|
|
163
170
|
|
|
164
|
-
log(
|
|
171
|
+
log("Some samples thrown away") if samples.size != results[:samples]
|
|
165
172
|
|
|
166
173
|
if samples.size <= MIN_SAMPLES_REQUIRED
|
|
167
|
-
log(
|
|
174
|
+
log("Not enough samples, discarding profiler")
|
|
168
175
|
record_lost_event(:insufficient_data)
|
|
169
176
|
return {}
|
|
170
177
|
end
|
|
@@ -186,48 +193,11 @@ module Sentry
|
|
|
186
193
|
private
|
|
187
194
|
|
|
188
195
|
def log(message)
|
|
189
|
-
Sentry.
|
|
190
|
-
end
|
|
191
|
-
|
|
192
|
-
def in_app?(abs_path)
|
|
193
|
-
abs_path.match?(@in_app_pattern)
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
# copied from stacktrace.rb since I don't want to touch existing code
|
|
197
|
-
# TODO-neel-profiler try to fetch this from stackprof once we patch
|
|
198
|
-
# the native extension
|
|
199
|
-
def compute_filename(abs_path, in_app)
|
|
200
|
-
return nil if abs_path.nil?
|
|
201
|
-
|
|
202
|
-
under_project_root = @project_root && abs_path.start_with?(@project_root)
|
|
203
|
-
|
|
204
|
-
prefix =
|
|
205
|
-
if under_project_root && in_app
|
|
206
|
-
@project_root
|
|
207
|
-
else
|
|
208
|
-
longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
|
|
209
|
-
|
|
210
|
-
if under_project_root
|
|
211
|
-
longest_load_path || @project_root
|
|
212
|
-
else
|
|
213
|
-
longest_load_path
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def split_module(name)
|
|
221
|
-
# last module plus class/instance method
|
|
222
|
-
i = name.rindex('::')
|
|
223
|
-
function = i ? name[(i + 2)..-1] : name
|
|
224
|
-
mod = i ? name[0...i] : nil
|
|
225
|
-
|
|
226
|
-
[function, mod]
|
|
196
|
+
Sentry.sdk_logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
|
|
227
197
|
end
|
|
228
198
|
|
|
229
199
|
def record_lost_event(reason)
|
|
230
|
-
Sentry.get_current_client&.transport&.record_lost_event(reason,
|
|
200
|
+
Sentry.get_current_client&.transport&.record_lost_event(reason, "profile")
|
|
231
201
|
end
|
|
232
202
|
end
|
|
233
203
|
end
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
require "sentry/baggage"
|
|
5
|
+
require "sentry/utils/uuid"
|
|
6
|
+
require "sentry/utils/sample_rand"
|
|
5
7
|
|
|
6
8
|
module Sentry
|
|
7
9
|
class PropagationContext
|
|
8
10
|
SENTRY_TRACE_REGEXP = Regexp.new(
|
|
9
|
-
"
|
|
10
|
-
"([0-9a-f]{32})?" + # trace_id
|
|
11
|
+
"\\A([0-9a-f]{32})?" + # trace_id
|
|
11
12
|
"-?([0-9a-f]{16})?" + # span_id
|
|
12
|
-
"-?([01])
|
|
13
|
-
"[ \t]*$" # whitespace
|
|
13
|
+
"-?([01])?\\z" # sampled
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
# An uuid that can be used to identify a trace.
|
|
@@ -32,6 +32,53 @@ module Sentry
|
|
|
32
32
|
# Please use the #get_baggage method for interfacing outside this class.
|
|
33
33
|
# @return [Baggage, nil]
|
|
34
34
|
attr_reader :baggage
|
|
35
|
+
# The propagated random value used for sampling decisions.
|
|
36
|
+
# @return [Float, nil]
|
|
37
|
+
attr_reader :sample_rand
|
|
38
|
+
|
|
39
|
+
# Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
|
|
40
|
+
#
|
|
41
|
+
# @param sentry_trace [String] the sentry-trace header value from the previous transaction.
|
|
42
|
+
# @return [Array, nil]
|
|
43
|
+
def self.extract_sentry_trace(sentry_trace)
|
|
44
|
+
value = sentry_trace.to_s.strip
|
|
45
|
+
return if value.empty?
|
|
46
|
+
|
|
47
|
+
match = SENTRY_TRACE_REGEXP.match(value)
|
|
48
|
+
return if match.nil?
|
|
49
|
+
|
|
50
|
+
trace_id, parent_span_id, sampled_flag = match[1..3]
|
|
51
|
+
parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
|
|
52
|
+
|
|
53
|
+
[trace_id, parent_span_id, parent_sampled]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.extract_sample_rand_from_baggage(baggage, trace_id = nil)
|
|
57
|
+
return unless baggage&.items
|
|
58
|
+
|
|
59
|
+
sample_rand_str = baggage.items["sample_rand"]
|
|
60
|
+
return unless sample_rand_str
|
|
61
|
+
|
|
62
|
+
generator = Utils::SampleRand.new(trace_id: trace_id)
|
|
63
|
+
generator.generate_from_value(sample_rand_str)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.generate_sample_rand(baggage, trace_id, parent_sampled)
|
|
67
|
+
generator = Utils::SampleRand.new(trace_id: trace_id)
|
|
68
|
+
|
|
69
|
+
if baggage&.items && !parent_sampled.nil?
|
|
70
|
+
sample_rate_str = baggage.items["sample_rate"]
|
|
71
|
+
sample_rate = sample_rate_str&.to_f
|
|
72
|
+
|
|
73
|
+
if sample_rate && !parent_sampled.nil?
|
|
74
|
+
generator.generate_from_sampling_decision(parent_sampled, sample_rate)
|
|
75
|
+
else
|
|
76
|
+
generator.generate_from_trace_id
|
|
77
|
+
end
|
|
78
|
+
else
|
|
79
|
+
generator.generate_from_trace_id
|
|
80
|
+
end
|
|
81
|
+
end
|
|
35
82
|
|
|
36
83
|
def initialize(scope, env = nil)
|
|
37
84
|
@scope = scope
|
|
@@ -39,6 +86,7 @@ module Sentry
|
|
|
39
86
|
@parent_sampled = nil
|
|
40
87
|
@baggage = nil
|
|
41
88
|
@incoming_trace = false
|
|
89
|
+
@sample_rand = nil
|
|
42
90
|
|
|
43
91
|
if env
|
|
44
92
|
sentry_trace_header = env["HTTP_SENTRY_TRACE"] || env[SENTRY_TRACE_HEADER_NAME]
|
|
@@ -60,28 +108,17 @@ module Sentry
|
|
|
60
108
|
Baggage.new({})
|
|
61
109
|
end
|
|
62
110
|
|
|
111
|
+
@sample_rand = self.class.extract_sample_rand_from_baggage(@baggage, @trace_id)
|
|
112
|
+
|
|
63
113
|
@baggage.freeze!
|
|
64
114
|
@incoming_trace = true
|
|
65
115
|
end
|
|
66
116
|
end
|
|
67
117
|
end
|
|
68
118
|
|
|
69
|
-
@trace_id ||=
|
|
70
|
-
@span_id =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# Extract the trace_id, parent_span_id and parent_sampled values from a sentry-trace header.
|
|
74
|
-
#
|
|
75
|
-
# @param sentry_trace [String] the sentry-trace header value from the previous transaction.
|
|
76
|
-
# @return [Array, nil]
|
|
77
|
-
def self.extract_sentry_trace(sentry_trace)
|
|
78
|
-
match = SENTRY_TRACE_REGEXP.match(sentry_trace)
|
|
79
|
-
return nil if match.nil?
|
|
80
|
-
|
|
81
|
-
trace_id, parent_span_id, sampled_flag = match[1..3]
|
|
82
|
-
parent_sampled = sampled_flag.nil? ? nil : sampled_flag != "0"
|
|
83
|
-
|
|
84
|
-
[trace_id, parent_span_id, parent_sampled]
|
|
119
|
+
@trace_id ||= Utils.uuid
|
|
120
|
+
@span_id = Utils.uuid.slice(0, 16)
|
|
121
|
+
@sample_rand ||= self.class.generate_sample_rand(@baggage, @trace_id, @parent_sampled)
|
|
85
122
|
end
|
|
86
123
|
|
|
87
124
|
# Returns the trace context that can be used to embed in an Event.
|
|
@@ -108,7 +145,7 @@ module Sentry
|
|
|
108
145
|
end
|
|
109
146
|
|
|
110
147
|
# Returns the Dynamic Sampling Context from the baggage.
|
|
111
|
-
# @return [
|
|
148
|
+
# @return [Hash, nil]
|
|
112
149
|
def get_dynamic_sampling_context
|
|
113
150
|
get_baggage&.dynamic_sampling_context
|
|
114
151
|
end
|
|
@@ -122,10 +159,10 @@ module Sentry
|
|
|
122
159
|
|
|
123
160
|
items = {
|
|
124
161
|
"trace_id" => trace_id,
|
|
162
|
+
"sample_rand" => Utils::SampleRand.format(@sample_rand),
|
|
125
163
|
"environment" => configuration.environment,
|
|
126
164
|
"release" => configuration.release,
|
|
127
|
-
"public_key" => configuration.dsn&.public_key
|
|
128
|
-
"user_segment" => @scope.user && @scope.user["segment"]
|
|
165
|
+
"public_key" => configuration.dsn&.public_key
|
|
129
166
|
}
|
|
130
167
|
|
|
131
168
|
items.compact!
|
|
@@ -50,11 +50,11 @@ module Sentry
|
|
|
50
50
|
private
|
|
51
51
|
|
|
52
52
|
def collect_exception(env)
|
|
53
|
-
env[
|
|
53
|
+
env["rack.exception"] || env["sinatra.error"]
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def transaction_op
|
|
57
|
-
"http.server"
|
|
57
|
+
"http.server"
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def capture_exception(exception, env)
|
data/lib/sentry/rack.rb
CHANGED
data/lib/sentry/rake.rb
CHANGED
|
@@ -8,10 +8,10 @@ module Sentry
|
|
|
8
8
|
module Application
|
|
9
9
|
# @api private
|
|
10
10
|
def display_error_message(ex)
|
|
11
|
-
mechanism = Sentry::Mechanism.new(type:
|
|
11
|
+
mechanism = Sentry::Mechanism.new(type: "rake", handled: false)
|
|
12
12
|
|
|
13
13
|
Sentry.capture_exception(ex, hint: { mechanism: mechanism }) do |scope|
|
|
14
|
-
task_name = top_level_tasks.join(
|
|
14
|
+
task_name = top_level_tasks.join(" ")
|
|
15
15
|
scope.set_transaction_name(task_name, source: :task)
|
|
16
16
|
scope.set_tag("rake_task", task_name)
|
|
17
17
|
end if Sentry.initialized? && !Sentry.configuration.skip_rake_integration
|
|
@@ -13,12 +13,12 @@ module Sentry
|
|
|
13
13
|
|
|
14
14
|
def detect_release_from_heroku(running_on_heroku)
|
|
15
15
|
return unless running_on_heroku
|
|
16
|
-
ENV[
|
|
16
|
+
ENV["HEROKU_SLUG_COMMIT"]
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def detect_release_from_capistrano(project_root)
|
|
20
|
-
revision_file = File.join(project_root,
|
|
21
|
-
revision_log = File.join(project_root,
|
|
20
|
+
revision_file = File.join(project_root, "REVISION")
|
|
21
|
+
revision_log = File.join(project_root, "..", "revisions.log")
|
|
22
22
|
|
|
23
23
|
if File.exist?(revision_file)
|
|
24
24
|
File.read(revision_file).strip
|
|
@@ -32,7 +32,7 @@ module Sentry
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def detect_release_from_env
|
|
35
|
-
ENV[
|
|
35
|
+
ENV["SENTRY_RELEASE"]
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
end
|
data/lib/sentry/rspec.rb
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec::Matchers.define :include_sentry_event do |event_message = "", **opts|
|
|
4
|
+
match do |sentry_events|
|
|
5
|
+
@expected_exception = expected_exception(**opts)
|
|
6
|
+
@context = context(**opts)
|
|
7
|
+
@tags = tags(**opts)
|
|
8
|
+
|
|
9
|
+
@expected_event = expected_event(event_message)
|
|
10
|
+
@matched_event = find_matched_event(event_message, sentry_events)
|
|
11
|
+
|
|
12
|
+
return false unless @matched_event
|
|
13
|
+
|
|
14
|
+
[verify_context(), verify_tags()].all?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
chain :with_context do |context|
|
|
18
|
+
@context = context
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
chain :with_tags do |tags|
|
|
22
|
+
@tags = tags
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
failure_message do |sentry_events|
|
|
26
|
+
info = ["Failed to find event matching:\n"]
|
|
27
|
+
info << " message: #{@expected_event.message.inspect}"
|
|
28
|
+
info << " exception: #{@expected_exception.inspect}"
|
|
29
|
+
info << " context: #{@context.inspect}"
|
|
30
|
+
info << " tags: #{@tags.inspect}"
|
|
31
|
+
info << "\n"
|
|
32
|
+
info << "Captured events:\n"
|
|
33
|
+
info << dump_events(sentry_events)
|
|
34
|
+
info.join("\n")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def expected_event(event_message)
|
|
38
|
+
if @expected_exception
|
|
39
|
+
Sentry.get_current_client.event_from_exception(@expected_exception)
|
|
40
|
+
else
|
|
41
|
+
Sentry.get_current_client.event_from_message(event_message)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def expected_exception(**opts)
|
|
46
|
+
opts[:exception].new(opts[:message]) if opts[:exception]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def context(**opts)
|
|
50
|
+
opts.fetch(:context, @context || {})
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def tags(**opts)
|
|
54
|
+
opts.fetch(:tags, @tags || {})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def find_matched_event(event_message, sentry_events)
|
|
58
|
+
@matched_event ||= sentry_events
|
|
59
|
+
.find { |event|
|
|
60
|
+
if @expected_exception
|
|
61
|
+
# Is it OK that we only compare the first exception?
|
|
62
|
+
event_exception = event.exception.values.first
|
|
63
|
+
expected_event_exception = @expected_event.exception.values.first
|
|
64
|
+
|
|
65
|
+
event_exception.type == expected_event_exception.type && event_exception.value == expected_event_exception.value
|
|
66
|
+
else
|
|
67
|
+
event.message == @expected_event.message
|
|
68
|
+
end
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def dump_events(sentry_events)
|
|
73
|
+
sentry_events.map(&Kernel.method(:Hash)).map do |hash|
|
|
74
|
+
hash.select { |k, _| [:message, :contexts, :tags, :exception].include?(k) }
|
|
75
|
+
end.map do |hash|
|
|
76
|
+
JSON.pretty_generate(hash)
|
|
77
|
+
end.join("\n\n")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def verify_context
|
|
81
|
+
return true if @context.empty?
|
|
82
|
+
|
|
83
|
+
@matched_event.contexts.any? { |key, value| value == @context[key] }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def verify_tags
|
|
87
|
+
return true if @tags.empty?
|
|
88
|
+
|
|
89
|
+
@tags.all? { |key, value| @matched_event.tags.include?(key) && @matched_event.tags[key] == value }
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/sentry/scope.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "sentry/breadcrumb_buffer"
|
|
4
4
|
require "sentry/propagation_context"
|
|
5
|
+
require "sentry/attachment"
|
|
5
6
|
require "etc"
|
|
6
7
|
|
|
7
8
|
module Sentry
|
|
@@ -22,6 +23,7 @@ module Sentry
|
|
|
22
23
|
:rack_env,
|
|
23
24
|
:span,
|
|
24
25
|
:session,
|
|
26
|
+
:attachments,
|
|
25
27
|
:propagation_context
|
|
26
28
|
]
|
|
27
29
|
|
|
@@ -44,7 +46,7 @@ module Sentry
|
|
|
44
46
|
# @param hint [Hash] the hint data that'll be passed to event processors.
|
|
45
47
|
# @return [Event]
|
|
46
48
|
def apply_to_event(event, hint = nil)
|
|
47
|
-
unless event.is_a?(CheckInEvent)
|
|
49
|
+
unless event.is_a?(CheckInEvent) || event.is_a?(LogEvent)
|
|
48
50
|
event.tags = tags.merge(event.tags)
|
|
49
51
|
event.user = user.merge(event.user)
|
|
50
52
|
event.extra = extra.merge(event.extra)
|
|
@@ -55,13 +57,25 @@ module Sentry
|
|
|
55
57
|
event.level = level
|
|
56
58
|
event.breadcrumbs = breadcrumbs
|
|
57
59
|
event.rack_env = rack_env if rack_env
|
|
60
|
+
event.attachments = attachments
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if event.is_a?(LogEvent)
|
|
64
|
+
event.user = user.merge(event.user)
|
|
58
65
|
end
|
|
59
66
|
|
|
60
67
|
if span
|
|
61
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
|
|
62
73
|
else
|
|
63
74
|
event.contexts[:trace] ||= propagation_context.get_trace_context
|
|
64
|
-
|
|
75
|
+
|
|
76
|
+
if event.respond_to?(:dynamic_sampling_context)
|
|
77
|
+
event.dynamic_sampling_context ||= propagation_context.get_dynamic_sampling_context
|
|
78
|
+
end
|
|
65
79
|
end
|
|
66
80
|
|
|
67
81
|
all_event_processors = self.class.global_event_processors + @event_processors
|
|
@@ -102,6 +116,7 @@ module Sentry
|
|
|
102
116
|
copy.span = span.deep_dup
|
|
103
117
|
copy.session = session.deep_dup
|
|
104
118
|
copy.propagation_context = propagation_context.deep_dup
|
|
119
|
+
copy.attachments = attachments.dup
|
|
105
120
|
copy
|
|
106
121
|
end
|
|
107
122
|
|
|
@@ -119,6 +134,7 @@ module Sentry
|
|
|
119
134
|
self.fingerprint = scope.fingerprint
|
|
120
135
|
self.span = scope.span
|
|
121
136
|
self.propagation_context = scope.propagation_context
|
|
137
|
+
self.attachments = scope.attachments
|
|
122
138
|
end
|
|
123
139
|
|
|
124
140
|
# Updates the scope's data from the given options.
|
|
@@ -128,6 +144,7 @@ module Sentry
|
|
|
128
144
|
# @param user [Hash]
|
|
129
145
|
# @param level [String, Symbol]
|
|
130
146
|
# @param fingerprint [Array]
|
|
147
|
+
# @param attachments [Array<Attachment>]
|
|
131
148
|
# @return [Array]
|
|
132
149
|
def update_from_options(
|
|
133
150
|
contexts: nil,
|
|
@@ -136,6 +153,7 @@ module Sentry
|
|
|
136
153
|
user: nil,
|
|
137
154
|
level: nil,
|
|
138
155
|
fingerprint: nil,
|
|
156
|
+
attachments: nil,
|
|
139
157
|
**options
|
|
140
158
|
)
|
|
141
159
|
self.contexts.merge!(contexts) if contexts
|
|
@@ -283,6 +301,12 @@ module Sentry
|
|
|
283
301
|
@propagation_context = PropagationContext.new(self, env)
|
|
284
302
|
end
|
|
285
303
|
|
|
304
|
+
# Add a new attachment to the scope.
|
|
305
|
+
def add_attachment(**opts)
|
|
306
|
+
attachments << (attachment = Attachment.new(**opts))
|
|
307
|
+
attachment
|
|
308
|
+
end
|
|
309
|
+
|
|
286
310
|
protected
|
|
287
311
|
|
|
288
312
|
# for duplicating scopes internally
|
|
@@ -303,6 +327,7 @@ module Sentry
|
|
|
303
327
|
@rack_env = {}
|
|
304
328
|
@span = nil
|
|
305
329
|
@session = nil
|
|
330
|
+
@attachments = []
|
|
306
331
|
generate_propagation_context
|
|
307
332
|
set_new_breadcrumb_buffer
|
|
308
333
|
end
|
|
@@ -5,11 +5,12 @@ module Sentry
|
|
|
5
5
|
FLUSH_INTERVAL = 60
|
|
6
6
|
|
|
7
7
|
def initialize(configuration, client)
|
|
8
|
-
super(configuration.
|
|
8
|
+
super(configuration.sdk_logger, FLUSH_INTERVAL)
|
|
9
9
|
@client = client
|
|
10
10
|
@pending_aggregates = {}
|
|
11
11
|
@release = configuration.release
|
|
12
12
|
@environment = configuration.environment
|
|
13
|
+
@mutex = Mutex.new
|
|
13
14
|
|
|
14
15
|
log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
|
|
15
16
|
end
|
|
@@ -18,7 +19,6 @@ module Sentry
|
|
|
18
19
|
return if @pending_aggregates.empty?
|
|
19
20
|
|
|
20
21
|
@client.capture_envelope(pending_envelope)
|
|
21
|
-
@pending_aggregates = {}
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
alias_method :run, :flush
|
|
@@ -42,11 +42,15 @@ module Sentry
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def pending_envelope
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
aggregates = @mutex.synchronize do
|
|
46
|
+
aggregates = @pending_aggregates.values
|
|
47
|
+
@pending_aggregates = {}
|
|
48
|
+
aggregates
|
|
49
|
+
end
|
|
49
50
|
|
|
51
|
+
envelope = Envelope.new
|
|
52
|
+
header = { type: "sessions" }
|
|
53
|
+
payload = { attrs: attrs, aggregates: aggregates }
|
|
50
54
|
envelope.add_item(header, payload)
|
|
51
55
|
envelope
|
|
52
56
|
end
|
data/lib/sentry/span.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
require "sentry/metrics/local_aggregator"
|
|
5
|
+
require "sentry/utils/uuid"
|
|
5
6
|
|
|
6
7
|
module Sentry
|
|
7
8
|
class Span
|
|
@@ -44,6 +45,11 @@ module Sentry
|
|
|
44
45
|
LINENO = "code.lineno"
|
|
45
46
|
FUNCTION = "code.function"
|
|
46
47
|
NAMESPACE = "code.namespace"
|
|
48
|
+
|
|
49
|
+
MESSAGING_MESSAGE_ID = "messaging.message.id"
|
|
50
|
+
MESSAGING_DESTINATION_NAME = "messaging.destination.name"
|
|
51
|
+
MESSAGING_MESSAGE_RECEIVE_LATENCY = "messaging.message.receive.latency"
|
|
52
|
+
MESSAGING_MESSAGE_RETRY_COUNT = "messaging.message.retry.count"
|
|
47
53
|
end
|
|
48
54
|
|
|
49
55
|
STATUS_MAP = {
|
|
@@ -122,8 +128,8 @@ module Sentry
|
|
|
122
128
|
timestamp: nil,
|
|
123
129
|
origin: nil
|
|
124
130
|
)
|
|
125
|
-
@trace_id = trace_id ||
|
|
126
|
-
@span_id = span_id ||
|
|
131
|
+
@trace_id = trace_id || Utils.uuid
|
|
132
|
+
@span_id = span_id || Utils.uuid.slice(0, 16)
|
|
127
133
|
@parent_span_id = parent_span_id
|
|
128
134
|
@sampled = sampled
|
|
129
135
|
@start_timestamp = start_timestamp || Sentry.utc_now.to_f
|
|
@@ -160,6 +166,12 @@ module Sentry
|
|
|
160
166
|
transaction.get_baggage&.serialize
|
|
161
167
|
end
|
|
162
168
|
|
|
169
|
+
# Returns the Dynamic Sampling Context from the transaction baggage.
|
|
170
|
+
# @return [Hash, nil]
|
|
171
|
+
def get_dynamic_sampling_context
|
|
172
|
+
transaction.get_baggage&.dynamic_sampling_context
|
|
173
|
+
end
|
|
174
|
+
|
|
163
175
|
# @return [Hash]
|
|
164
176
|
def to_hash
|
|
165
177
|
hash = {
|
|
@@ -192,7 +204,8 @@ module Sentry
|
|
|
192
204
|
description: @description,
|
|
193
205
|
op: @op,
|
|
194
206
|
status: @status,
|
|
195
|
-
origin: @origin
|
|
207
|
+
origin: @origin,
|
|
208
|
+
data: @data
|
|
196
209
|
}
|
|
197
210
|
end
|
|
198
211
|
|
|
@@ -249,7 +262,7 @@ module Sentry
|
|
|
249
262
|
|
|
250
263
|
|
|
251
264
|
# Sets the span's status.
|
|
252
|
-
# @param
|
|
265
|
+
# @param status [String] status of the span.
|
|
253
266
|
def set_status(status)
|
|
254
267
|
@status = status
|
|
255
268
|
end
|