sentry-ruby 5.4.2 → 5.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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