bugsnag 6.14.0 → 6.15.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 +17 -0
- data/VERSION +1 -1
- data/features/fixtures/docker-compose.yml +5 -1
- data/features/fixtures/plain/app/report_modification/initiators/handled_on_error.rb +10 -0
- data/features/fixtures/plain/app/report_modification/initiators/unhandled_on_error.rb +11 -0
- data/features/fixtures/plain/app/stack_frame_modification/initiators/handled_on_error.rb +29 -0
- data/features/fixtures/plain/app/stack_frame_modification/initiators/unhandled_on_error.rb +26 -0
- data/features/fixtures/rails3/app/config/initializers/bugsnag.rb +8 -0
- data/features/fixtures/rails4/app/config/initializers/bugsnag.rb +8 -0
- data/features/fixtures/rails5/app/config/initializers/bugsnag.rb +8 -0
- data/features/fixtures/rails6/app/config/initializers/bugsnag.rb +8 -0
- data/features/plain_features/add_tab.feature +7 -1
- data/features/plain_features/ignore_report.feature +2 -0
- data/features/plain_features/report_api_key.feature +3 -1
- data/features/plain_features/report_severity.feature +2 -0
- data/features/plain_features/report_stack_frames.feature +4 -0
- data/features/plain_features/report_user.feature +7 -1
- data/features/rails_features/on_error.feature +29 -0
- data/lib/bugsnag.rb +35 -0
- data/lib/bugsnag/code_extractor.rb +137 -0
- data/lib/bugsnag/configuration.rb +27 -0
- data/lib/bugsnag/middleware_stack.rb +38 -3
- data/lib/bugsnag/on_error_callbacks.rb +33 -0
- data/lib/bugsnag/report.rb +1 -1
- data/lib/bugsnag/session_tracker.rb +3 -3
- data/lib/bugsnag/stacktrace.rb +25 -68
- data/spec/code_extractor_spec.rb +129 -0
- data/spec/fixtures/crashes/file1.rb +29 -0
- data/spec/fixtures/crashes/file2.rb +25 -0
- data/spec/fixtures/crashes/file_with_long_lines.rb +7 -0
- data/spec/fixtures/crashes/functions.rb +29 -0
- data/spec/fixtures/crashes/short_file.rb +2 -0
- data/spec/on_error_spec.rb +332 -0
- data/spec/report_spec.rb +7 -4
- data/spec/spec_helper.rb +8 -0
- data/spec/stacktrace_spec.rb +276 -30
- metadata +15 -2
@@ -0,0 +1,25 @@
|
|
1
|
+
module File2
|
2
|
+
def self.foo2
|
3
|
+
File1.bar1
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.bar2
|
7
|
+
File1.baz1
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.baz2
|
11
|
+
raise 'uh oh'
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.abc2
|
15
|
+
puts 'abc'
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.abcdef2
|
19
|
+
puts 'abcdef2'
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.abcdefghi2
|
23
|
+
puts 'abcdefghi2'
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# rubocop:disable Layout/LineLength
|
2
|
+
def a_super_long_function_name_that_would_be_really_impractical_to_use_but_luckily_this_is_just_for_a_test_to_prove_we_can_handle_really_long_lines_of_code_that_go_over_200_characters_and_some_more_padding
|
3
|
+
puts 'This is a shorter string'
|
4
|
+
puts 'A more realistic example of when a line would be really long is long strings such as this one, which extends over the 200 character limit by containing a lot of excess words for padding its length so that it is super long'
|
5
|
+
puts 'and another shorter string for comparison'
|
6
|
+
end
|
7
|
+
# rubocop:enable Layout/LineLength
|
@@ -0,0 +1,29 @@
|
|
1
|
+
def foo
|
2
|
+
bar
|
3
|
+
end
|
4
|
+
|
5
|
+
def bar
|
6
|
+
baz
|
7
|
+
end
|
8
|
+
|
9
|
+
def baz
|
10
|
+
xyz
|
11
|
+
end
|
12
|
+
|
13
|
+
def xyz
|
14
|
+
raise 'uh oh'
|
15
|
+
end
|
16
|
+
|
17
|
+
def abc
|
18
|
+
puts 'abc'
|
19
|
+
end
|
20
|
+
|
21
|
+
def abcdef
|
22
|
+
puts 'abcdef'
|
23
|
+
end
|
24
|
+
|
25
|
+
def abcdefghi
|
26
|
+
puts 'abcdefghi'
|
27
|
+
end
|
28
|
+
|
29
|
+
foo
|
@@ -0,0 +1,332 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "on_error callbacks" do
|
4
|
+
it "runs callbacks on notify" do
|
5
|
+
callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
|
6
|
+
callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
|
7
|
+
|
8
|
+
Bugsnag.add_on_error(callback1)
|
9
|
+
Bugsnag.add_on_error(callback2)
|
10
|
+
|
11
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
12
|
+
|
13
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
14
|
+
event = get_event_from_payload(payload)
|
15
|
+
|
16
|
+
expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
|
17
|
+
expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
|
18
|
+
end)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "can add callbacks in a configure block" do
|
22
|
+
callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
|
23
|
+
callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
|
24
|
+
|
25
|
+
Bugsnag.configure do |config|
|
26
|
+
config.add_on_error(callback1)
|
27
|
+
config.add_on_error(callback2)
|
28
|
+
end
|
29
|
+
|
30
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
31
|
+
|
32
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
33
|
+
event = get_event_from_payload(payload)
|
34
|
+
|
35
|
+
expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
|
36
|
+
expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
|
37
|
+
end)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can remove an already registered callback" do
|
41
|
+
callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
|
42
|
+
callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
|
43
|
+
|
44
|
+
Bugsnag.add_on_error(callback1)
|
45
|
+
Bugsnag.add_on_error(callback2)
|
46
|
+
|
47
|
+
Bugsnag.remove_on_error(callback1)
|
48
|
+
|
49
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
50
|
+
|
51
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
52
|
+
event = get_event_from_payload(payload)
|
53
|
+
|
54
|
+
expect(event["metaData"]["important"]).to be_nil
|
55
|
+
expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
|
56
|
+
end)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "can remove all registered callbacks" do
|
60
|
+
callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
|
61
|
+
callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
|
62
|
+
|
63
|
+
Bugsnag.add_on_error(callback1)
|
64
|
+
Bugsnag.add_on_error(callback2)
|
65
|
+
|
66
|
+
Bugsnag.remove_on_error(callback2)
|
67
|
+
Bugsnag.remove_on_error(callback1)
|
68
|
+
|
69
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
70
|
+
|
71
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
72
|
+
event = get_event_from_payload(payload)
|
73
|
+
|
74
|
+
expect(event["metaData"]["important"]).to be_nil
|
75
|
+
expect(event["metaData"]["significant"]).to be_nil
|
76
|
+
end)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "does not remove an identical callback if it is not the same Proc" do
|
80
|
+
callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
|
81
|
+
callback1_duplicate = proc {|report| report.add_tab(:important, { hello: "world" }) }
|
82
|
+
|
83
|
+
Bugsnag.add_on_error(callback1)
|
84
|
+
Bugsnag.remove_on_error(callback1_duplicate)
|
85
|
+
|
86
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
87
|
+
|
88
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
89
|
+
event = get_event_from_payload(payload)
|
90
|
+
|
91
|
+
expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
|
92
|
+
end)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "can re-add callbacks that have previously been removed" do
|
96
|
+
callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
|
97
|
+
callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
|
98
|
+
|
99
|
+
Bugsnag.add_on_error(callback1)
|
100
|
+
Bugsnag.add_on_error(callback2)
|
101
|
+
|
102
|
+
Bugsnag.remove_on_error(callback1)
|
103
|
+
|
104
|
+
Bugsnag.add_on_error(callback1)
|
105
|
+
|
106
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
107
|
+
|
108
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
109
|
+
event = get_event_from_payload(payload)
|
110
|
+
|
111
|
+
expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
|
112
|
+
expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
|
113
|
+
end)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "will only add a callback once" do
|
117
|
+
called_count = 0
|
118
|
+
|
119
|
+
callback = proc do |report|
|
120
|
+
called_count += 1
|
121
|
+
|
122
|
+
report.add_tab(:important, { called: called_count })
|
123
|
+
end
|
124
|
+
|
125
|
+
1.upto(10) do |i|
|
126
|
+
Bugsnag.add_on_error(callback)
|
127
|
+
end
|
128
|
+
|
129
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
130
|
+
|
131
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
132
|
+
event = get_event_from_payload(payload)
|
133
|
+
|
134
|
+
expect(called_count).to be(1)
|
135
|
+
expect(event["metaData"]["important"]).to eq({ "called" => 1 })
|
136
|
+
end)
|
137
|
+
end
|
138
|
+
|
139
|
+
it "will ignore the report and stop calling callbacks if one returns false" do
|
140
|
+
logger = spy('logger')
|
141
|
+
Bugsnag.configuration.logger = logger
|
142
|
+
|
143
|
+
called_count = 0
|
144
|
+
|
145
|
+
callback1 = proc { called_count += 1 }
|
146
|
+
callback2 = proc { called_count += 1 }
|
147
|
+
callback3 = proc { false }
|
148
|
+
callback4 = proc { called_count += 1 }
|
149
|
+
callback5 = proc { called_count += 1 }
|
150
|
+
|
151
|
+
Bugsnag.add_on_error(callback1)
|
152
|
+
Bugsnag.add_on_error(callback2)
|
153
|
+
Bugsnag.add_on_error(callback3)
|
154
|
+
Bugsnag.add_on_error(callback4)
|
155
|
+
Bugsnag.add_on_error(callback5)
|
156
|
+
|
157
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
158
|
+
|
159
|
+
expect(Bugsnag).not_to have_sent_notification
|
160
|
+
expect(called_count).to be(2)
|
161
|
+
|
162
|
+
expect(logger).to have_received(:debug).with("[Bugsnag]") do |&block|
|
163
|
+
expect(block.call).to eq("Not notifying RuntimeError due to ignore being signified in user provided middleware")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it "callbacks are called in the same order they are added (FIFO)" do
|
168
|
+
callback1 = proc do |report|
|
169
|
+
expect(report.meta_data[:important]).to be_nil
|
170
|
+
|
171
|
+
report.add_tab(:important, { magic_number: 9 })
|
172
|
+
end
|
173
|
+
|
174
|
+
callback2 = proc do |report|
|
175
|
+
expect(report.meta_data[:important]).to eq({ magic_number: 9 })
|
176
|
+
|
177
|
+
report.add_tab(:important, { magic_number: 99 })
|
178
|
+
end
|
179
|
+
|
180
|
+
callback3 = proc do |report|
|
181
|
+
expect(report.meta_data[:important]).to eq({ magic_number: 99 })
|
182
|
+
|
183
|
+
report.add_tab(:important, { magic_number: 999 })
|
184
|
+
end
|
185
|
+
|
186
|
+
Bugsnag.add_on_error(callback1)
|
187
|
+
Bugsnag.add_on_error(callback2)
|
188
|
+
Bugsnag.add_on_error(callback3)
|
189
|
+
|
190
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
191
|
+
|
192
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
193
|
+
event = get_event_from_payload(payload)
|
194
|
+
|
195
|
+
expect(event["metaData"]["important"]).to eq({ "magic_number" => 999 })
|
196
|
+
end)
|
197
|
+
end
|
198
|
+
|
199
|
+
it "callbacks continue being called after a callback raises" do
|
200
|
+
logger = spy('logger')
|
201
|
+
Bugsnag.configuration.logger = logger
|
202
|
+
|
203
|
+
callback1 = proc {|report| report.add_tab(:important, { a: "b" }) }
|
204
|
+
callback2 = proc {|_report| raise "bad things" }
|
205
|
+
callback3 = proc {|report| report.add_tab(:important, { c: "d" }) }
|
206
|
+
|
207
|
+
Bugsnag.add_on_error(callback1)
|
208
|
+
Bugsnag.add_on_error(callback2)
|
209
|
+
Bugsnag.add_on_error(callback3)
|
210
|
+
|
211
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
212
|
+
|
213
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
214
|
+
event = get_event_from_payload(payload)
|
215
|
+
|
216
|
+
expect(event["metaData"]["important"]).to eq({ "a" => "b", "c" => "d" })
|
217
|
+
end)
|
218
|
+
|
219
|
+
message_index = 0
|
220
|
+
expected_messages = [
|
221
|
+
/^Error occurred in on_error callback: 'bad things'$/,
|
222
|
+
/^on_error callback stacktrace:/
|
223
|
+
]
|
224
|
+
|
225
|
+
expect(logger).to have_received(:warn).with("[Bugsnag]").twice do |&block|
|
226
|
+
expect(block.call).to match(expected_messages[message_index])
|
227
|
+
message_index += 1
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
it "runs callbacks even if no other middleware is registered" do
|
232
|
+
# Reset the middleware stack so any default middleware are removed
|
233
|
+
Bugsnag.configuration.middleware = Bugsnag::MiddlewareStack.new
|
234
|
+
|
235
|
+
callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
|
236
|
+
callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
|
237
|
+
|
238
|
+
Bugsnag.add_on_error(callback1)
|
239
|
+
Bugsnag.add_on_error(callback2)
|
240
|
+
|
241
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
242
|
+
|
243
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
244
|
+
event = get_event_from_payload(payload)
|
245
|
+
|
246
|
+
expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
|
247
|
+
expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
|
248
|
+
end)
|
249
|
+
end
|
250
|
+
|
251
|
+
describe "using callbacks across threads" do
|
252
|
+
it "runs callbacks that are added in different threads" do
|
253
|
+
callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
|
254
|
+
callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
|
255
|
+
callback3 = proc {|report| report.add_tab(:crucial, { magic_number: 999 }) }
|
256
|
+
|
257
|
+
Bugsnag.add_on_error(callback1)
|
258
|
+
|
259
|
+
threads = [
|
260
|
+
Thread.new { Bugsnag.add_on_error(callback2) },
|
261
|
+
Thread.new { Bugsnag.add_on_error(callback3) }
|
262
|
+
]
|
263
|
+
|
264
|
+
threads.each(&:join)
|
265
|
+
|
266
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
267
|
+
|
268
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
269
|
+
event = get_event_from_payload(payload)
|
270
|
+
|
271
|
+
expect(event["metaData"]["important"]).to eq({ "hello" => "world" })
|
272
|
+
expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
|
273
|
+
expect(event["metaData"]["crucial"]).to eq({ "magic_number" => 999 })
|
274
|
+
end)
|
275
|
+
end
|
276
|
+
|
277
|
+
it "can remove callbacks that are added in different threads" do
|
278
|
+
callback1 = proc {|report| report.add_tab(:important, { hello: "world" }) }
|
279
|
+
callback2 = proc {|report| report.add_tab(:significant, { hey: "earth" }) }
|
280
|
+
|
281
|
+
# We need to create & join these one at a time so that callback1 has
|
282
|
+
# definitely been added before it is removed, otherwise this test will fail
|
283
|
+
# at random
|
284
|
+
Thread.new { Bugsnag.add_on_error(callback1) }.join
|
285
|
+
Thread.new { Bugsnag.remove_on_error(callback1) }.join
|
286
|
+
Thread.new { Bugsnag.add_on_error(callback2) }.join
|
287
|
+
|
288
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
289
|
+
|
290
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
291
|
+
event = get_event_from_payload(payload)
|
292
|
+
|
293
|
+
expect(event["metaData"]["important"]).to be_nil
|
294
|
+
expect(event["metaData"]["significant"]).to eq({ "hey" => "earth" })
|
295
|
+
end)
|
296
|
+
end
|
297
|
+
|
298
|
+
it "callbacks are called in FIFO order when added in separate threads" do
|
299
|
+
callback1 = proc do |report|
|
300
|
+
expect(report.meta_data[:important]).to be_nil
|
301
|
+
|
302
|
+
report.add_tab(:important, { magic_number: 9 })
|
303
|
+
end
|
304
|
+
|
305
|
+
callback2 = proc do |report|
|
306
|
+
expect(report.meta_data[:important]).to eq({ magic_number: 9 })
|
307
|
+
|
308
|
+
report.add_tab(:important, { magic_number: 99 })
|
309
|
+
end
|
310
|
+
|
311
|
+
callback3 = proc do |report|
|
312
|
+
expect(report.meta_data[:important]).to eq({ magic_number: 99 })
|
313
|
+
|
314
|
+
report.add_tab(:important, { magic_number: 999 })
|
315
|
+
end
|
316
|
+
|
317
|
+
# As above, we need to create & join these one at a time so that each
|
318
|
+
# callback is added in sequence
|
319
|
+
Thread.new { Bugsnag.add_on_error(callback1) }.join
|
320
|
+
Thread.new { Bugsnag.add_on_error(callback2) }.join
|
321
|
+
Thread.new { Bugsnag.add_on_error(callback3) }.join
|
322
|
+
|
323
|
+
Bugsnag.notify(RuntimeError.new("Oh no!"))
|
324
|
+
|
325
|
+
expect(Bugsnag).to(have_sent_notification do |payload, _headers|
|
326
|
+
event = get_event_from_payload(payload)
|
327
|
+
|
328
|
+
expect(event["metaData"]["important"]).to eq({ "magic_number" => 999 })
|
329
|
+
end)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
data/spec/report_spec.rb
CHANGED
@@ -1373,27 +1373,30 @@ describe Bugsnag::Report do
|
|
1373
1373
|
notify_test_exception
|
1374
1374
|
expect(Bugsnag).to have_sent_notification{ |payload, headers|
|
1375
1375
|
exception = get_exception_from_payload(payload)
|
1376
|
+
|
1376
1377
|
bugsnag_count = 0
|
1378
|
+
|
1377
1379
|
exception["stacktrace"].each do |frame|
|
1378
1380
|
if /.*lib\/bugsnag.*\.rb/.match(frame["file"])
|
1379
1381
|
bugsnag_count += 1
|
1380
1382
|
expect(frame["inProject"]).to be_nil
|
1381
1383
|
end
|
1382
1384
|
end
|
1383
|
-
|
1385
|
+
|
1386
|
+
# 6 is used here as the called bugsnag frames for a `notify` call should be:
|
1384
1387
|
# - Bugsnag.notify
|
1385
1388
|
# - Report.new
|
1386
1389
|
# - Report.initialize
|
1387
1390
|
# - Report.generate_exceptions_list
|
1388
1391
|
# - Report.generate_exceptions_list | raw_exceptions.map
|
1389
1392
|
# - Report.generate_exceptions_list | raw_exceptions.map | block
|
1390
|
-
#
|
1391
|
-
# However, JRUBY does not include the two `new` frames, resulting in 5 bugsnag frames
|
1393
|
+
# However, JRUBY does not include the `Report.new` frame, resulting in 5 bugsnag frames
|
1392
1394
|
if defined?(JRUBY_VERSION)
|
1393
1395
|
frame_count = 5
|
1394
1396
|
else
|
1395
|
-
frame_count =
|
1397
|
+
frame_count = 6
|
1396
1398
|
end
|
1399
|
+
|
1397
1400
|
expect(bugsnag_count).to equal frame_count
|
1398
1401
|
}
|
1399
1402
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -30,6 +30,14 @@ def get_exception_from_payload(payload)
|
|
30
30
|
event["exceptions"].last
|
31
31
|
end
|
32
32
|
|
33
|
+
def get_code_from_payload(payload, index = 0)
|
34
|
+
exception = get_exception_from_payload(payload)
|
35
|
+
|
36
|
+
expect(exception["stacktrace"].size).to be > index
|
37
|
+
|
38
|
+
exception["stacktrace"][index]["code"]
|
39
|
+
end
|
40
|
+
|
33
41
|
def notify_test_exception(*args)
|
34
42
|
Bugsnag.notify(RuntimeError.new("test message"), *args)
|
35
43
|
end
|