sentry-ruby 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile +5 -0
- data/Rakefile +3 -1
- data/lib/sentry-ruby.rb +11 -0
- data/lib/sentry/benchmarks/benchmark_transport.rb +14 -0
- data/lib/sentry/breadcrumb.rb +1 -1
- data/lib/sentry/breadcrumb/sentry_logger.rb +0 -14
- data/lib/sentry/client.rb +12 -0
- data/lib/sentry/configuration.rb +63 -49
- data/lib/sentry/dsn.rb +6 -3
- data/lib/sentry/event.rb +23 -21
- data/lib/sentry/hub.rb +7 -1
- data/lib/sentry/rack.rb +1 -0
- data/lib/sentry/rack/tracing.rb +39 -0
- data/lib/sentry/scope.rb +23 -1
- data/lib/sentry/span.rb +146 -0
- data/lib/sentry/transaction.rb +113 -0
- data/lib/sentry/transaction_event.rb +29 -0
- data/lib/sentry/transport.rb +7 -5
- data/lib/sentry/transport/http_transport.rb +5 -2
- data/lib/sentry/transport/state.rb +2 -2
- data/lib/sentry/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8f89cd1b0354cede1b5aab23b0b8652fed01bcebc3af7f0cb45a13d0f1d99ba
|
4
|
+
data.tar.gz: 38fa765f4acb958b8170a31017028a45cce342667bd8dee7dbff07c4e097ffc5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be0a56af7629bf528be987f62bcb0a4fd12430d75b7541acc6d946314306ab4968c214eb7a533695dcd8aa3b3f434225f8dffceecee4bc39a8b5bd45e168e3d1
|
7
|
+
data.tar.gz: 383d6e88932c6ff3f6e79ca3c35c05d7a23151dd85db057e45dc8577f88673e8195659df3a0c01d71b1e67a61e7f1008eb36ab294047fdeea5cedf2a7d02f92c
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/lib/sentry-ruby.rb
CHANGED
@@ -3,6 +3,9 @@ require "sentry/core_ext/object/deep_dup"
|
|
3
3
|
require "sentry/configuration"
|
4
4
|
require "sentry/logger"
|
5
5
|
require "sentry/event"
|
6
|
+
require "sentry/transaction_event"
|
7
|
+
require "sentry/span"
|
8
|
+
require "sentry/transaction"
|
6
9
|
require "sentry/hub"
|
7
10
|
require "sentry/rack"
|
8
11
|
|
@@ -20,6 +23,10 @@ module Sentry
|
|
20
23
|
META
|
21
24
|
end
|
22
25
|
|
26
|
+
def self.utc_now
|
27
|
+
Time.now.utc
|
28
|
+
end
|
29
|
+
|
23
30
|
class << self
|
24
31
|
def init(&block)
|
25
32
|
config = Configuration.new
|
@@ -92,6 +99,10 @@ module Sentry
|
|
92
99
|
get_current_hub.capture_message(message, **options, &block)
|
93
100
|
end
|
94
101
|
|
102
|
+
def start_transaction(**options)
|
103
|
+
get_current_hub.start_transaction(**options)
|
104
|
+
end
|
105
|
+
|
95
106
|
def last_event_id
|
96
107
|
get_current_hub.last_event_id
|
97
108
|
end
|
data/lib/sentry/breadcrumb.rb
CHANGED
@@ -83,20 +83,6 @@ module Sentry
|
|
83
83
|
Sentry.breadcrumbs
|
84
84
|
end
|
85
85
|
end
|
86
|
-
module OldBreadcrumbsSentryLogger
|
87
|
-
def self.included(base)
|
88
|
-
base.class_eval do
|
89
|
-
include Sentry::Breadcrumbs::SentryLogger
|
90
|
-
alias_method :add_without_sentry, :add
|
91
|
-
alias_method :add, :add_with_sentry
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def add_with_sentry(*args)
|
96
|
-
add_breadcrumb(*args)
|
97
|
-
add_without_sentry(*args)
|
98
|
-
end
|
99
|
-
end
|
100
86
|
end
|
101
87
|
end
|
102
88
|
|
data/lib/sentry/client.rb
CHANGED
@@ -51,6 +51,18 @@ module Sentry
|
|
51
51
|
Event.new(configuration: configuration, message: message)
|
52
52
|
end
|
53
53
|
|
54
|
+
def event_from_transaction(transaction)
|
55
|
+
TransactionEvent.new(configuration: configuration).tap do |event|
|
56
|
+
event.transaction = transaction.name
|
57
|
+
event.contexts.merge!(trace: transaction.get_trace_context)
|
58
|
+
event.timestamp = transaction.timestamp
|
59
|
+
event.start_timestamp = transaction.start_timestamp
|
60
|
+
|
61
|
+
finished_spans = transaction.span_recorder.spans.select { |span| span.timestamp && span != transaction }
|
62
|
+
event.spans = finished_spans.map(&:to_hash)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
54
66
|
def send_event(event)
|
55
67
|
return false unless configuration.sending_allowed?(event)
|
56
68
|
|
data/lib/sentry/configuration.rb
CHANGED
@@ -15,6 +15,28 @@ module Sentry
|
|
15
15
|
attr_reader :async
|
16
16
|
alias async? async
|
17
17
|
|
18
|
+
# a proc/lambda that takes an array of stack traces
|
19
|
+
# it'll be used to silence (reduce) backtrace of the exception
|
20
|
+
#
|
21
|
+
# for example:
|
22
|
+
#
|
23
|
+
# ```ruby
|
24
|
+
# Sentry.configuration.backtrace_cleanup_callback = lambda do |backtrace|
|
25
|
+
# Rails.backtrace_cleaner.clean(backtrace)
|
26
|
+
# end
|
27
|
+
# ```
|
28
|
+
#
|
29
|
+
attr_accessor :backtrace_cleanup_callback
|
30
|
+
|
31
|
+
# Optional Proc, called before sending an event to the server/
|
32
|
+
# E.g.: lambda { |event| event }
|
33
|
+
# E.g.: lambda { |event| nil }
|
34
|
+
# E.g.: lambda { |event|
|
35
|
+
# event[:message] = 'a'
|
36
|
+
# event
|
37
|
+
# }
|
38
|
+
attr_reader :before_send
|
39
|
+
|
18
40
|
# An array of breadcrumbs loggers to be used. Available options are:
|
19
41
|
# - :sentry_logger
|
20
42
|
# - :active_support_logger
|
@@ -26,6 +48,9 @@ module Sentry
|
|
26
48
|
# RACK_ENV by default.
|
27
49
|
attr_reader :current_environment
|
28
50
|
|
51
|
+
# the dsn value, whether it's set via `config.dsn=` or `ENV["SENTRY_DSN"]`
|
52
|
+
attr_reader :dsn
|
53
|
+
|
29
54
|
# Whitelist of environments that will send notifications to Sentry. Array of Strings.
|
30
55
|
attr_accessor :environments
|
31
56
|
|
@@ -64,22 +89,14 @@ module Sentry
|
|
64
89
|
# any events, and a value of 1.0 will send 100% of events.
|
65
90
|
attr_accessor :sample_rate
|
66
91
|
|
67
|
-
# a proc/lambda that takes an array of stack traces
|
68
|
-
# it'll be used to silence (reduce) backtrace of the exception
|
69
|
-
#
|
70
|
-
# for example:
|
71
|
-
#
|
72
|
-
# ```ruby
|
73
|
-
# Sentry.configuration.backtrace_cleanup_callback = lambda do |backtrace|
|
74
|
-
# Rails.backtrace_cleaner.clean(backtrace)
|
75
|
-
# end
|
76
|
-
# ```
|
77
|
-
#
|
78
|
-
attr_accessor :backtrace_cleanup_callback
|
79
|
-
|
80
92
|
# Include module versions in reports - boolean.
|
81
93
|
attr_accessor :send_modules
|
82
94
|
|
95
|
+
# When send_default_pii's value is false (default), sensitive information like
|
96
|
+
# - user ip
|
97
|
+
# - user cookie
|
98
|
+
# - request body
|
99
|
+
# will not be sent to Sentry.
|
83
100
|
attr_accessor :send_default_pii
|
84
101
|
|
85
102
|
attr_accessor :server_name
|
@@ -90,25 +107,24 @@ module Sentry
|
|
90
107
|
# e.g. lambda { |exc_or_msg| exc_or_msg.some_attr == false }
|
91
108
|
attr_reader :should_capture
|
92
109
|
|
93
|
-
#
|
94
|
-
attr_accessor :silence_ready
|
95
|
-
|
110
|
+
# Return a Transport::Configuration object for transport-related configurations.
|
96
111
|
attr_reader :transport
|
97
112
|
|
98
|
-
#
|
99
|
-
|
100
|
-
# E.g.: lambda { |event| nil }
|
101
|
-
# E.g.: lambda { |event|
|
102
|
-
# event[:message] = 'a'
|
103
|
-
# event
|
104
|
-
# }
|
105
|
-
attr_reader :before_send
|
113
|
+
# Take a float between 0.0 and 1.0 as the sample rate for tracing events (transactions).
|
114
|
+
attr_accessor :traces_sample_rate
|
106
115
|
|
107
|
-
#
|
108
|
-
|
116
|
+
# Take a Proc that controls the sample rate for every tracing event, e.g.
|
117
|
+
# ```
|
118
|
+
# lambda do |tracing_context|
|
119
|
+
# # tracing_context[:transaction_context] contains the information about the transaction
|
120
|
+
# # tracing_context[:parent_sampled] contains the transaction's parent's sample decision
|
121
|
+
# true # return value can be a boolean or a float between 0.0 and 1.0
|
122
|
+
# end
|
123
|
+
# ```
|
124
|
+
attr_accessor :traces_sampler
|
109
125
|
|
110
|
-
#
|
111
|
-
attr_reader :
|
126
|
+
# these are not config options
|
127
|
+
attr_reader :errors, :gem_specs
|
112
128
|
|
113
129
|
# Most of these errors generate 4XX responses. In general, Sentry clients
|
114
130
|
# only automatically report 5xx responses.
|
@@ -155,9 +171,11 @@ module Sentry
|
|
155
171
|
self.server_name = server_name_from_env
|
156
172
|
self.should_capture = false
|
157
173
|
|
158
|
-
@transport = Transport::Configuration.new
|
159
174
|
self.before_send = false
|
160
175
|
self.rack_env_whitelist = RACK_ENV_WHITELIST_DEFAULT
|
176
|
+
|
177
|
+
@transport = Transport::Configuration.new
|
178
|
+
@gem_specs = Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
161
179
|
post_initialization_callback
|
162
180
|
end
|
163
181
|
|
@@ -211,13 +229,6 @@ module Sentry
|
|
211
229
|
@before_send = value
|
212
230
|
end
|
213
231
|
|
214
|
-
# Allows config options to be read like a hash
|
215
|
-
#
|
216
|
-
# @param [Symbol] option Key for a given attribute
|
217
|
-
def [](option)
|
218
|
-
public_send(option)
|
219
|
-
end
|
220
|
-
|
221
232
|
def current_environment=(environment)
|
222
233
|
@current_environment = environment.to_s
|
223
234
|
end
|
@@ -234,8 +245,8 @@ module Sentry
|
|
234
245
|
alias sending_allowed? capture_allowed?
|
235
246
|
|
236
247
|
def error_messages
|
237
|
-
@errors = [errors[0]] + errors[1..-1].map(&:downcase) # fix case of all but first
|
238
|
-
errors.join(", ")
|
248
|
+
@errors = [@errors[0]] + @errors[1..-1].map(&:downcase) # fix case of all but first
|
249
|
+
@errors.join(", ")
|
239
250
|
end
|
240
251
|
|
241
252
|
def project_root=(root_dir)
|
@@ -259,6 +270,10 @@ module Sentry
|
|
259
270
|
environments.empty? || environments.include?(current_environment)
|
260
271
|
end
|
261
272
|
|
273
|
+
def tracing_enabled?
|
274
|
+
!!((@traces_sample_rate && @traces_sample_rate > 0.0) || @traces_sampler)
|
275
|
+
end
|
276
|
+
|
262
277
|
private
|
263
278
|
|
264
279
|
def detect_project_root
|
@@ -279,13 +294,17 @@ module Sentry
|
|
279
294
|
end
|
280
295
|
|
281
296
|
def excluded_exception?(incoming_exception)
|
282
|
-
|
283
|
-
matches_exception?(
|
297
|
+
excluded_exception_classes.any? do |excluded_exception|
|
298
|
+
matches_exception?(excluded_exception, incoming_exception)
|
284
299
|
end
|
285
300
|
end
|
286
301
|
|
302
|
+
def excluded_exception_classes
|
303
|
+
@excluded_exception_classes ||= excluded_exceptions.map { |e| get_exception_class(e) }
|
304
|
+
end
|
305
|
+
|
287
306
|
def get_exception_class(x)
|
288
|
-
x.is_a?(Module) ? x :
|
307
|
+
x.is_a?(Module) ? x : safe_const_get(x)
|
289
308
|
end
|
290
309
|
|
291
310
|
def matches_exception?(excluded_exception_class, incoming_exception)
|
@@ -296,14 +315,9 @@ module Sentry
|
|
296
315
|
end
|
297
316
|
end
|
298
317
|
|
299
|
-
|
300
|
-
|
301
|
-
x
|
302
|
-
if !x.match(/::/)
|
303
|
-
Object.const_get(x)
|
304
|
-
else
|
305
|
-
x.split(MODULE_SEPARATOR).reject(&:empty?).inject(Object) { |a, e| a.const_get(e) }
|
306
|
-
end
|
318
|
+
def safe_const_get(x)
|
319
|
+
x = x.to_s unless x.is_a?(String)
|
320
|
+
Object.const_get(x)
|
307
321
|
rescue NameError # There's no way to safely ask if a constant exist for an unknown string
|
308
322
|
nil
|
309
323
|
end
|
data/lib/sentry/dsn.rb
CHANGED
@@ -2,7 +2,10 @@ require "uri"
|
|
2
2
|
|
3
3
|
module Sentry
|
4
4
|
class DSN
|
5
|
-
|
5
|
+
PORT_MAP = { 'http' => 80, 'https' => 443 }.freeze
|
6
|
+
REQUIRED_ATTRIBUTES = %w(host path public_key project_id).freeze
|
7
|
+
|
8
|
+
attr_reader :scheme, :secret_key, :port, *REQUIRED_ATTRIBUTES
|
6
9
|
|
7
10
|
def initialize(dsn_string)
|
8
11
|
@raw_value = dsn_string
|
@@ -24,7 +27,7 @@ module Sentry
|
|
24
27
|
end
|
25
28
|
|
26
29
|
def valid?
|
27
|
-
|
30
|
+
REQUIRED_ATTRIBUTES.all? { |k| public_send(k) }
|
28
31
|
end
|
29
32
|
|
30
33
|
def to_s
|
@@ -33,7 +36,7 @@ module Sentry
|
|
33
36
|
|
34
37
|
def server
|
35
38
|
server = "#{scheme}://#{host}"
|
36
|
-
server += ":#{port}" unless port ==
|
39
|
+
server += ":#{port}" unless port == PORT_MAP[scheme]
|
37
40
|
server += path
|
38
41
|
server
|
39
42
|
end
|
data/lib/sentry/event.rb
CHANGED
@@ -13,21 +13,19 @@ module Sentry
|
|
13
13
|
release environment server_name modules
|
14
14
|
message user tags contexts extra
|
15
15
|
fingerprint breadcrumbs backtrace transaction
|
16
|
-
platform sdk
|
16
|
+
platform sdk type
|
17
17
|
)
|
18
18
|
|
19
19
|
attr_accessor(*ATTRIBUTES)
|
20
|
-
attr_reader :
|
21
|
-
|
22
|
-
alias event_id id
|
20
|
+
attr_reader :configuration
|
23
21
|
|
24
22
|
def initialize(configuration:, message: nil)
|
25
23
|
# this needs to go first because some setters rely on configuration
|
26
24
|
@configuration = configuration
|
27
25
|
|
28
26
|
# Set some simple default values
|
29
|
-
@
|
30
|
-
@timestamp =
|
27
|
+
@event_id = SecureRandom.uuid.delete("-")
|
28
|
+
@timestamp = Sentry.utc_now.iso8601
|
31
29
|
@platform = :ruby
|
32
30
|
@sdk = Sentry.sdk_meta
|
33
31
|
|
@@ -41,7 +39,7 @@ module Sentry
|
|
41
39
|
@server_name = configuration.server_name
|
42
40
|
@environment = configuration.current_environment
|
43
41
|
@release = configuration.release
|
44
|
-
@modules =
|
42
|
+
@modules = configuration.gem_specs if configuration.send_modules
|
45
43
|
|
46
44
|
@message = message || ""
|
47
45
|
|
@@ -51,8 +49,8 @@ module Sentry
|
|
51
49
|
class << self
|
52
50
|
def get_log_message(event_hash)
|
53
51
|
message = event_hash[:message] || event_hash['message']
|
54
|
-
message = get_message_from_exception(event_hash) if message.empty?
|
55
|
-
message = '<no message value>' if message.empty?
|
52
|
+
message = get_message_from_exception(event_hash) if message.nil? || message.empty?
|
53
|
+
message = '<no message value>' if message.nil? || message.empty?
|
56
54
|
message
|
57
55
|
end
|
58
56
|
|
@@ -68,7 +66,7 @@ module Sentry
|
|
68
66
|
end
|
69
67
|
|
70
68
|
def timestamp=(time)
|
71
|
-
@timestamp = time.is_a?(Time) ? time.
|
69
|
+
@timestamp = time.is_a?(Time) ? time.to_f : time
|
72
70
|
end
|
73
71
|
|
74
72
|
def level=(new_level) # needed to meet the Sentry spec
|
@@ -87,11 +85,12 @@ module Sentry
|
|
87
85
|
end
|
88
86
|
end
|
89
87
|
|
90
|
-
def
|
91
|
-
|
92
|
-
|
93
|
-
end
|
88
|
+
def type
|
89
|
+
"event"
|
90
|
+
end
|
94
91
|
|
92
|
+
def to_hash
|
93
|
+
data = serialize_attributes
|
95
94
|
data[:breadcrumbs] = breadcrumbs.to_hash if breadcrumbs
|
96
95
|
data[:stacktrace] = @stacktrace.to_hash if @stacktrace
|
97
96
|
data[:request] = @request.to_hash if @request
|
@@ -141,9 +140,9 @@ module Sentry
|
|
141
140
|
frame.in_app = line.in_app
|
142
141
|
frame.module = line.module_name if line.module_name
|
143
142
|
|
144
|
-
if configuration
|
143
|
+
if configuration.context_lines && frame.abs_path
|
145
144
|
frame.pre_context, frame.context_line, frame.post_context = \
|
146
|
-
configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration
|
145
|
+
configuration.linecache.get_file_context(frame.abs_path, frame.lineno, configuration.context_lines)
|
147
146
|
end
|
148
147
|
|
149
148
|
memo << frame if frame.filename
|
@@ -152,6 +151,14 @@ module Sentry
|
|
152
151
|
|
153
152
|
private
|
154
153
|
|
154
|
+
def serialize_attributes
|
155
|
+
self.class::ATTRIBUTES.each_with_object({}) do |att, memo|
|
156
|
+
if value = public_send(att)
|
157
|
+
memo[att] = value
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
155
162
|
# When behind a proxy (or if the user is using a proxy), we can't use
|
156
163
|
# REMOTE_ADDR to determine the Event IP, and must use other headers instead.
|
157
164
|
def calculate_real_ip_from_rack(env)
|
@@ -162,10 +169,5 @@ module Sentry
|
|
162
169
|
:forwarded_for => env["HTTP_X_FORWARDED_FOR"]
|
163
170
|
).calculate_ip
|
164
171
|
end
|
165
|
-
|
166
|
-
def list_gem_specs
|
167
|
-
# Older versions of Rubygems don't support iterating over all specs
|
168
|
-
Hash[Gem::Specification.map { |spec| [spec.name, spec.version.to_s] }] if Gem::Specification.respond_to?(:map)
|
169
|
-
end
|
170
172
|
end
|
171
173
|
end
|
data/lib/sentry/hub.rb
CHANGED
@@ -67,6 +67,12 @@ module Sentry
|
|
67
67
|
@stack.pop
|
68
68
|
end
|
69
69
|
|
70
|
+
def start_transaction(transaction: nil, **options)
|
71
|
+
transaction ||= Transaction.new(**options)
|
72
|
+
transaction.set_initial_sample_desicion
|
73
|
+
transaction
|
74
|
+
end
|
75
|
+
|
70
76
|
def capture_exception(exception, **options, &block)
|
71
77
|
return unless current_client
|
72
78
|
|
@@ -99,7 +105,7 @@ module Sentry
|
|
99
105
|
|
100
106
|
event = current_client.capture_event(event, scope)
|
101
107
|
|
102
|
-
@last_event_id = event.
|
108
|
+
@last_event_id = event.event_id
|
103
109
|
event
|
104
110
|
end
|
105
111
|
|
data/lib/sentry/rack.rb
CHANGED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Sentry
|
2
|
+
module Rack
|
3
|
+
class Tracing
|
4
|
+
def initialize(app)
|
5
|
+
@app = app
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(env)
|
9
|
+
Sentry.clone_hub_to_current_thread unless Sentry.get_current_hub
|
10
|
+
|
11
|
+
if Sentry.configuration.traces_sample_rate.to_f == 0.0
|
12
|
+
return @app.call(env)
|
13
|
+
end
|
14
|
+
|
15
|
+
Sentry.with_scope do |scope|
|
16
|
+
scope.clear_breadcrumbs
|
17
|
+
scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
|
18
|
+
span = Sentry.start_transaction(name: scope.transaction_name, op: "rack.request")
|
19
|
+
scope.set_span(span)
|
20
|
+
|
21
|
+
begin
|
22
|
+
response = @app.call(env)
|
23
|
+
rescue
|
24
|
+
finish_span(span, 500)
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
|
28
|
+
finish_span(span, response[0])
|
29
|
+
response
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def finish_span(span, status_code)
|
34
|
+
span.set_http_status(status_code)
|
35
|
+
span.finish
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/sentry/scope.rb
CHANGED
@@ -3,7 +3,7 @@ require "etc"
|
|
3
3
|
|
4
4
|
module Sentry
|
5
5
|
class Scope
|
6
|
-
ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env]
|
6
|
+
ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env, :span]
|
7
7
|
|
8
8
|
attr_reader(*ATTRIBUTES)
|
9
9
|
|
@@ -20,6 +20,11 @@ module Sentry
|
|
20
20
|
event.user = user.merge(event.user)
|
21
21
|
event.extra = extra.merge(event.extra)
|
22
22
|
event.contexts = contexts.merge(event.contexts)
|
23
|
+
|
24
|
+
if span
|
25
|
+
event.contexts[:trace] = span.get_trace_context
|
26
|
+
end
|
27
|
+
|
23
28
|
event.fingerprint = fingerprint
|
24
29
|
event.level ||= level
|
25
30
|
event.transaction = transaction_names.last
|
@@ -52,6 +57,7 @@ module Sentry
|
|
52
57
|
copy.user = user.deep_dup
|
53
58
|
copy.transaction_names = transaction_names.deep_dup
|
54
59
|
copy.fingerprint = fingerprint.deep_dup
|
60
|
+
copy.span = span
|
55
61
|
copy
|
56
62
|
end
|
57
63
|
|
@@ -63,6 +69,7 @@ module Sentry
|
|
63
69
|
self.user = scope.user
|
64
70
|
self.transaction_names = scope.transaction_names
|
65
71
|
self.fingerprint = scope.fingerprint
|
72
|
+
self.span = scope.span
|
66
73
|
end
|
67
74
|
|
68
75
|
def update_from_options(
|
@@ -86,6 +93,11 @@ module Sentry
|
|
86
93
|
@rack_env = env
|
87
94
|
end
|
88
95
|
|
96
|
+
def set_span(span)
|
97
|
+
check_argument_type!(span, Span)
|
98
|
+
@span = span
|
99
|
+
end
|
100
|
+
|
89
101
|
def set_user(user_hash)
|
90
102
|
check_argument_type!(user_hash, Hash)
|
91
103
|
@user = user_hash
|
@@ -130,6 +142,15 @@ module Sentry
|
|
130
142
|
@transaction_names.last
|
131
143
|
end
|
132
144
|
|
145
|
+
def get_transaction
|
146
|
+
# transaction will always be the first in the span_recorder
|
147
|
+
span.span_recorder.spans.first if span
|
148
|
+
end
|
149
|
+
|
150
|
+
def get_span
|
151
|
+
span
|
152
|
+
end
|
153
|
+
|
133
154
|
def set_fingerprint(fingerprint)
|
134
155
|
check_argument_type!(fingerprint, Array)
|
135
156
|
|
@@ -164,6 +185,7 @@ module Sentry
|
|
164
185
|
@transaction_names = []
|
165
186
|
@event_processors = []
|
166
187
|
@rack_env = {}
|
188
|
+
@span = nil
|
167
189
|
end
|
168
190
|
|
169
191
|
class << self
|
data/lib/sentry/span.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "securerandom"
|
3
|
+
|
4
|
+
module Sentry
|
5
|
+
class Span
|
6
|
+
STATUS_MAP = {
|
7
|
+
400 => "invalid_argument",
|
8
|
+
401 => "unauthenticated",
|
9
|
+
403 => "permission_denied",
|
10
|
+
404 => "not_found",
|
11
|
+
409 => "already_exists",
|
12
|
+
429 => "resource_exhausted",
|
13
|
+
499 => "cancelled",
|
14
|
+
500 => "internal_error",
|
15
|
+
501 => "unimplemented",
|
16
|
+
503 => "unavailable",
|
17
|
+
504 => "deadline_exceeded"
|
18
|
+
}
|
19
|
+
|
20
|
+
|
21
|
+
attr_reader :trace_id, :span_id, :parent_span_id, :sampled, :start_timestamp, :timestamp, :description, :op, :status, :tags, :data
|
22
|
+
attr_accessor :span_recorder
|
23
|
+
|
24
|
+
def initialize(description: nil, op: nil, status: nil, trace_id: nil, parent_span_id: nil, sampled: nil, start_timestamp: nil, timestamp: nil)
|
25
|
+
@trace_id = trace_id || SecureRandom.uuid.delete("-")
|
26
|
+
@span_id = SecureRandom.hex(8)
|
27
|
+
@parent_span_id = parent_span_id
|
28
|
+
@sampled = sampled
|
29
|
+
@start_timestamp = start_timestamp || Sentry.utc_now.to_f
|
30
|
+
@timestamp = timestamp
|
31
|
+
@description = description
|
32
|
+
@op = op
|
33
|
+
@status = status
|
34
|
+
@data = {}
|
35
|
+
@tags = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def set_span_recorder
|
39
|
+
@span_recorder = SpanRecorder.new(1000)
|
40
|
+
@span_recorder.add(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def finish
|
44
|
+
# already finished
|
45
|
+
return if @timestamp
|
46
|
+
|
47
|
+
@timestamp = Sentry.utc_now.to_f
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_sentry_trace
|
51
|
+
sampled_flag = ""
|
52
|
+
sampled_flag = @sampled ? 1 : 0 unless @sampled.nil?
|
53
|
+
|
54
|
+
"#{@trace_id}-#{@span_id}-#{sampled_flag}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_hash
|
58
|
+
{
|
59
|
+
trace_id: @trace_id,
|
60
|
+
span_id: @span_id,
|
61
|
+
parent_span_id: @parent_span_id,
|
62
|
+
start_timestamp: @start_timestamp,
|
63
|
+
timestamp: @timestamp,
|
64
|
+
description: @description,
|
65
|
+
op: @op,
|
66
|
+
status: @status,
|
67
|
+
tags: @tags,
|
68
|
+
data: @data
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_trace_context
|
73
|
+
{
|
74
|
+
trace_id: @trace_id,
|
75
|
+
span_id: @span_id,
|
76
|
+
description: @description,
|
77
|
+
op: @op,
|
78
|
+
status: @status
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def start_child(**options)
|
83
|
+
options = options.dup.merge(trace_id: @trace_id, parent_span_id: @span_id, sampled: @sampled)
|
84
|
+
child_span = Span.new(options)
|
85
|
+
child_span.span_recorder = @span_recorder
|
86
|
+
|
87
|
+
if @span_recorder && @sampled
|
88
|
+
@span_recorder.add(child_span)
|
89
|
+
end
|
90
|
+
|
91
|
+
child_span
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_op(op)
|
95
|
+
@op = op
|
96
|
+
end
|
97
|
+
|
98
|
+
def set_description(description)
|
99
|
+
@description = description
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_status(status)
|
103
|
+
@status = status
|
104
|
+
end
|
105
|
+
|
106
|
+
def set_timestamp(timestamp)
|
107
|
+
@timestamp = timestamp
|
108
|
+
end
|
109
|
+
|
110
|
+
def set_http_status(status_code)
|
111
|
+
status_code = status_code.to_i
|
112
|
+
set_data("status_code", status_code)
|
113
|
+
|
114
|
+
status =
|
115
|
+
if status_code >= 200 && status_code < 299
|
116
|
+
"ok"
|
117
|
+
else
|
118
|
+
STATUS_MAP[status_code]
|
119
|
+
end
|
120
|
+
set_status(status)
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_data(key, value)
|
124
|
+
@data[key] = value
|
125
|
+
end
|
126
|
+
|
127
|
+
def set_tag(key, value)
|
128
|
+
@tags[key] = value
|
129
|
+
end
|
130
|
+
|
131
|
+
class SpanRecorder
|
132
|
+
attr_reader :max_length, :spans
|
133
|
+
|
134
|
+
def initialize(max_length)
|
135
|
+
@max_length = max_length
|
136
|
+
@spans = []
|
137
|
+
end
|
138
|
+
|
139
|
+
def add(span)
|
140
|
+
if @spans.count < @max_length
|
141
|
+
@spans << span
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Sentry
|
2
|
+
class Transaction < Span
|
3
|
+
SENTRY_TRACE_REGEXP = Regexp.new(
|
4
|
+
"^[ \t]*" + # whitespace
|
5
|
+
"([0-9a-f]{32})?" + # trace_id
|
6
|
+
"-?([0-9a-f]{16})?" + # span_id
|
7
|
+
"-?([01])?" + # sampled
|
8
|
+
"[ \t]*$" # whitespace
|
9
|
+
)
|
10
|
+
UNLABELD_NAME = "<unlabeled transaction>".freeze
|
11
|
+
MESSAGE_PREFIX = "[Tracing]"
|
12
|
+
|
13
|
+
attr_reader :name, :parent_sampled
|
14
|
+
|
15
|
+
def initialize(name: nil, parent_sampled: nil, **options)
|
16
|
+
super(**options)
|
17
|
+
|
18
|
+
@name = name
|
19
|
+
@parent_sampled = parent_sampled
|
20
|
+
set_span_recorder
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.from_sentry_trace(sentry_trace, **options)
|
24
|
+
return unless sentry_trace
|
25
|
+
|
26
|
+
match = SENTRY_TRACE_REGEXP.match(sentry_trace)
|
27
|
+
trace_id, parent_span_id, sampled_flag = match[1..3]
|
28
|
+
|
29
|
+
sampled = sampled_flag != "0"
|
30
|
+
|
31
|
+
new(trace_id: trace_id, parent_span_id: parent_span_id, parent_sampled: sampled, **options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_hash
|
35
|
+
hash = super
|
36
|
+
hash.merge!(name: @name, sampled: @sampled, parent_sampled: @parent_sampled)
|
37
|
+
hash
|
38
|
+
end
|
39
|
+
|
40
|
+
def set_initial_sample_desicion(sampling_context = {})
|
41
|
+
unless Sentry.configuration.tracing_enabled?
|
42
|
+
@sampled = false
|
43
|
+
return
|
44
|
+
end
|
45
|
+
|
46
|
+
return unless @sampled.nil?
|
47
|
+
|
48
|
+
transaction_description = generate_transaction_description
|
49
|
+
|
50
|
+
logger = Sentry.configuration.logger
|
51
|
+
sample_rate = Sentry.configuration.traces_sample_rate
|
52
|
+
traces_sampler = Sentry.configuration.traces_sampler
|
53
|
+
|
54
|
+
if traces_sampler.is_a?(Proc)
|
55
|
+
sampling_context = sampling_context.merge(
|
56
|
+
parent_sampled: @parent_sampled,
|
57
|
+
transaction_context: self.to_hash
|
58
|
+
)
|
59
|
+
|
60
|
+
sample_rate = traces_sampler.call(sampling_context)
|
61
|
+
end
|
62
|
+
|
63
|
+
unless [true, false].include?(sample_rate) || (sample_rate.is_a?(Float) && sample_rate >= 0.0 && sample_rate <= 1.0)
|
64
|
+
@sampled = false
|
65
|
+
logger.warn("#{MESSAGE_PREFIX} Discarding #{transaction_description} because of invalid sample_rate: #{sample_rate}")
|
66
|
+
return
|
67
|
+
end
|
68
|
+
|
69
|
+
if sample_rate == 0.0 || sample_rate == false
|
70
|
+
@sampled = false
|
71
|
+
logger.debug("#{MESSAGE_PREFIX} Discarding #{transaction_description} because traces_sampler returned 0 or false")
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
if sample_rate == true
|
76
|
+
@sampled = true
|
77
|
+
else
|
78
|
+
@sampled = Random.rand < sample_rate
|
79
|
+
end
|
80
|
+
|
81
|
+
if @sampled
|
82
|
+
logger.debug("#{MESSAGE_PREFIX} Starting #{transaction_description}")
|
83
|
+
else
|
84
|
+
logger.debug(
|
85
|
+
"#{MESSAGE_PREFIX} Discarding #{transaction_description} because it's not included in the random sample (sampling rate = #{sample_rate})"
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def finish(hub: nil)
|
91
|
+
super() # Span#finish doesn't take arguments
|
92
|
+
|
93
|
+
if @name.nil?
|
94
|
+
@name = UNLABELD_NAME
|
95
|
+
end
|
96
|
+
|
97
|
+
return unless @sampled
|
98
|
+
|
99
|
+
hub ||= Sentry.get_current_hub
|
100
|
+
event = hub.current_client.event_from_transaction(self)
|
101
|
+
hub.capture_event(event)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def generate_transaction_description
|
107
|
+
result = op.nil? ? "" : "<#{@op}> "
|
108
|
+
result += "transaction"
|
109
|
+
result += " <#{@name}>" if @name
|
110
|
+
result
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
class TransactionEvent < Event
|
5
|
+
ATTRIBUTES = %i(
|
6
|
+
event_id level timestamp start_timestamp
|
7
|
+
release environment server_name modules
|
8
|
+
user tags contexts extra
|
9
|
+
transaction platform sdk type
|
10
|
+
)
|
11
|
+
|
12
|
+
attr_accessor(*ATTRIBUTES)
|
13
|
+
attr_accessor :spans
|
14
|
+
|
15
|
+
def start_timestamp=(time)
|
16
|
+
@start_timestamp = time.is_a?(Time) ? time.to_f : time
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
"transaction"
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
data = super
|
25
|
+
data[:spans] = @spans.map(&:to_hash) if @spans
|
26
|
+
data
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/sentry/transport.rb
CHANGED
@@ -32,10 +32,11 @@ module Sentry
|
|
32
32
|
event
|
33
33
|
rescue => e
|
34
34
|
failed_for_exception(e, event)
|
35
|
+
nil
|
35
36
|
end
|
36
37
|
|
37
38
|
def generate_auth_header
|
38
|
-
now =
|
39
|
+
now = Sentry.utc_now.to_i
|
39
40
|
fields = {
|
40
41
|
'sentry_version' => PROTOCOL_VERSION,
|
41
42
|
'sentry_client' => USER_AGENT,
|
@@ -48,11 +49,12 @@ module Sentry
|
|
48
49
|
|
49
50
|
def encode(event_hash)
|
50
51
|
event_id = event_hash[:event_id] || event_hash['event_id']
|
52
|
+
event_type = event_hash[:type] || event_hash['type']
|
51
53
|
|
52
54
|
envelope = <<~ENVELOPE
|
53
|
-
{"event_id":"#{event_id}","dsn":"#{configuration.dsn.to_s}","sdk":#{Sentry.sdk_meta.to_json},"sent_at":"#{
|
54
|
-
{"type":"
|
55
|
-
#{event_hash
|
55
|
+
{"event_id":"#{event_id}","dsn":"#{configuration.dsn.to_s}","sdk":#{Sentry.sdk_meta.to_json},"sent_at":"#{Sentry.utc_now.iso8601}"}
|
56
|
+
{"type":"#{event_type}","content_type":"application/json"}
|
57
|
+
#{JSON.generate(event_hash)}
|
56
58
|
ENVELOPE
|
57
59
|
|
58
60
|
[CONTENT_TYPE, envelope]
|
@@ -86,7 +88,7 @@ module Sentry
|
|
86
88
|
end
|
87
89
|
|
88
90
|
def log_not_sending(event)
|
89
|
-
configuration.logger.warn(LOGGER_PROGNAME) { "Failed to submit event: #{Event.get_log_message(event.to_hash)}" }
|
91
|
+
configuration.logger.warn(LOGGER_PROGNAME) { "Failed to submit event. Unreported Event: #{Event.get_log_message(event.to_hash)}" }
|
90
92
|
end
|
91
93
|
end
|
92
94
|
end
|
@@ -23,9 +23,12 @@ module Sentry
|
|
23
23
|
end
|
24
24
|
rescue Faraday::Error => e
|
25
25
|
error_info = e.message
|
26
|
-
|
27
|
-
|
26
|
+
|
27
|
+
if e.response
|
28
|
+
error_info += "\nbody: #{e.response[:body]}"
|
29
|
+
error_info += " Error in headers is: #{e.response[:headers]['x-sentry-error']}" if e.response[:headers]['x-sentry-error']
|
28
30
|
end
|
31
|
+
|
29
32
|
raise Sentry::Error, error_info
|
30
33
|
end
|
31
34
|
|
@@ -9,7 +9,7 @@ module Sentry
|
|
9
9
|
return true if @status == :online
|
10
10
|
|
11
11
|
interval = @retry_after || [@retry_number, 6].min**2
|
12
|
-
return true if
|
12
|
+
return true if Sentry.utc_now - @last_check >= interval
|
13
13
|
|
14
14
|
false
|
15
15
|
end
|
@@ -17,7 +17,7 @@ module Sentry
|
|
17
17
|
def failure(retry_after = nil)
|
18
18
|
@status = :error
|
19
19
|
@retry_number += 1
|
20
|
-
@last_check =
|
20
|
+
@last_check = Sentry.utc_now
|
21
21
|
@retry_after = retry_after
|
22
22
|
end
|
23
23
|
|
data/lib/sentry/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sentry-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sentry Team
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-11-
|
11
|
+
date: 2020-11-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -46,6 +46,7 @@ files:
|
|
46
46
|
- bin/setup
|
47
47
|
- lib/sentry-ruby.rb
|
48
48
|
- lib/sentry/backtrace.rb
|
49
|
+
- lib/sentry/benchmarks/benchmark_transport.rb
|
49
50
|
- lib/sentry/breadcrumb.rb
|
50
51
|
- lib/sentry/breadcrumb/sentry_logger.rb
|
51
52
|
- lib/sentry/breadcrumb_buffer.rb
|
@@ -65,7 +66,11 @@ files:
|
|
65
66
|
- lib/sentry/logger.rb
|
66
67
|
- lib/sentry/rack.rb
|
67
68
|
- lib/sentry/rack/capture_exception.rb
|
69
|
+
- lib/sentry/rack/tracing.rb
|
68
70
|
- lib/sentry/scope.rb
|
71
|
+
- lib/sentry/span.rb
|
72
|
+
- lib/sentry/transaction.rb
|
73
|
+
- lib/sentry/transaction_event.rb
|
69
74
|
- lib/sentry/transport.rb
|
70
75
|
- lib/sentry/transport/configuration.rb
|
71
76
|
- lib/sentry/transport/dummy_transport.rb
|