sentry-ruby 5.4.2 → 5.10.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.
@@ -15,7 +15,15 @@ module Sentry
15
15
 
16
16
  def initialize(exception:, stacktrace: nil)
17
17
  @type = exception.class.to_s
18
- @value = (exception.message || "").byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
18
+ exception_message =
19
+ if exception.respond_to?(:detailed_message)
20
+ exception.detailed_message(highlight: false)
21
+ else
22
+ exception.message || ""
23
+ end
24
+
25
+ @value = exception_message.byteslice(0..Event::MAX_MESSAGE_SIZE_IN_BYTES)
26
+
19
27
  @module = exception.class.to_s.split('::')[0...-1].join('::')
20
28
  @thread_id = Thread.current.object_id
21
29
  @stacktrace = stacktrace
@@ -26,14 +26,24 @@ module Sentry
26
26
  #
27
27
  # So we're only instrumenting request when `Net::HTTP` is already started
28
28
  def request(req, body = nil, &block)
29
- return super unless started?
30
-
31
- sentry_span = start_sentry_span
32
- set_sentry_trace_header(req, sentry_span)
33
-
34
- super.tap do |res|
35
- record_sentry_breadcrumb(req, res)
36
- record_sentry_span(req, res, sentry_span)
29
+ return super unless started? && Sentry.initialized?
30
+ return super if from_sentry_sdk?
31
+
32
+ Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |sentry_span|
33
+ set_sentry_trace_header(req, sentry_span)
34
+
35
+ super.tap do |res|
36
+ record_sentry_breadcrumb(req, res)
37
+
38
+ if sentry_span
39
+ request_info = extract_request_info(req)
40
+ sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
41
+ sentry_span.set_data('url', request_info[:url])
42
+ sentry_span.set_data('http.method', request_info[:method])
43
+ sentry_span.set_data('http.query', request_info[:query]) if request_info[:query]
44
+ sentry_span.set_data('status', res.code.to_i)
45
+ end
46
+ end
37
47
  end
38
48
  end
39
49
 
@@ -42,13 +52,17 @@ module Sentry
42
52
  def set_sentry_trace_header(req, sentry_span)
43
53
  return unless sentry_span
44
54
 
45
- trace = Sentry.get_current_client.generate_sentry_trace(sentry_span)
55
+ client = Sentry.get_current_client
56
+
57
+ trace = client.generate_sentry_trace(sentry_span)
46
58
  req[SENTRY_TRACE_HEADER_NAME] = trace if trace
59
+
60
+ baggage = client.generate_baggage(sentry_span)
61
+ req[BAGGAGE_HEADER_NAME] = baggage if baggage && !baggage.empty?
47
62
  end
48
63
 
49
64
  def record_sentry_breadcrumb(req, res)
50
65
  return unless Sentry.initialized? && Sentry.configuration.breadcrumbs_logger.include?(:http_logger)
51
- return if from_sentry_sdk?
52
66
 
53
67
  request_info = extract_request_info(req)
54
68
 
@@ -64,29 +78,6 @@ module Sentry
64
78
  Sentry.add_breadcrumb(crumb)
65
79
  end
66
80
 
67
- def record_sentry_span(req, res, sentry_span)
68
- return unless Sentry.initialized? && sentry_span
69
-
70
- request_info = extract_request_info(req)
71
- sentry_span.set_description("#{request_info[:method]} #{request_info[:url]}")
72
- sentry_span.set_data(:status, res.code.to_i)
73
- finish_sentry_span(sentry_span)
74
- end
75
-
76
- def start_sentry_span
77
- return unless Sentry.initialized? && span = Sentry.get_current_scope.get_span
78
- return if from_sentry_sdk?
79
- return if span.sampled == false
80
-
81
- span.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
82
- end
83
-
84
- def finish_sentry_span(sentry_span)
85
- return unless Sentry.initialized? && sentry_span
86
-
87
- sentry_span.set_timestamp(Sentry.utc_now.to_f)
88
- end
89
-
90
81
  def from_sentry_sdk?
91
82
  dsn = Sentry.configuration.dsn
92
83
  dsn && dsn.host == self.address
@@ -99,7 +90,7 @@ module Sentry
99
90
  result = { method: req.method, url: url }
100
91
 
101
92
  if Sentry.configuration.send_default_pii
102
- result[:url] = result[:url] + "?#{uri.query}"
93
+ result[:query] = uri.query
103
94
  result[:body] = req.body
104
95
  end
105
96
 
@@ -109,7 +100,4 @@ module Sentry
109
100
  end
110
101
  end
111
102
 
112
- Sentry.register_patch do
113
- patch = Sentry::Net::HTTP
114
- Net::HTTP.send(:prepend, patch) unless Net::HTTP.ancestors.include?(patch)
115
- end
103
+ Sentry.register_patch(Sentry::Net::HTTP, Net::HTTP)
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Sentry
6
+ class Profiler
7
+ VERSION = '1'
8
+ PLATFORM = 'ruby'
9
+ # 101 Hz in microseconds
10
+ DEFAULT_INTERVAL = 1e6 / 101
11
+ MICRO_TO_NANO_SECONDS = 1e3
12
+
13
+ attr_reader :sampled, :started, :event_id
14
+
15
+ def initialize(configuration)
16
+ @event_id = SecureRandom.uuid.delete('-')
17
+ @started = false
18
+ @sampled = nil
19
+
20
+ @profiling_enabled = defined?(StackProf) && configuration.profiling_enabled?
21
+ @profiles_sample_rate = configuration.profiles_sample_rate
22
+ @project_root = configuration.project_root
23
+ @app_dirs_pattern = configuration.app_dirs_pattern || Backtrace::APP_DIRS_PATTERN
24
+ @in_app_pattern = Regexp.new("^(#{@project_root}/)?#{@app_dirs_pattern}")
25
+ end
26
+
27
+ def start
28
+ return unless @sampled
29
+
30
+ @started = StackProf.start(interval: DEFAULT_INTERVAL,
31
+ mode: :wall,
32
+ raw: true,
33
+ aggregate: false)
34
+
35
+ @started ? log('Started') : log('Not started since running elsewhere')
36
+ end
37
+
38
+ def stop
39
+ return unless @sampled
40
+ return unless @started
41
+
42
+ StackProf.stop
43
+ log('Stopped')
44
+ end
45
+
46
+ # Sets initial sampling decision of the profile.
47
+ # @return [void]
48
+ def set_initial_sample_decision(transaction_sampled)
49
+ unless @profiling_enabled
50
+ @sampled = false
51
+ return
52
+ end
53
+
54
+ unless transaction_sampled
55
+ @sampled = false
56
+ log('Discarding profile because transaction not sampled')
57
+ return
58
+ end
59
+
60
+ case @profiles_sample_rate
61
+ when 0.0
62
+ @sampled = false
63
+ log('Discarding profile because sample_rate is 0')
64
+ return
65
+ when 1.0
66
+ @sampled = true
67
+ return
68
+ else
69
+ @sampled = Random.rand < @profiles_sample_rate
70
+ end
71
+
72
+ log('Discarding profile due to sampling decision') unless @sampled
73
+ end
74
+
75
+ def to_hash
76
+ return {} unless @sampled
77
+ return {} unless @started
78
+
79
+ results = StackProf.results
80
+ return {} unless results
81
+ return {} if results.empty?
82
+ return {} if results[:samples] == 0
83
+ return {} unless results[:raw]
84
+
85
+ frame_map = {}
86
+
87
+ frames = results[:frames].to_enum.with_index.map do |frame, idx|
88
+ frame_id, frame_data = frame
89
+
90
+ # need to map over stackprof frame ids to ours
91
+ frame_map[frame_id] = idx
92
+
93
+ file_path = frame_data[:file]
94
+ in_app = in_app?(file_path)
95
+ filename = compute_filename(file_path, in_app)
96
+ function, mod = split_module(frame_data[:name])
97
+
98
+ frame_hash = {
99
+ abs_path: file_path,
100
+ function: function,
101
+ filename: filename,
102
+ in_app: in_app
103
+ }
104
+
105
+ frame_hash[:module] = mod if mod
106
+ frame_hash[:lineno] = frame_data[:line] if frame_data[:line]
107
+
108
+ frame_hash
109
+ end
110
+
111
+ idx = 0
112
+ stacks = []
113
+ num_seen = []
114
+
115
+ # extract stacks from raw
116
+ # raw is a single array of [.., len_stack, *stack_frames(len_stack), num_stack_seen , ..]
117
+ while (len = results[:raw][idx])
118
+ idx += 1
119
+
120
+ # our call graph is reversed
121
+ stack = results[:raw].slice(idx, len).map { |id| frame_map[id] }.compact.reverse
122
+ stacks << stack
123
+
124
+ num_seen << results[:raw][idx + len]
125
+ idx += len + 1
126
+
127
+ log('Unknown frame in stack') if stack.size != len
128
+ end
129
+
130
+ idx = 0
131
+ elapsed_since_start_ns = 0
132
+ samples = []
133
+
134
+ num_seen.each_with_index do |n, i|
135
+ n.times do
136
+ # stackprof deltas are in microseconds
137
+ delta = results[:raw_timestamp_deltas][idx]
138
+ elapsed_since_start_ns += (delta * MICRO_TO_NANO_SECONDS).to_i
139
+ idx += 1
140
+
141
+ # Not sure why but some deltas are very small like 0/1 values,
142
+ # they pollute our flamegraph so just ignore them for now.
143
+ # Open issue at https://github.com/tmm1/stackprof/issues/201
144
+ next if delta < 10
145
+
146
+ samples << {
147
+ stack_id: i,
148
+ # TODO-neel-profiler we need to patch rb_profile_frames and write our own C extension to enable threading info.
149
+ # Till then, on multi-threaded servers like puma, we will get frames from other active threads when the one
150
+ # we're profiling is idle/sleeping/waiting for IO etc.
151
+ # https://bugs.ruby-lang.org/issues/10602
152
+ thread_id: '0',
153
+ elapsed_since_start_ns: elapsed_since_start_ns.to_s
154
+ }
155
+ end
156
+ end
157
+
158
+ log('Some samples thrown away') if samples.size != results[:samples]
159
+
160
+ if samples.size <= 2
161
+ log('Not enough samples, discarding profiler')
162
+ return {}
163
+ end
164
+
165
+ profile = {
166
+ frames: frames,
167
+ stacks: stacks,
168
+ samples: samples
169
+ }
170
+
171
+ {
172
+ event_id: @event_id,
173
+ platform: PLATFORM,
174
+ version: VERSION,
175
+ profile: profile
176
+ }
177
+ end
178
+
179
+ private
180
+
181
+ def log(message)
182
+ Sentry.logger.debug(LOGGER_PROGNAME) { "[Profiler] #{message}" }
183
+ end
184
+
185
+ def in_app?(abs_path)
186
+ abs_path.match?(@in_app_pattern)
187
+ end
188
+
189
+ # copied from stacktrace.rb since I don't want to touch existing code
190
+ # TODO-neel-profiler try to fetch this from stackprof once we patch
191
+ # the native extension
192
+ def compute_filename(abs_path, in_app)
193
+ return nil if abs_path.nil?
194
+
195
+ under_project_root = @project_root && abs_path.start_with?(@project_root)
196
+
197
+ prefix =
198
+ if under_project_root && in_app
199
+ @project_root
200
+ else
201
+ longest_load_path = $LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
202
+
203
+ if under_project_root
204
+ longest_load_path || @project_root
205
+ else
206
+ longest_load_path
207
+ end
208
+ end
209
+
210
+ prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
211
+ end
212
+
213
+ def split_module(name)
214
+ # last module plus class/instance method
215
+ i = name.rindex('::')
216
+ function = i ? name[(i + 2)..-1] : name
217
+ mod = i ? name[0...i] : nil
218
+
219
+ [function, mod]
220
+ end
221
+ end
222
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sentry
4
+ module Puma
5
+ module Server
6
+ def lowlevel_error(e, env, status=500)
7
+ result = super
8
+
9
+ begin
10
+ Sentry.capture_exception(e) do |scope|
11
+ scope.set_rack_env(env)
12
+ end
13
+ rescue
14
+ # if anything happens, we don't want to break the app
15
+ end
16
+
17
+ result
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ if defined?(Puma::Server)
24
+ Sentry.register_patch(Sentry::Puma::Server, Puma::Server)
25
+ end
@@ -18,7 +18,7 @@ module Sentry
18
18
  Sentry.with_scope do |scope|
19
19
  Sentry.with_session_tracking do
20
20
  scope.clear_breadcrumbs
21
- scope.set_transaction_name(env["PATH_INFO"]) if env["PATH_INFO"]
21
+ scope.set_transaction_name(env["PATH_INFO"], source: :url) if env["PATH_INFO"]
22
22
  scope.set_rack_env(env)
23
23
 
24
24
  transaction = start_transaction(env, scope)
@@ -52,7 +52,7 @@ module Sentry
52
52
  end
53
53
 
54
54
  def transaction_op
55
- "rack.request".freeze
55
+ "http.server".freeze
56
56
  end
57
57
 
58
58
  def capture_exception(exception, env)
@@ -63,8 +63,10 @@ module Sentry
63
63
 
64
64
  def start_transaction(env, scope)
65
65
  sentry_trace = env["HTTP_SENTRY_TRACE"]
66
- options = { name: scope.transaction_name, op: transaction_op }
67
- transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, **options) if sentry_trace
66
+ baggage = env["HTTP_BAGGAGE"]
67
+
68
+ options = { name: scope.transaction_name, source: scope.transaction_source, op: transaction_op }
69
+ transaction = Sentry::Transaction.from_sentry_trace(sentry_trace, baggage: baggage, **options) if sentry_trace
68
70
  Sentry.start_transaction(transaction: transaction, custom_sampling_context: { env: env }, **options)
69
71
  end
70
72
 
data/lib/sentry/rake.rb CHANGED
@@ -10,7 +10,7 @@ module Sentry
10
10
  def display_error_message(ex)
11
11
  Sentry.capture_exception(ex) do |scope|
12
12
  task_name = top_level_tasks.join(' ')
13
- scope.set_transaction_name(task_name)
13
+ scope.set_transaction_name(task_name, source: :task)
14
14
  scope.set_tag("rake_task", task_name)
15
15
  end if Sentry.initialized? && !Sentry.configuration.skip_rake_integration
16
16
 
data/lib/sentry/redis.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  module Sentry
4
4
  # @api private
5
5
  class Redis
6
- OP_NAME = "db.redis.command"
6
+ OP_NAME = "db.redis"
7
7
  LOGGER_NAME = :redis_logger
8
8
 
9
9
  def initialize(commands, host, port, db)
@@ -13,9 +13,14 @@ module Sentry
13
13
  def instrument
14
14
  return yield unless Sentry.initialized?
15
15
 
16
- record_span do
16
+ Sentry.with_child_span(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f) do |span|
17
17
  yield.tap do
18
18
  record_breadcrumb
19
+
20
+ if span
21
+ span.set_description(commands_description)
22
+ span.set_data(:server, server_description)
23
+ end
19
24
  end
20
25
  end
21
26
  end
@@ -24,19 +29,8 @@ module Sentry
24
29
 
25
30
  attr_reader :commands, :host, :port, :db
26
31
 
27
- def record_span
28
- return yield unless (transaction = Sentry.get_current_scope.get_transaction) && transaction.sampled
29
-
30
- sentry_span = transaction.start_child(op: OP_NAME, start_timestamp: Sentry.utc_now.to_f)
31
-
32
- yield.tap do
33
- sentry_span.set_description(commands_description)
34
- sentry_span.set_data(:server, server_description)
35
- sentry_span.set_timestamp(Sentry.utc_now.to_f)
36
- end
37
- end
38
-
39
32
  def record_breadcrumb
33
+ return unless Sentry.initialized?
40
34
  return unless Sentry.configuration.breadcrumbs_logger.include?(LOGGER_NAME)
41
35
 
42
36
  Sentry.add_breadcrumb(
@@ -61,10 +55,16 @@ module Sentry
61
55
  def parsed_commands
62
56
  commands.map do |statement|
63
57
  command, key, *arguments = statement
58
+ command_set = { command: command.to_s.upcase }
59
+ command_set[:key] = key if Utils::EncodingHelper.valid_utf_8?(key)
64
60
 
65
- { command: command.to_s.upcase, key: key }.tap do |command_set|
66
- command_set[:arguments] = arguments.join(" ") if Sentry.configuration.send_default_pii
61
+ if Sentry.configuration.send_default_pii
62
+ command_set[:arguments] = arguments
63
+ .select { |a| Utils::EncodingHelper.valid_utf_8?(a) }
64
+ .join(" ")
67
65
  end
66
+
67
+ command_set
68
68
  end
69
69
  end
70
70
 
@@ -72,19 +72,32 @@ module Sentry
72
72
  "#{host}:#{port}/#{db}"
73
73
  end
74
74
 
75
- module Client
75
+ module OldClientPatch
76
76
  def logging(commands, &block)
77
- Sentry::Redis.new(commands, host, port, db).instrument do
78
- super
79
- end
77
+ Sentry::Redis.new(commands, host, port, db).instrument { super }
78
+ end
79
+ end
80
+
81
+ module GlobalRedisInstrumentation
82
+ def call(command, redis_config)
83
+ Sentry::Redis
84
+ .new([command], redis_config.host, redis_config.port, redis_config.db)
85
+ .instrument { super }
86
+ end
87
+
88
+ def call_pipelined(commands, redis_config)
89
+ Sentry::Redis
90
+ .new(commands, redis_config.host, redis_config.port, redis_config.db)
91
+ .instrument { super }
80
92
  end
81
93
  end
82
94
  end
83
95
  end
84
96
 
85
97
  if defined?(::Redis::Client)
86
- Sentry.register_patch do
87
- patch = Sentry::Redis::Client
88
- Redis::Client.prepend(patch) unless Redis::Client.ancestors.include?(patch)
98
+ if Gem::Version.new(::Redis::VERSION) < Gem::Version.new("5.0")
99
+ Sentry.register_patch(Sentry::Redis::OldClientPatch, ::Redis::Client)
100
+ elsif defined?(RedisClient)
101
+ RedisClient.register(Sentry::Redis::GlobalRedisInstrumentation)
89
102
  end
90
103
  end
data/lib/sentry/scope.rb CHANGED
@@ -7,7 +7,21 @@ module Sentry
7
7
  class Scope
8
8
  include ArgumentCheckingHelper
9
9
 
10
- ATTRIBUTES = [:transaction_names, :contexts, :extra, :tags, :user, :level, :breadcrumbs, :fingerprint, :event_processors, :rack_env, :span, :session]
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
+ ]
11
25
 
12
26
  attr_reader(*ATTRIBUTES)
13
27
 
@@ -33,6 +47,7 @@ module Sentry
33
47
  event.extra = extra.merge(event.extra)
34
48
  event.contexts = contexts.merge(event.contexts)
35
49
  event.transaction = transaction_name if transaction_name
50
+ event.transaction_info = { source: transaction_source } if transaction_source
36
51
 
37
52
  if span
38
53
  event.contexts[:trace] = span.get_trace_context
@@ -43,8 +58,10 @@ module Sentry
43
58
  event.breadcrumbs = breadcrumbs
44
59
  event.rack_env = rack_env if rack_env
45
60
 
46
- unless @event_processors.empty?
47
- @event_processors.each do |processor_block|
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|
48
65
  event = processor_block.call(event, hint)
49
66
  end
50
67
  end
@@ -73,7 +90,8 @@ module Sentry
73
90
  copy.extra = extra.deep_dup
74
91
  copy.tags = tags.deep_dup
75
92
  copy.user = user.deep_dup
76
- copy.transaction_names = transaction_names.deep_dup
93
+ copy.transaction_names = transaction_names.dup
94
+ copy.transaction_sources = transaction_sources.dup
77
95
  copy.fingerprint = fingerprint.deep_dup
78
96
  copy.span = span.deep_dup
79
97
  copy.session = session.deep_dup
@@ -90,6 +108,7 @@ module Sentry
90
108
  self.tags = scope.tags
91
109
  self.user = scope.user
92
110
  self.transaction_names = scope.transaction_names
111
+ self.transaction_sources = scope.transaction_sources
93
112
  self.fingerprint = scope.fingerprint
94
113
  self.span = scope.span
95
114
  end
@@ -173,6 +192,10 @@ module Sentry
173
192
  # @return [Hash]
174
193
  def set_contexts(contexts_hash)
175
194
  check_argument_type!(contexts_hash, Hash)
195
+ contexts_hash.values.each do |val|
196
+ check_argument_type!(val, Hash)
197
+ end
198
+
176
199
  @contexts.merge!(contexts_hash) do |key, old, new|
177
200
  old.merge(new)
178
201
  end
@@ -195,8 +218,9 @@ module Sentry
195
218
  # The "transaction" here does not refer to `Transaction` objects.
196
219
  # @param transaction_name [String]
197
220
  # @return [void]
198
- def set_transaction_name(transaction_name)
221
+ def set_transaction_name(transaction_name, source: :custom)
199
222
  @transaction_names << transaction_name
223
+ @transaction_sources << source
200
224
  end
201
225
 
202
226
  # Sets the currently active session on the scope.
@@ -213,6 +237,13 @@ module Sentry
213
237
  @transaction_names.last
214
238
  end
215
239
 
240
+ # Returns current transaction source.
241
+ # The "transaction" here does not refer to `Transaction` objects.
242
+ # @return [String, nil]
243
+ def transaction_source
244
+ @transaction_sources.last
245
+ end
246
+
216
247
  # Returns the associated Transaction object.
217
248
  # @return [Transaction, nil]
218
249
  def get_transaction
@@ -256,6 +287,7 @@ module Sentry
256
287
  @level = :error
257
288
  @fingerprint = []
258
289
  @transaction_names = []
290
+ @transaction_sources = []
259
291
  @event_processors = []
260
292
  @rack_env = {}
261
293
  @span = nil
@@ -277,7 +309,8 @@ module Sentry
277
309
  name: uname[:sysname] || RbConfig::CONFIG["host_os"],
278
310
  version: uname[:version],
279
311
  build: uname[:release],
280
- kernel_version: uname[:version]
312
+ kernel_version: uname[:version],
313
+ machine: uname[:machine]
281
314
  }
282
315
  end
283
316
  end
@@ -289,6 +322,22 @@ module Sentry
289
322
  version: RUBY_DESCRIPTION || Sentry.sys_command("ruby -v")
290
323
  }
291
324
  end
325
+
326
+ # Returns the global event processors array.
327
+ # @return [Array<Proc>]
328
+ def global_event_processors
329
+ @global_event_processors ||= []
330
+ end
331
+
332
+ # Adds a new global event processor [Proc].
333
+ # Sometimes we need a global event processor without needing to configure scope.
334
+ # These run before scope event processors.
335
+ #
336
+ # @param block [Proc]
337
+ # @return [void]
338
+ def add_global_event_processor(&block)
339
+ global_event_processors << block
340
+ end
292
341
  end
293
342
 
294
343
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Sentry
4
4
  class Session
5
- attr_reader :started, :status
5
+ attr_reader :started, :status, :aggregation_key
6
6
 
7
7
  # TODO-neel add :crashed after adding handled mechanism
8
8
  STATUSES = %i(ok errored exited)
@@ -11,6 +11,10 @@ module Sentry
11
11
  def initialize
12
12
  @started = Sentry.utc_now
13
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)
14
18
  end
15
19
 
16
20
  # TODO-neel add :crashed after adding handled mechanism
@@ -22,12 +26,6 @@ module Sentry
22
26
  @status = :exited if @status == :ok
23
27
  end
24
28
 
25
- # truncate seconds from the timestamp since we only care about
26
- # minute level granularity for aggregation
27
- def aggregation_key
28
- Time.utc(started.year, started.month, started.day, started.hour, started.min)
29
- end
30
-
31
29
  def deep_dup
32
30
  dup
33
31
  end