sentry-ruby 0.1.3 → 0.2.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/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
|