sentry-ruby 5.3.0 → 5.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +313 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +31 -0
- data/Makefile +4 -0
- data/README.md +10 -6
- data/Rakefile +13 -0
- data/bin/console +18 -0
- data/bin/setup +8 -0
- data/lib/sentry/background_worker.rb +72 -0
- data/lib/sentry/backtrace.rb +124 -0
- data/lib/sentry/baggage.rb +81 -0
- data/lib/sentry/breadcrumb/sentry_logger.rb +90 -0
- data/lib/sentry/breadcrumb.rb +70 -0
- data/lib/sentry/breadcrumb_buffer.rb +64 -0
- data/lib/sentry/client.rb +207 -0
- data/lib/sentry/configuration.rb +543 -0
- data/lib/sentry/core_ext/object/deep_dup.rb +61 -0
- data/lib/sentry/core_ext/object/duplicable.rb +155 -0
- data/lib/sentry/dsn.rb +53 -0
- data/lib/sentry/envelope.rb +96 -0
- data/lib/sentry/error_event.rb +38 -0
- data/lib/sentry/event.rb +178 -0
- data/lib/sentry/exceptions.rb +9 -0
- data/lib/sentry/hub.rb +241 -0
- data/lib/sentry/integrable.rb +26 -0
- data/lib/sentry/interface.rb +16 -0
- data/lib/sentry/interfaces/exception.rb +43 -0
- data/lib/sentry/interfaces/request.rb +134 -0
- data/lib/sentry/interfaces/single_exception.rb +65 -0
- data/lib/sentry/interfaces/stacktrace.rb +87 -0
- data/lib/sentry/interfaces/stacktrace_builder.rb +79 -0
- data/lib/sentry/interfaces/threads.rb +42 -0
- data/lib/sentry/linecache.rb +47 -0
- data/lib/sentry/logger.rb +20 -0
- data/lib/sentry/net/http.rb +103 -0
- data/lib/sentry/rack/capture_exceptions.rb +82 -0
- data/lib/sentry/rack.rb +5 -0
- data/lib/sentry/rake.rb +41 -0
- data/lib/sentry/redis.rb +107 -0
- data/lib/sentry/release_detector.rb +39 -0
- data/lib/sentry/scope.rb +339 -0
- data/lib/sentry/session.rb +33 -0
- data/lib/sentry/session_flusher.rb +90 -0
- data/lib/sentry/span.rb +236 -0
- data/lib/sentry/test_helper.rb +78 -0
- data/lib/sentry/transaction.rb +345 -0
- data/lib/sentry/transaction_event.rb +53 -0
- data/lib/sentry/transport/configuration.rb +25 -0
- data/lib/sentry/transport/dummy_transport.rb +21 -0
- data/lib/sentry/transport/http_transport.rb +175 -0
- data/lib/sentry/transport.rb +214 -0
- data/lib/sentry/utils/argument_checking_helper.rb +13 -0
- data/lib/sentry/utils/custom_inspection.rb +14 -0
- data/lib/sentry/utils/encoding_helper.rb +22 -0
- data/lib/sentry/utils/exception_cause_chain.rb +20 -0
- data/lib/sentry/utils/logging_helper.rb +26 -0
- data/lib/sentry/utils/real_ip.rb +84 -0
- data/lib/sentry/utils/request_id.rb +18 -0
- data/lib/sentry/version.rb +5 -0
- data/lib/sentry-ruby.rb +511 -0
- data/sentry-ruby-core.gemspec +23 -0
- data/sentry-ruby.gemspec +24 -0
- metadata +66 -16
data/lib/sentry/scope.rb
ADDED
@@ -0,0 +1,339 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "sentry/breadcrumb_buffer"
|
4
|
+
require "etc"
|
5
|
+
|
6
|
+
module Sentry
|
7
|
+
class Scope
|
8
|
+
include ArgumentCheckingHelper
|
9
|
+
|
10
|
+
ATTRIBUTES = [
|
11
|
+
:transaction_names,
|
12
|
+
:transaction_sources,
|
13
|
+
:contexts,
|
14
|
+
:extra,
|
15
|
+
:tags,
|
16
|
+
:user,
|
17
|
+
:level,
|
18
|
+
:breadcrumbs,
|
19
|
+
:fingerprint,
|
20
|
+
:event_processors,
|
21
|
+
:rack_env,
|
22
|
+
:span,
|
23
|
+
:session
|
24
|
+
]
|
25
|
+
|
26
|
+
attr_reader(*ATTRIBUTES)
|
27
|
+
|
28
|
+
# @param max_breadcrumbs [Integer] the maximum number of breadcrumbs to be stored in the scope.
|
29
|
+
def initialize(max_breadcrumbs: nil)
|
30
|
+
@max_breadcrumbs = max_breadcrumbs
|
31
|
+
set_default_value
|
32
|
+
end
|
33
|
+
|
34
|
+
# Resets the scope's attributes to defaults.
|
35
|
+
# @return [void]
|
36
|
+
def clear
|
37
|
+
set_default_value
|
38
|
+
end
|
39
|
+
|
40
|
+
# Applies stored attributes and event processors to the given event.
|
41
|
+
# @param event [Event]
|
42
|
+
# @param hint [Hash] the hint data that'll be passed to event processors.
|
43
|
+
# @return [Event]
|
44
|
+
def apply_to_event(event, hint = nil)
|
45
|
+
event.tags = tags.merge(event.tags)
|
46
|
+
event.user = user.merge(event.user)
|
47
|
+
event.extra = extra.merge(event.extra)
|
48
|
+
event.contexts = contexts.merge(event.contexts)
|
49
|
+
event.transaction = transaction_name if transaction_name
|
50
|
+
event.transaction_info = { source: transaction_source } if transaction_source
|
51
|
+
|
52
|
+
if span
|
53
|
+
event.contexts[:trace] = span.get_trace_context
|
54
|
+
end
|
55
|
+
|
56
|
+
event.fingerprint = fingerprint
|
57
|
+
event.level = level
|
58
|
+
event.breadcrumbs = breadcrumbs
|
59
|
+
event.rack_env = rack_env if rack_env
|
60
|
+
|
61
|
+
all_event_processors = self.class.global_event_processors + @event_processors
|
62
|
+
|
63
|
+
unless all_event_processors.empty?
|
64
|
+
all_event_processors.each do |processor_block|
|
65
|
+
event = processor_block.call(event, hint)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
event
|
70
|
+
end
|
71
|
+
|
72
|
+
# Adds the breadcrumb to the scope's breadcrumbs buffer.
|
73
|
+
# @param breadcrumb [Breadcrumb]
|
74
|
+
# @return [void]
|
75
|
+
def add_breadcrumb(breadcrumb)
|
76
|
+
breadcrumbs.record(breadcrumb)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Clears the scope's breadcrumbs buffer
|
80
|
+
# @return [void]
|
81
|
+
def clear_breadcrumbs
|
82
|
+
set_new_breadcrumb_buffer
|
83
|
+
end
|
84
|
+
|
85
|
+
# @return [Scope]
|
86
|
+
def dup
|
87
|
+
copy = super
|
88
|
+
copy.breadcrumbs = breadcrumbs.dup
|
89
|
+
copy.contexts = contexts.deep_dup
|
90
|
+
copy.extra = extra.deep_dup
|
91
|
+
copy.tags = tags.deep_dup
|
92
|
+
copy.user = user.deep_dup
|
93
|
+
copy.transaction_names = transaction_names.dup
|
94
|
+
copy.transaction_sources = transaction_sources.dup
|
95
|
+
copy.fingerprint = fingerprint.deep_dup
|
96
|
+
copy.span = span.deep_dup
|
97
|
+
copy.session = session.deep_dup
|
98
|
+
copy
|
99
|
+
end
|
100
|
+
|
101
|
+
# Updates the scope's data from a given scope.
|
102
|
+
# @param scope [Scope]
|
103
|
+
# @return [void]
|
104
|
+
def update_from_scope(scope)
|
105
|
+
self.breadcrumbs = scope.breadcrumbs
|
106
|
+
self.contexts = scope.contexts
|
107
|
+
self.extra = scope.extra
|
108
|
+
self.tags = scope.tags
|
109
|
+
self.user = scope.user
|
110
|
+
self.transaction_names = scope.transaction_names
|
111
|
+
self.transaction_sources = scope.transaction_sources
|
112
|
+
self.fingerprint = scope.fingerprint
|
113
|
+
self.span = scope.span
|
114
|
+
end
|
115
|
+
|
116
|
+
# Updates the scope's data from the given options.
|
117
|
+
# @param contexts [Hash]
|
118
|
+
# @param extras [Hash]
|
119
|
+
# @param tags [Hash]
|
120
|
+
# @param user [Hash]
|
121
|
+
# @param level [String, Symbol]
|
122
|
+
# @param fingerprint [Array]
|
123
|
+
# @return [void]
|
124
|
+
def update_from_options(
|
125
|
+
contexts: nil,
|
126
|
+
extra: nil,
|
127
|
+
tags: nil,
|
128
|
+
user: nil,
|
129
|
+
level: nil,
|
130
|
+
fingerprint: nil
|
131
|
+
)
|
132
|
+
self.contexts.merge!(contexts) if contexts
|
133
|
+
self.extra.merge!(extra) if extra
|
134
|
+
self.tags.merge!(tags) if tags
|
135
|
+
self.user = user if user
|
136
|
+
self.level = level if level
|
137
|
+
self.fingerprint = fingerprint if fingerprint
|
138
|
+
end
|
139
|
+
|
140
|
+
# Sets the scope's rack_env attribute.
|
141
|
+
# @param env [Hash]
|
142
|
+
# @return [Hash]
|
143
|
+
def set_rack_env(env)
|
144
|
+
env = env || {}
|
145
|
+
@rack_env = env
|
146
|
+
end
|
147
|
+
|
148
|
+
# Sets the scope's span attribute.
|
149
|
+
# @param span [Span]
|
150
|
+
# @return [Span]
|
151
|
+
def set_span(span)
|
152
|
+
check_argument_type!(span, Span)
|
153
|
+
@span = span
|
154
|
+
end
|
155
|
+
|
156
|
+
# @!macro set_user
|
157
|
+
def set_user(user_hash)
|
158
|
+
check_argument_type!(user_hash, Hash)
|
159
|
+
@user = user_hash
|
160
|
+
end
|
161
|
+
|
162
|
+
# @!macro set_extras
|
163
|
+
def set_extras(extras_hash)
|
164
|
+
check_argument_type!(extras_hash, Hash)
|
165
|
+
@extra.merge!(extras_hash)
|
166
|
+
end
|
167
|
+
|
168
|
+
# Adds a new key-value pair to current extras.
|
169
|
+
# @param key [String, Symbol]
|
170
|
+
# @param value [Object]
|
171
|
+
# @return [Hash]
|
172
|
+
def set_extra(key, value)
|
173
|
+
set_extras(key => value)
|
174
|
+
end
|
175
|
+
|
176
|
+
# @!macro set_tags
|
177
|
+
def set_tags(tags_hash)
|
178
|
+
check_argument_type!(tags_hash, Hash)
|
179
|
+
@tags.merge!(tags_hash)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Adds a new key-value pair to current tags.
|
183
|
+
# @param key [String, Symbol]
|
184
|
+
# @param value [Object]
|
185
|
+
# @return [Hash]
|
186
|
+
def set_tag(key, value)
|
187
|
+
set_tags(key => value)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Updates the scope's contexts attribute by merging with the old value.
|
191
|
+
# @param contexts [Hash]
|
192
|
+
# @return [Hash]
|
193
|
+
def set_contexts(contexts_hash)
|
194
|
+
check_argument_type!(contexts_hash, Hash)
|
195
|
+
@contexts.merge!(contexts_hash) do |key, old, new|
|
196
|
+
old.merge(new)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# @!macro set_context
|
201
|
+
def set_context(key, value)
|
202
|
+
check_argument_type!(value, Hash)
|
203
|
+
set_contexts(key => value)
|
204
|
+
end
|
205
|
+
|
206
|
+
# Sets the scope's level attribute.
|
207
|
+
# @param level [String, Symbol]
|
208
|
+
# @return [void]
|
209
|
+
def set_level(level)
|
210
|
+
@level = level
|
211
|
+
end
|
212
|
+
|
213
|
+
# Appends a new transaction name to the scope.
|
214
|
+
# The "transaction" here does not refer to `Transaction` objects.
|
215
|
+
# @param transaction_name [String]
|
216
|
+
# @return [void]
|
217
|
+
def set_transaction_name(transaction_name, source: :custom)
|
218
|
+
@transaction_names << transaction_name
|
219
|
+
@transaction_sources << source
|
220
|
+
end
|
221
|
+
|
222
|
+
# Sets the currently active session on the scope.
|
223
|
+
# @param session [Session, nil]
|
224
|
+
# @return [void]
|
225
|
+
def set_session(session)
|
226
|
+
@session = session
|
227
|
+
end
|
228
|
+
|
229
|
+
# Returns current transaction name.
|
230
|
+
# The "transaction" here does not refer to `Transaction` objects.
|
231
|
+
# @return [String, nil]
|
232
|
+
def transaction_name
|
233
|
+
@transaction_names.last
|
234
|
+
end
|
235
|
+
|
236
|
+
# Returns current transaction source.
|
237
|
+
# The "transaction" here does not refer to `Transaction` objects.
|
238
|
+
# @return [String, nil]
|
239
|
+
def transaction_source
|
240
|
+
@transaction_sources.last
|
241
|
+
end
|
242
|
+
|
243
|
+
# Returns the associated Transaction object.
|
244
|
+
# @return [Transaction, nil]
|
245
|
+
def get_transaction
|
246
|
+
span.transaction if span
|
247
|
+
end
|
248
|
+
|
249
|
+
# Returns the associated Span object.
|
250
|
+
# @return [Span, nil]
|
251
|
+
def get_span
|
252
|
+
span
|
253
|
+
end
|
254
|
+
|
255
|
+
# Sets the scope's fingerprint attribute.
|
256
|
+
# @param fingerprint [Array]
|
257
|
+
# @return [Array]
|
258
|
+
def set_fingerprint(fingerprint)
|
259
|
+
check_argument_type!(fingerprint, Array)
|
260
|
+
|
261
|
+
@fingerprint = fingerprint
|
262
|
+
end
|
263
|
+
|
264
|
+
# Adds a new event processor [Proc] to the scope.
|
265
|
+
# @param block [Proc]
|
266
|
+
# @return [void]
|
267
|
+
def add_event_processor(&block)
|
268
|
+
@event_processors << block
|
269
|
+
end
|
270
|
+
|
271
|
+
protected
|
272
|
+
|
273
|
+
# for duplicating scopes internally
|
274
|
+
attr_writer(*ATTRIBUTES)
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
def set_default_value
|
279
|
+
@contexts = { :os => self.class.os_context, :runtime => self.class.runtime_context }
|
280
|
+
@extra = {}
|
281
|
+
@tags = {}
|
282
|
+
@user = {}
|
283
|
+
@level = :error
|
284
|
+
@fingerprint = []
|
285
|
+
@transaction_names = []
|
286
|
+
@transaction_sources = []
|
287
|
+
@event_processors = []
|
288
|
+
@rack_env = {}
|
289
|
+
@span = nil
|
290
|
+
@session = nil
|
291
|
+
set_new_breadcrumb_buffer
|
292
|
+
end
|
293
|
+
|
294
|
+
def set_new_breadcrumb_buffer
|
295
|
+
@breadcrumbs = BreadcrumbBuffer.new(@max_breadcrumbs)
|
296
|
+
end
|
297
|
+
|
298
|
+
class << self
|
299
|
+
# @return [Hash]
|
300
|
+
def os_context
|
301
|
+
@os_context ||=
|
302
|
+
begin
|
303
|
+
uname = Etc.uname
|
304
|
+
{
|
305
|
+
name: uname[:sysname] || RbConfig::CONFIG["host_os"],
|
306
|
+
version: uname[:version],
|
307
|
+
build: uname[:release],
|
308
|
+
kernel_version: uname[:version]
|
309
|
+
}
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# @return [Hash]
|
314
|
+
def runtime_context
|
315
|
+
@runtime_context ||= {
|
316
|
+
name: RbConfig::CONFIG["ruby_install_name"],
|
317
|
+
version: RUBY_DESCRIPTION || Sentry.sys_command("ruby -v")
|
318
|
+
}
|
319
|
+
end
|
320
|
+
|
321
|
+
# Returns the global event processors array.
|
322
|
+
# @return [Array<Proc>]
|
323
|
+
def global_event_processors
|
324
|
+
@global_event_processors ||= []
|
325
|
+
end
|
326
|
+
|
327
|
+
# Adds a new global event processor [Proc].
|
328
|
+
# Sometimes we need a global event processor without needing to configure scope.
|
329
|
+
# These run before scope event processors.
|
330
|
+
#
|
331
|
+
# @param block [Proc]
|
332
|
+
# @return [void]
|
333
|
+
def add_global_event_processor(&block)
|
334
|
+
global_event_processors << block
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
end
|
339
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
class Session
|
5
|
+
attr_reader :started, :status, :aggregation_key
|
6
|
+
|
7
|
+
# TODO-neel add :crashed after adding handled mechanism
|
8
|
+
STATUSES = %i(ok errored exited)
|
9
|
+
AGGREGATE_STATUSES = %i(errored exited)
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@started = Sentry.utc_now
|
13
|
+
@status = :ok
|
14
|
+
|
15
|
+
# truncate seconds from the timestamp since we only care about
|
16
|
+
# minute level granularity for aggregation
|
17
|
+
@aggregation_key = Time.utc(@started.year, @started.month, @started.day, @started.hour, @started.min)
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO-neel add :crashed after adding handled mechanism
|
21
|
+
def update_from_exception(_exception = nil)
|
22
|
+
@status = :errored
|
23
|
+
end
|
24
|
+
|
25
|
+
def close
|
26
|
+
@status = :exited if @status == :ok
|
27
|
+
end
|
28
|
+
|
29
|
+
def deep_dup
|
30
|
+
dup
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sentry
|
4
|
+
class SessionFlusher
|
5
|
+
include LoggingHelper
|
6
|
+
|
7
|
+
FLUSH_INTERVAL = 60
|
8
|
+
|
9
|
+
def initialize(configuration, client)
|
10
|
+
@thread = nil
|
11
|
+
@exited = false
|
12
|
+
@client = client
|
13
|
+
@pending_aggregates = {}
|
14
|
+
@release = configuration.release
|
15
|
+
@environment = configuration.environment
|
16
|
+
@logger = configuration.logger
|
17
|
+
|
18
|
+
log_debug("[Sessions] Sessions won't be captured without a valid release") unless @release
|
19
|
+
end
|
20
|
+
|
21
|
+
def flush
|
22
|
+
return if @pending_aggregates.empty?
|
23
|
+
envelope = pending_envelope
|
24
|
+
|
25
|
+
Sentry.background_worker.perform do
|
26
|
+
@client.transport.send_envelope(envelope)
|
27
|
+
end
|
28
|
+
|
29
|
+
@pending_aggregates = {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_session(session)
|
33
|
+
return if @exited
|
34
|
+
return unless @release
|
35
|
+
|
36
|
+
begin
|
37
|
+
ensure_thread
|
38
|
+
rescue ThreadError
|
39
|
+
log_debug("Session flusher thread creation failed")
|
40
|
+
@exited = true
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
return unless Session::AGGREGATE_STATUSES.include?(session.status)
|
45
|
+
@pending_aggregates[session.aggregation_key] ||= init_aggregates(session.aggregation_key)
|
46
|
+
@pending_aggregates[session.aggregation_key][session.status] += 1
|
47
|
+
end
|
48
|
+
|
49
|
+
def kill
|
50
|
+
log_debug("Killing session flusher")
|
51
|
+
|
52
|
+
@exited = true
|
53
|
+
@thread&.kill
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def init_aggregates(aggregation_key)
|
59
|
+
aggregates = { started: aggregation_key.iso8601 }
|
60
|
+
Session::AGGREGATE_STATUSES.each { |k| aggregates[k] = 0 }
|
61
|
+
aggregates
|
62
|
+
end
|
63
|
+
|
64
|
+
def pending_envelope
|
65
|
+
envelope = Envelope.new
|
66
|
+
|
67
|
+
header = { type: 'sessions' }
|
68
|
+
payload = { attrs: attrs, aggregates: @pending_aggregates.values }
|
69
|
+
|
70
|
+
envelope.add_item(header, payload)
|
71
|
+
envelope
|
72
|
+
end
|
73
|
+
|
74
|
+
def attrs
|
75
|
+
{ release: @release, environment: @environment }
|
76
|
+
end
|
77
|
+
|
78
|
+
def ensure_thread
|
79
|
+
return if @thread&.alive?
|
80
|
+
|
81
|
+
@thread = Thread.new do
|
82
|
+
loop do
|
83
|
+
sleep(FLUSH_INTERVAL)
|
84
|
+
flush
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
data/lib/sentry/span.rb
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
module Sentry
|
6
|
+
class Span
|
7
|
+
STATUS_MAP = {
|
8
|
+
400 => "invalid_argument",
|
9
|
+
401 => "unauthenticated",
|
10
|
+
403 => "permission_denied",
|
11
|
+
404 => "not_found",
|
12
|
+
409 => "already_exists",
|
13
|
+
429 => "resource_exhausted",
|
14
|
+
499 => "cancelled",
|
15
|
+
500 => "internal_error",
|
16
|
+
501 => "unimplemented",
|
17
|
+
503 => "unavailable",
|
18
|
+
504 => "deadline_exceeded"
|
19
|
+
}
|
20
|
+
|
21
|
+
# An uuid that can be used to identify a trace.
|
22
|
+
# @return [String]
|
23
|
+
attr_reader :trace_id
|
24
|
+
# An uuid that can be used to identify the span.
|
25
|
+
# @return [String]
|
26
|
+
attr_reader :span_id
|
27
|
+
# Span parent's span_id.
|
28
|
+
# @return [String]
|
29
|
+
attr_reader :parent_span_id
|
30
|
+
# Sampling result of the span.
|
31
|
+
# @return [Boolean, nil]
|
32
|
+
attr_reader :sampled
|
33
|
+
# Starting timestamp of the span.
|
34
|
+
# @return [Float]
|
35
|
+
attr_reader :start_timestamp
|
36
|
+
# Finishing timestamp of the span.
|
37
|
+
# @return [Float]
|
38
|
+
attr_reader :timestamp
|
39
|
+
# Span description
|
40
|
+
# @return [String]
|
41
|
+
attr_reader :description
|
42
|
+
# Span operation
|
43
|
+
# @return [String]
|
44
|
+
attr_reader :op
|
45
|
+
# Span status
|
46
|
+
# @return [String]
|
47
|
+
attr_reader :status
|
48
|
+
# Span tags
|
49
|
+
# @return [Hash]
|
50
|
+
attr_reader :tags
|
51
|
+
# Span data
|
52
|
+
# @return [Hash]
|
53
|
+
attr_reader :data
|
54
|
+
|
55
|
+
# The SpanRecorder the current span belongs to.
|
56
|
+
# SpanRecorder holds all spans under the same Transaction object (including the Transaction itself).
|
57
|
+
# @return [SpanRecorder]
|
58
|
+
attr_accessor :span_recorder
|
59
|
+
|
60
|
+
# The Transaction object the Span belongs to.
|
61
|
+
# Every span needs to be attached to a Transaction and their child spans will also inherit the same transaction.
|
62
|
+
# @return [Transaction]
|
63
|
+
attr_reader :transaction
|
64
|
+
|
65
|
+
def initialize(
|
66
|
+
transaction:,
|
67
|
+
description: nil,
|
68
|
+
op: nil,
|
69
|
+
status: nil,
|
70
|
+
trace_id: nil,
|
71
|
+
span_id: nil,
|
72
|
+
parent_span_id: nil,
|
73
|
+
sampled: nil,
|
74
|
+
start_timestamp: nil,
|
75
|
+
timestamp: nil
|
76
|
+
)
|
77
|
+
@trace_id = trace_id || SecureRandom.uuid.delete("-")
|
78
|
+
@span_id = span_id || SecureRandom.hex(8)
|
79
|
+
@parent_span_id = parent_span_id
|
80
|
+
@sampled = sampled
|
81
|
+
@start_timestamp = start_timestamp || Sentry.utc_now.to_f
|
82
|
+
@timestamp = timestamp
|
83
|
+
@description = description
|
84
|
+
@transaction = transaction
|
85
|
+
@op = op
|
86
|
+
@status = status
|
87
|
+
@data = {}
|
88
|
+
@tags = {}
|
89
|
+
end
|
90
|
+
|
91
|
+
# Finishes the span by adding a timestamp.
|
92
|
+
# @return [self]
|
93
|
+
def finish(end_timestamp: nil)
|
94
|
+
@timestamp = end_timestamp || @timestamp || Sentry.utc_now.to_f
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
# Generates a trace string that can be used to connect other transactions.
|
99
|
+
# @return [String]
|
100
|
+
def to_sentry_trace
|
101
|
+
sampled_flag = ""
|
102
|
+
sampled_flag = @sampled ? 1 : 0 unless @sampled.nil?
|
103
|
+
|
104
|
+
"#{@trace_id}-#{@span_id}-#{sampled_flag}"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Generates a W3C Baggage header string for distributed tracing
|
108
|
+
# from the incoming baggage stored on the transaction.
|
109
|
+
# @return [String, nil]
|
110
|
+
def to_baggage
|
111
|
+
transaction.get_baggage&.serialize
|
112
|
+
end
|
113
|
+
|
114
|
+
# @return [Hash]
|
115
|
+
def to_hash
|
116
|
+
{
|
117
|
+
trace_id: @trace_id,
|
118
|
+
span_id: @span_id,
|
119
|
+
parent_span_id: @parent_span_id,
|
120
|
+
start_timestamp: @start_timestamp,
|
121
|
+
timestamp: @timestamp,
|
122
|
+
description: @description,
|
123
|
+
op: @op,
|
124
|
+
status: @status,
|
125
|
+
tags: @tags,
|
126
|
+
data: @data
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns the span's context that can be used to embed in an Event.
|
131
|
+
# @return [Hash]
|
132
|
+
def get_trace_context
|
133
|
+
{
|
134
|
+
trace_id: @trace_id,
|
135
|
+
span_id: @span_id,
|
136
|
+
parent_span_id: @parent_span_id,
|
137
|
+
description: @description,
|
138
|
+
op: @op,
|
139
|
+
status: @status
|
140
|
+
}
|
141
|
+
end
|
142
|
+
|
143
|
+
# Starts a child span with given attributes.
|
144
|
+
# @param attributes [Hash] the attributes for the child span.
|
145
|
+
def start_child(**attributes)
|
146
|
+
attributes = attributes.dup.merge(transaction: @transaction, trace_id: @trace_id, parent_span_id: @span_id, sampled: @sampled)
|
147
|
+
new_span = Span.new(**attributes)
|
148
|
+
new_span.span_recorder = span_recorder
|
149
|
+
|
150
|
+
if span_recorder
|
151
|
+
span_recorder.add(new_span)
|
152
|
+
end
|
153
|
+
|
154
|
+
new_span
|
155
|
+
end
|
156
|
+
|
157
|
+
# Starts a child span, yield it to the given block, and then finish the span after the block is executed.
|
158
|
+
# @example
|
159
|
+
# span.with_child_span do |child_span|
|
160
|
+
# # things happen here will be recorded in a child span
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# @param attributes [Hash] the attributes for the child span.
|
164
|
+
# @param block [Proc] the action to be recorded in the child span.
|
165
|
+
# @yieldparam child_span [Span]
|
166
|
+
def with_child_span(**attributes, &block)
|
167
|
+
child_span = start_child(**attributes)
|
168
|
+
|
169
|
+
yield(child_span)
|
170
|
+
|
171
|
+
child_span.finish
|
172
|
+
rescue
|
173
|
+
child_span.set_http_status(500)
|
174
|
+
child_span.finish
|
175
|
+
raise
|
176
|
+
end
|
177
|
+
|
178
|
+
def deep_dup
|
179
|
+
dup
|
180
|
+
end
|
181
|
+
|
182
|
+
# Sets the span's operation.
|
183
|
+
# @param op [String] operation of the span.
|
184
|
+
def set_op(op)
|
185
|
+
@op = op
|
186
|
+
end
|
187
|
+
|
188
|
+
# Sets the span's description.
|
189
|
+
# @param description [String] description of the span.
|
190
|
+
def set_description(description)
|
191
|
+
@description = description
|
192
|
+
end
|
193
|
+
|
194
|
+
|
195
|
+
# Sets the span's status.
|
196
|
+
# @param satus [String] status of the span.
|
197
|
+
def set_status(status)
|
198
|
+
@status = status
|
199
|
+
end
|
200
|
+
|
201
|
+
# Sets the span's finish timestamp.
|
202
|
+
# @param timestamp [Float] finished time in float format (most precise).
|
203
|
+
def set_timestamp(timestamp)
|
204
|
+
@timestamp = timestamp
|
205
|
+
end
|
206
|
+
|
207
|
+
# Sets the span's status with given http status code.
|
208
|
+
# @param status_code [String] example: "500".
|
209
|
+
def set_http_status(status_code)
|
210
|
+
status_code = status_code.to_i
|
211
|
+
set_data("status_code", status_code)
|
212
|
+
|
213
|
+
status =
|
214
|
+
if status_code >= 200 && status_code < 299
|
215
|
+
"ok"
|
216
|
+
else
|
217
|
+
STATUS_MAP[status_code]
|
218
|
+
end
|
219
|
+
set_status(status)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Inserts a key-value pair to the span's data payload.
|
223
|
+
# @param key [String, Symbol]
|
224
|
+
# @param value [Object]
|
225
|
+
def set_data(key, value)
|
226
|
+
@data[key] = value
|
227
|
+
end
|
228
|
+
|
229
|
+
# Sets a tag to the span.
|
230
|
+
# @param key [String, Symbol]
|
231
|
+
# @param value [String]
|
232
|
+
def set_tag(key, value)
|
233
|
+
@tags[key] = value
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|