airbrake-ruby 4.8.0 → 4.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.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby.rb +84 -5
- data/lib/airbrake-ruby/async_sender.rb +3 -3
- data/lib/airbrake-ruby/backtrace.rb +2 -2
- data/lib/airbrake-ruby/code_hunk.rb +1 -1
- data/lib/airbrake-ruby/config.rb +1 -1
- data/lib/airbrake-ruby/config/validator.rb +3 -3
- data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
- data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +2 -2
- data/lib/airbrake-ruby/filters/keys_filter.rb +1 -1
- data/lib/airbrake-ruby/filters/sql_filter.rb +3 -3
- data/lib/airbrake-ruby/filters/thread_filter.rb +1 -1
- data/lib/airbrake-ruby/grouppable.rb +12 -0
- data/lib/airbrake-ruby/inspectable.rb +2 -2
- data/lib/airbrake-ruby/mergeable.rb +12 -0
- data/lib/airbrake-ruby/notice.rb +7 -7
- data/lib/airbrake-ruby/notice_notifier.rb +1 -1
- data/lib/airbrake-ruby/performance_breakdown.rb +2 -1
- data/lib/airbrake-ruby/performance_notifier.rb +42 -21
- data/lib/airbrake-ruby/query.rb +3 -5
- data/lib/airbrake-ruby/queue.rb +52 -0
- data/lib/airbrake-ruby/request.rb +3 -5
- data/lib/airbrake-ruby/stat.rb +1 -1
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/airbrake_spec.rb +135 -45
- data/spec/async_sender_spec.rb +4 -4
- data/spec/backtrace_spec.rb +18 -18
- data/spec/code_hunk_spec.rb +9 -9
- data/spec/config/validator_spec.rb +5 -5
- data/spec/config_spec.rb +5 -5
- data/spec/deploy_notifier_spec.rb +2 -2
- data/spec/filter_chain_spec.rb +1 -1
- data/spec/filters/dependency_filter_spec.rb +1 -1
- data/spec/filters/gem_root_filter_spec.rb +5 -5
- data/spec/filters/git_last_checkout_filter_spec.rb +1 -1
- data/spec/filters/git_repository_filter.rb +1 -1
- data/spec/filters/git_revision_filter_spec.rb +10 -10
- data/spec/filters/keys_blacklist_spec.rb +22 -22
- data/spec/filters/keys_whitelist_spec.rb +21 -21
- data/spec/filters/root_directory_filter_spec.rb +5 -5
- data/spec/filters/sql_filter_spec.rb +53 -53
- data/spec/filters/system_exit_filter_spec.rb +1 -1
- data/spec/filters/thread_filter_spec.rb +28 -28
- data/spec/fixtures/project_root/code.rb +9 -9
- data/spec/notice_notifier/options_spec.rb +12 -12
- data/spec/notice_notifier_spec.rb +17 -17
- data/spec/notice_spec.rb +5 -5
- data/spec/performance_notifier_spec.rb +171 -60
- data/spec/query_spec.rb +1 -1
- data/spec/queue_spec.rb +11 -0
- data/spec/request_spec.rb +1 -1
- data/spec/response_spec.rb +8 -8
- data/spec/spec_helper.rb +2 -2
- data/spec/stat_spec.rb +2 -2
- data/spec/sync_sender_spec.rb +12 -12
- data/spec/tdigest_spec.rb +6 -6
- data/spec/thread_pool_spec.rb +5 -5
- data/spec/timed_trace_spec.rb +1 -1
- data/spec/truncator_spec.rb +12 -12
- 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: f7d68430114320da9313ddd9a3dbfd05d3610701bcbb4156c5a36afba295de5f
|
4
|
+
data.tar.gz: ecd8983a7b37efb831faa440f294db56c8a59de9b9a05c1d014493d3c3da1e94
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 95a4ae5fe3b38a8aaaaec88a65524144f509058091d414ec1d10e7c741767c00a59c5d046ba9c4861c960bde14555d58d937e3aafee983f9fdfeb1476b5a1f77
|
7
|
+
data.tar.gz: adba5aecb6240193c212a6f7a4011adde53fcaf87238060d4474eaca012daf66b59d0361b4c982fa5b103a28ea7f4fcc70a5bd75f57f908a08ccdbc2062a56b3
|
data/lib/airbrake-ruby.rb
CHANGED
@@ -9,6 +9,8 @@ require 'time'
|
|
9
9
|
require 'airbrake-ruby/version'
|
10
10
|
require 'airbrake-ruby/loggable'
|
11
11
|
require 'airbrake-ruby/stashable'
|
12
|
+
require 'airbrake-ruby/mergeable'
|
13
|
+
require 'airbrake-ruby/grouppable'
|
12
14
|
require 'airbrake-ruby/config'
|
13
15
|
require 'airbrake-ruby/config/validator'
|
14
16
|
require 'airbrake-ruby/promise'
|
@@ -52,6 +54,7 @@ require 'airbrake-ruby/performance_breakdown'
|
|
52
54
|
require 'airbrake-ruby/benchmark'
|
53
55
|
require 'airbrake-ruby/monotonic_time'
|
54
56
|
require 'airbrake-ruby/timed_trace'
|
57
|
+
require 'airbrake-ruby/queue'
|
55
58
|
|
56
59
|
# Airbrake is a thin wrapper around instances of the notifier classes (such as
|
57
60
|
# notice, performance & deploy notifiers). It creates a way to access them via a
|
@@ -69,6 +72,7 @@ require 'airbrake-ruby/timed_trace'
|
|
69
72
|
#
|
70
73
|
# @since v1.0.0
|
71
74
|
# @api public
|
75
|
+
# rubocop:disable Metrics/ModuleLength
|
72
76
|
module Airbrake
|
73
77
|
# The general error that this library uses when it wants to raise.
|
74
78
|
Error = Class.new(StandardError)
|
@@ -131,7 +135,7 @@ module Airbrake
|
|
131
135
|
# @return [Boolean] true if the notifier was configured, false otherwise
|
132
136
|
# @since v2.3.0
|
133
137
|
def configured?
|
134
|
-
notice_notifier.configured?
|
138
|
+
@notice_notifier && @notice_notifier.configured?
|
135
139
|
end
|
136
140
|
|
137
141
|
# Sends an exception to Airbrake asynchronously.
|
@@ -251,10 +255,17 @@ module Airbrake
|
|
251
255
|
# Airbrake.notify('App crashed!') #=> raises Airbrake::Error
|
252
256
|
#
|
253
257
|
# @return [void]
|
258
|
+
# rubocop:disable Style/GuardClause, Style/IfUnlessModifier
|
254
259
|
def close
|
255
|
-
notice_notifier
|
256
|
-
|
260
|
+
if defined?(@notice_notifier) && @notice_notifier
|
261
|
+
@notice_notifier.close
|
262
|
+
end
|
263
|
+
|
264
|
+
if defined?(@performance_notifier) && @performance_notifier
|
265
|
+
@performance_notifier.close
|
266
|
+
end
|
257
267
|
end
|
268
|
+
# rubocop:enable Style/GuardClause, Style/IfUnlessModifier
|
258
269
|
|
259
270
|
# Pings the Airbrake Deploy API endpoint about the occurred deploy.
|
260
271
|
#
|
@@ -360,6 +371,17 @@ module Airbrake
|
|
360
371
|
performance_notifier.notify(request)
|
361
372
|
end
|
362
373
|
|
374
|
+
# Synchronously Increments request statistics of a certain +route+ that was
|
375
|
+
# invoked on +start_time+ and ended on +end_time+ with +method+, and
|
376
|
+
# returned +status_code+.
|
377
|
+
# @since v4.10.0
|
378
|
+
# @see .notify_request
|
379
|
+
def notify_request_sync(request_info, stash = {})
|
380
|
+
request = Request.new(request_info)
|
381
|
+
request.stash.merge!(stash)
|
382
|
+
performance_notifier.notify_sync(request)
|
383
|
+
end
|
384
|
+
|
363
385
|
# Increments SQL statistics of a certain +query+ that was invoked on
|
364
386
|
# +start_time+ and finished on +end_time+. When +method+ and +route+ are
|
365
387
|
# provided, the query is grouped by these parameters.
|
@@ -395,6 +417,17 @@ module Airbrake
|
|
395
417
|
performance_notifier.notify(query)
|
396
418
|
end
|
397
419
|
|
420
|
+
# Synchronously increments SQL statistics of a certain +query+ that was
|
421
|
+
# invoked on +start_time+ and finished on +end_time+. When +method+ and
|
422
|
+
# +route+ are provided, the query is grouped by these parameters.
|
423
|
+
# @since v4.10.0
|
424
|
+
# @see .notify_query
|
425
|
+
def notify_query_sync(query_info, stash = {})
|
426
|
+
query = Query.new(query_info)
|
427
|
+
query.stash.merge!(stash)
|
428
|
+
performance_notifier.notify_sync(query)
|
429
|
+
end
|
430
|
+
|
398
431
|
# Increments performance breakdown statistics of a certain route.
|
399
432
|
#
|
400
433
|
# @example
|
@@ -423,6 +456,51 @@ module Airbrake
|
|
423
456
|
performance_notifier.notify(performance_breakdown)
|
424
457
|
end
|
425
458
|
|
459
|
+
# Increments performance breakdown statistics of a certain route
|
460
|
+
# synchronously.
|
461
|
+
# @since v4.10.0
|
462
|
+
# @see .notify_performance_breakdown
|
463
|
+
def notify_performance_breakdown_sync(breakdown_info, stash = {})
|
464
|
+
performance_breakdown = PerformanceBreakdown.new(breakdown_info)
|
465
|
+
performance_breakdown.stash.merge!(stash)
|
466
|
+
performance_notifier.notify_sync(performance_breakdown)
|
467
|
+
end
|
468
|
+
|
469
|
+
# Increments statistics of a certain queue (worker).
|
470
|
+
#
|
471
|
+
# @example
|
472
|
+
# Airbrake.notify_queue(
|
473
|
+
# queue: 'emails',
|
474
|
+
# error_count: 1,
|
475
|
+
# groups: { redis: 24.0, sql: 0.4 } # ms
|
476
|
+
# )
|
477
|
+
#
|
478
|
+
# @param [Hash{Symbol=>Object}] queue_info
|
479
|
+
# @option queue_info [String] :queue The name of the queue/worker
|
480
|
+
# @option queue_info [Integer] :error_count How many times this worker
|
481
|
+
# failed
|
482
|
+
# @option queue_info [Array<Hash{Symbol=>Float}>] :groups Where the job
|
483
|
+
# spent its time
|
484
|
+
# @param [Hash] stash What needs to be appended to the stash, so it's
|
485
|
+
# available in filters
|
486
|
+
# @return [void]
|
487
|
+
# @since v4.9.0
|
488
|
+
# @see .notify_queue_sync
|
489
|
+
def notify_queue(queue_info, stash = {})
|
490
|
+
queue = Queue.new(queue_info)
|
491
|
+
queue.stash.merge!(stash)
|
492
|
+
performance_notifier.notify(queue)
|
493
|
+
end
|
494
|
+
|
495
|
+
# Increments statistics of a certain queue (worker) synchronously.
|
496
|
+
# @since v4.10.0
|
497
|
+
# @see .notify_queue
|
498
|
+
def notify_queue_sync(queue_info, stash = {})
|
499
|
+
queue = Queue.new(queue_info)
|
500
|
+
queue.stash.merge!(stash)
|
501
|
+
performance_notifier.notify_sync(queue)
|
502
|
+
end
|
503
|
+
|
426
504
|
# Runs a callback before {.notify_request} or {.notify_query} kicks in. This
|
427
505
|
# is useful if you want to ignore specific resources or filter the data the
|
428
506
|
# resource contains.
|
@@ -480,7 +558,7 @@ module Airbrake
|
|
480
558
|
# @return [void]
|
481
559
|
# @since v4.2.2
|
482
560
|
def reset
|
483
|
-
close
|
561
|
+
close
|
484
562
|
|
485
563
|
self.performance_notifier = PerformanceNotifier.new
|
486
564
|
self.notice_notifier = NoticeNotifier.new
|
@@ -506,10 +584,11 @@ module Airbrake
|
|
506
584
|
Airbrake::Filters::RootDirectoryFilter,
|
507
585
|
Airbrake::Filters::GitRevisionFilter,
|
508
586
|
Airbrake::Filters::GitRepositoryFilter,
|
509
|
-
Airbrake::Filters::GitLastCheckoutFilter
|
587
|
+
Airbrake::Filters::GitLastCheckoutFilter,
|
510
588
|
].each do |filter|
|
511
589
|
notice_notifier.add_filter(filter.new(config.root_directory))
|
512
590
|
end
|
513
591
|
end
|
514
592
|
end
|
515
593
|
end
|
594
|
+
# rubocop:enable Metrics/ModuleLength
|
@@ -54,7 +54,7 @@ module Airbrake
|
|
54
54
|
ThreadPool.new(
|
55
55
|
worker_size: @config.workers,
|
56
56
|
queue_size: @config.queue_size,
|
57
|
-
block: proc { |args| sender.send(*args) }
|
57
|
+
block: proc { |args| sender.send(*args) },
|
58
58
|
)
|
59
59
|
end
|
60
60
|
end
|
@@ -71,8 +71,8 @@ module Airbrake
|
|
71
71
|
message: error[:message],
|
72
72
|
backtrace: error[:backtrace].map do |line|
|
73
73
|
"#{line[:file]}:#{line[:line]} in `#{line[:function]}'"
|
74
|
-
end.join("\n")
|
75
|
-
)
|
74
|
+
end.join("\n"),
|
75
|
+
),
|
76
76
|
)
|
77
77
|
promise.reject("AsyncSender has reached its capacity of #{@config.queue_size}")
|
78
78
|
end
|
@@ -147,13 +147,13 @@ module Airbrake
|
|
147
147
|
return {
|
148
148
|
file: match[:file],
|
149
149
|
line: (Integer(match[:line]) if match[:line]),
|
150
|
-
function: match[:function]
|
150
|
+
function: match[:function],
|
151
151
|
}
|
152
152
|
end
|
153
153
|
|
154
154
|
logger.error(
|
155
155
|
"can't parse '#{stackframe}' (please file an issue so we can fix " \
|
156
|
-
"it: https://github.com/airbrake/airbrake-ruby/issues/new)"
|
156
|
+
"it: https://github.com/airbrake/airbrake-ruby/issues/new)",
|
157
157
|
)
|
158
158
|
{ file: nil, line: nil, function: stackframe }
|
159
159
|
end
|
@@ -30,7 +30,7 @@ module Airbrake
|
|
30
30
|
Airbrake::FileCache[file] ||= File.foreach(file)
|
31
31
|
rescue StandardError => ex
|
32
32
|
logger.error(
|
33
|
-
"#{self.class.name}: can't read code hunk for #{file}: #{ex}"
|
33
|
+
"#{self.class.name}: can't read code hunk for #{file}: #{ex}",
|
34
34
|
)
|
35
35
|
nil
|
36
36
|
end
|
data/lib/airbrake-ruby/config.rb
CHANGED
@@ -29,7 +29,7 @@ module Airbrake
|
|
29
29
|
return promise.reject(
|
30
30
|
"the 'environment' option must be configured " \
|
31
31
|
"with a Symbol (or String), but '#{config.environment.class}' was " \
|
32
|
-
"provided: #{config.environment}"
|
32
|
+
"provided: #{config.environment}",
|
33
33
|
)
|
34
34
|
end
|
35
35
|
|
@@ -46,7 +46,7 @@ module Airbrake
|
|
46
46
|
|
47
47
|
if ignored_environment?(config)
|
48
48
|
return promise.reject(
|
49
|
-
"current environment '#{config.environment}' is ignored"
|
49
|
+
"current environment '#{config.environment}' is ignored",
|
50
50
|
)
|
51
51
|
end
|
52
52
|
|
@@ -74,7 +74,7 @@ module Airbrake
|
|
74
74
|
if config.ignore_environments.any? && config.environment.nil?
|
75
75
|
config.logger.warn(
|
76
76
|
"#{LOG_LABEL} the 'environment' option is not set, " \
|
77
|
-
"'ignore_environments' has no effect"
|
77
|
+
"'ignore_environments' has no effect",
|
78
78
|
)
|
79
79
|
end
|
80
80
|
|
@@ -22,13 +22,13 @@ module Airbrake
|
|
22
22
|
attributes = exception.to_airbrake
|
23
23
|
rescue StandardError => ex
|
24
24
|
logger.error(
|
25
|
-
"#{LOG_LABEL} #{exception.class}#to_airbrake failed. #{ex.class}: #{ex}"
|
25
|
+
"#{LOG_LABEL} #{exception.class}#to_airbrake failed. #{ex.class}: #{ex}",
|
26
26
|
)
|
27
27
|
end
|
28
28
|
|
29
29
|
unless attributes.is_a?(Hash)
|
30
30
|
logger.error(
|
31
|
-
"#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}"
|
31
|
+
"#{LOG_LABEL} #{self.class}: wanted Hash, got #{attributes.class}",
|
32
32
|
)
|
33
33
|
return
|
34
34
|
end
|
@@ -52,7 +52,7 @@ module Airbrake
|
|
52
52
|
parts = line.chomp.split("\t").first.split(' ')
|
53
53
|
if parts.size < MIN_HEAD_COLS
|
54
54
|
logger.error(
|
55
|
-
"#{LOG_LABEL} Airbrake::#{self.class.name}: can't parse line: #{line}"
|
55
|
+
"#{LOG_LABEL} Airbrake::#{self.class.name}: can't parse line: #{line}",
|
56
56
|
)
|
57
57
|
return
|
58
58
|
end
|
@@ -62,7 +62,7 @@ module Airbrake
|
|
62
62
|
username: author[0..1].join(' '),
|
63
63
|
email: parts[-3][1..-2],
|
64
64
|
revision: parts[1],
|
65
|
-
time: timestamp(parts[-2].to_i)
|
65
|
+
time: timestamp(parts[-2].to_i),
|
66
66
|
}
|
67
67
|
end
|
68
68
|
# rubocop:enable Metrics/AbcSize
|
@@ -64,7 +64,7 @@ module Airbrake
|
|
64
64
|
cassandra: %i[
|
65
65
|
single_quotes uuids numeric_literals boolean_literals
|
66
66
|
hexadecimal_literals comments multi_line_comments
|
67
|
-
].freeze
|
67
|
+
].freeze,
|
68
68
|
}.freeze
|
69
69
|
|
70
70
|
# @return [Hash{Symbol=>Regexp}] a set of regexps to check for unmatches
|
@@ -76,7 +76,7 @@ module Airbrake
|
|
76
76
|
sqlite: %r{'|/\*|\*/},
|
77
77
|
cassandra: %r{'|/\*|\*/},
|
78
78
|
oracle: %r{'|/\*|\*/},
|
79
|
-
oracle_enhanced: %r{'|/\*|\*/}
|
79
|
+
oracle_enhanced: %r{'|/\*|\*/},
|
80
80
|
}.freeze
|
81
81
|
|
82
82
|
# @return [Array<Regexp>] the list of queries to be ignored
|
@@ -89,7 +89,7 @@ module Airbrake
|
|
89
89
|
/FROM pg_attribute/i,
|
90
90
|
/FROM pg_index/i,
|
91
91
|
/FROM pg_class/i,
|
92
|
-
/FROM pg_type/i
|
92
|
+
/FROM pg_type/i,
|
93
93
|
].freeze
|
94
94
|
|
95
95
|
def initialize(dialect)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Airbrake
|
2
|
+
# Grouppable adds the `#groups` method, so that we don't need to define it in
|
3
|
+
# all of performance models every time we add a model without groups.
|
4
|
+
#
|
5
|
+
# @since 4.9.0
|
6
|
+
# @api private
|
7
|
+
module Grouppable
|
8
|
+
def groups
|
9
|
+
{}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -21,7 +21,7 @@ module Airbrake
|
|
21
21
|
project_id: @config.project_id,
|
22
22
|
project_key: @config.project_key,
|
23
23
|
host: @config.host,
|
24
|
-
filter_chain: @filter_chain.inspect
|
24
|
+
filter_chain: @filter_chain.inspect,
|
25
25
|
)
|
26
26
|
end
|
27
27
|
|
@@ -30,7 +30,7 @@ module Airbrake
|
|
30
30
|
q.text("#<#{self.class}:0x#{(object_id << 1).to_s(16).rjust(16, '0')} ")
|
31
31
|
q.text(
|
32
32
|
"project_id=\"#{@config.project_id}\" project_key=\"#{@config.project_key}\" " \
|
33
|
-
"host=\"#{@config.host}\" filter_chain="
|
33
|
+
"host=\"#{@config.host}\" filter_chain=",
|
34
34
|
)
|
35
35
|
q.pp(@filter_chain)
|
36
36
|
q.text('>')
|
data/lib/airbrake-ruby/notice.rb
CHANGED
@@ -8,7 +8,7 @@ module Airbrake
|
|
8
8
|
NOTIFIER = {
|
9
9
|
name: 'airbrake-ruby'.freeze,
|
10
10
|
version: Airbrake::AIRBRAKE_RUBY_VERSION,
|
11
|
-
url: 'https://github.com/airbrake/airbrake-ruby'.freeze
|
11
|
+
url: 'https://github.com/airbrake/airbrake-ruby'.freeze,
|
12
12
|
}.freeze
|
13
13
|
|
14
14
|
# @return [Hash{Symbol=>String,Hash}] the information to be displayed in the
|
@@ -16,7 +16,7 @@ module Airbrake
|
|
16
16
|
CONTEXT = {
|
17
17
|
os: RUBY_PLATFORM,
|
18
18
|
language: "#{RUBY_ENGINE}/#{RUBY_VERSION}".freeze,
|
19
|
-
notifier: NOTIFIER
|
19
|
+
notifier: NOTIFIER,
|
20
20
|
}.freeze
|
21
21
|
|
22
22
|
# @return [Integer] the maxium size of the JSON payload in bytes
|
@@ -32,7 +32,7 @@ module Airbrake
|
|
32
32
|
IOError,
|
33
33
|
NotImplementedError,
|
34
34
|
JSON::GeneratorError,
|
35
|
-
Encoding::UndefinedConversionError
|
35
|
+
Encoding::UndefinedConversionError,
|
36
36
|
].freeze
|
37
37
|
|
38
38
|
# @return [Array<Symbol>] the list of keys that can be be overwritten with
|
@@ -60,10 +60,10 @@ module Airbrake
|
|
60
60
|
errors: NestedException.new(exception).as_json,
|
61
61
|
context: context,
|
62
62
|
environment: {
|
63
|
-
program_name: $PROGRAM_NAME
|
63
|
+
program_name: $PROGRAM_NAME,
|
64
64
|
},
|
65
65
|
session: {},
|
66
|
-
params: params
|
66
|
+
params: params,
|
67
67
|
}
|
68
68
|
@truncator = Airbrake::Truncator.new(PAYLOAD_MAX_SIZE)
|
69
69
|
|
@@ -138,7 +138,7 @@ module Airbrake
|
|
138
138
|
# Make sure we always send hostname.
|
139
139
|
hostname: HOSTNAME,
|
140
140
|
|
141
|
-
severity: DEFAULT_SEVERITY
|
141
|
+
severity: DEFAULT_SEVERITY,
|
142
142
|
}.merge(CONTEXT).delete_if { |_key, val| val.nil? || val.empty? }
|
143
143
|
end
|
144
144
|
|
@@ -152,7 +152,7 @@ module Airbrake
|
|
152
152
|
logger.error(
|
153
153
|
"#{LOG_LABEL} truncation failed. File an issue at " \
|
154
154
|
"https://github.com/airbrake/airbrake-ruby " \
|
155
|
-
"and attach the following payload: #{@payload}"
|
155
|
+
"and attach the following payload: #{@payload}",
|
156
156
|
)
|
157
157
|
end
|
158
158
|
|