atatus 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile +57 -0
  5. data/LICENSE +65 -0
  6. data/LICENSE-THIRD-PARTY +205 -0
  7. data/README.md +13 -0
  8. data/Rakefile +19 -0
  9. data/atatus.gemspec +36 -0
  10. data/atatus.yml +2 -0
  11. data/bench/.gitignore +2 -0
  12. data/bench/app.rb +53 -0
  13. data/bench/benchmark.rb +36 -0
  14. data/bench/report.rb +55 -0
  15. data/bench/rubyprof.rb +39 -0
  16. data/bench/stackprof.rb +23 -0
  17. data/bin/build_docs +5 -0
  18. data/bin/console +15 -0
  19. data/bin/setup +8 -0
  20. data/bin/with_framework +7 -0
  21. data/lib/atatus.rb +325 -0
  22. data/lib/atatus/agent.rb +260 -0
  23. data/lib/atatus/central_config.rb +141 -0
  24. data/lib/atatus/central_config/cache_control.rb +34 -0
  25. data/lib/atatus/collector/base.rb +329 -0
  26. data/lib/atatus/collector/builder.rb +317 -0
  27. data/lib/atatus/collector/transport.rb +72 -0
  28. data/lib/atatus/config.rb +248 -0
  29. data/lib/atatus/config/bytes.rb +25 -0
  30. data/lib/atatus/config/duration.rb +23 -0
  31. data/lib/atatus/config/options.rb +134 -0
  32. data/lib/atatus/config/regexp_list.rb +13 -0
  33. data/lib/atatus/context.rb +33 -0
  34. data/lib/atatus/context/request.rb +11 -0
  35. data/lib/atatus/context/request/socket.rb +19 -0
  36. data/lib/atatus/context/request/url.rb +42 -0
  37. data/lib/atatus/context/response.rb +22 -0
  38. data/lib/atatus/context/user.rb +42 -0
  39. data/lib/atatus/context_builder.rb +97 -0
  40. data/lib/atatus/deprecations.rb +22 -0
  41. data/lib/atatus/error.rb +22 -0
  42. data/lib/atatus/error/exception.rb +46 -0
  43. data/lib/atatus/error/log.rb +24 -0
  44. data/lib/atatus/error_builder.rb +76 -0
  45. data/lib/atatus/instrumenter.rb +224 -0
  46. data/lib/atatus/internal_error.rb +6 -0
  47. data/lib/atatus/logging.rb +55 -0
  48. data/lib/atatus/metadata.rb +19 -0
  49. data/lib/atatus/metadata/process_info.rb +18 -0
  50. data/lib/atatus/metadata/service_info.rb +61 -0
  51. data/lib/atatus/metadata/system_info.rb +35 -0
  52. data/lib/atatus/metadata/system_info/container_info.rb +121 -0
  53. data/lib/atatus/metadata/system_info/hw_info.rb +118 -0
  54. data/lib/atatus/metadata/system_info/os_info.rb +31 -0
  55. data/lib/atatus/metrics.rb +98 -0
  56. data/lib/atatus/metrics/cpu_mem.rb +240 -0
  57. data/lib/atatus/metrics/vm.rb +60 -0
  58. data/lib/atatus/metricset.rb +19 -0
  59. data/lib/atatus/middleware.rb +76 -0
  60. data/lib/atatus/naively_hashable.rb +21 -0
  61. data/lib/atatus/normalizers.rb +68 -0
  62. data/lib/atatus/normalizers/action_controller.rb +27 -0
  63. data/lib/atatus/normalizers/action_mailer.rb +26 -0
  64. data/lib/atatus/normalizers/action_view.rb +77 -0
  65. data/lib/atatus/normalizers/active_record.rb +45 -0
  66. data/lib/atatus/opentracing.rb +346 -0
  67. data/lib/atatus/rails.rb +61 -0
  68. data/lib/atatus/railtie.rb +30 -0
  69. data/lib/atatus/span.rb +125 -0
  70. data/lib/atatus/span/context.rb +40 -0
  71. data/lib/atatus/span_helpers.rb +44 -0
  72. data/lib/atatus/spies.rb +86 -0
  73. data/lib/atatus/spies/action_dispatch.rb +28 -0
  74. data/lib/atatus/spies/delayed_job.rb +68 -0
  75. data/lib/atatus/spies/elasticsearch.rb +36 -0
  76. data/lib/atatus/spies/faraday.rb +70 -0
  77. data/lib/atatus/spies/http.rb +44 -0
  78. data/lib/atatus/spies/json.rb +22 -0
  79. data/lib/atatus/spies/mongo.rb +87 -0
  80. data/lib/atatus/spies/net_http.rb +70 -0
  81. data/lib/atatus/spies/rake.rb +45 -0
  82. data/lib/atatus/spies/redis.rb +27 -0
  83. data/lib/atatus/spies/sequel.rb +47 -0
  84. data/lib/atatus/spies/sidekiq.rb +89 -0
  85. data/lib/atatus/spies/sinatra.rb +41 -0
  86. data/lib/atatus/spies/tilt.rb +27 -0
  87. data/lib/atatus/sql_summarizer.rb +35 -0
  88. data/lib/atatus/stacktrace.rb +16 -0
  89. data/lib/atatus/stacktrace/frame.rb +52 -0
  90. data/lib/atatus/stacktrace_builder.rb +104 -0
  91. data/lib/atatus/subscriber.rb +77 -0
  92. data/lib/atatus/trace_context.rb +85 -0
  93. data/lib/atatus/transaction.rb +100 -0
  94. data/lib/atatus/transport/base.rb +174 -0
  95. data/lib/atatus/transport/connection.rb +156 -0
  96. data/lib/atatus/transport/connection/http.rb +116 -0
  97. data/lib/atatus/transport/connection/proxy_pipe.rb +75 -0
  98. data/lib/atatus/transport/filters.rb +43 -0
  99. data/lib/atatus/transport/filters/secrets_filter.rb +74 -0
  100. data/lib/atatus/transport/serializers.rb +93 -0
  101. data/lib/atatus/transport/serializers/context_serializer.rb +85 -0
  102. data/lib/atatus/transport/serializers/error_serializer.rb +77 -0
  103. data/lib/atatus/transport/serializers/metadata_serializer.rb +70 -0
  104. data/lib/atatus/transport/serializers/metricset_serializer.rb +28 -0
  105. data/lib/atatus/transport/serializers/span_serializer.rb +80 -0
  106. data/lib/atatus/transport/serializers/transaction_serializer.rb +37 -0
  107. data/lib/atatus/transport/worker.rb +73 -0
  108. data/lib/atatus/util.rb +42 -0
  109. data/lib/atatus/util/inflector.rb +93 -0
  110. data/lib/atatus/util/lru_cache.rb +48 -0
  111. data/lib/atatus/util/prefixed_logger.rb +18 -0
  112. data/lib/atatus/util/throttle.rb +35 -0
  113. data/lib/atatus/version.rb +5 -0
  114. data/vendor/.gitkeep +0 -0
  115. metadata +190 -0
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
5
+ require 'active_record'
6
+ require 'action_controller/railtie'
7
+ require 'atatus'
8
+ require 'atatus/railtie'
9
+
10
+ $log = Logger.new('/tmp/bench.log')
11
+
12
+ ActiveRecord::Base.logger = $log
13
+
14
+ ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: '/tmp/bench.sqlite3')
15
+
16
+ ActiveRecord::Schema.define do
17
+ create_table :posts, force: true do |t|
18
+ t.string :title
19
+ t.timestamps
20
+ end
21
+ end
22
+
23
+ class Post < ActiveRecord::Base
24
+ end
25
+
26
+ 10.times { |i| Post.create! title: "Post #{i}" }
27
+
28
+ class ApplicationController < ActionController::Base
29
+ def index
30
+ render inline: '<%= Post.pluck(:title).join(", ") %>'
31
+ end
32
+
33
+ def favicon
34
+ render nothing: true
35
+ end
36
+ end
37
+
38
+ class App < Rails::Application
39
+ config.secret_key_base = '__secret'
40
+ config.logger = $log
41
+ config.eager_load = false
42
+
43
+ config.atatus.disable_send = true
44
+ config.atatus.logger = $log
45
+ config.atatus.log_level = Logger::DEBUG
46
+ end
47
+
48
+ App.initialize!
49
+
50
+ App.routes.draw do
51
+ get '/favicon.ico', to: 'application#favicon'
52
+ root to: 'application#index'
53
+ end
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ ENV['RAILS_ENV'] = 'production'
5
+
6
+ require 'bundler'
7
+ require 'bundler/setup'
8
+
9
+ require 'benchmark'
10
+ include Benchmark
11
+ require 'rack/test'
12
+
13
+ require './bench/app'
14
+
15
+ def app
16
+ App
17
+ end
18
+
19
+ include Rack::Test::Methods
20
+
21
+ def perform
22
+ 10_000.times do
23
+ get '/'
24
+ end
25
+ end
26
+
27
+ bench = Benchmark.benchmark(CAPTION, 15, FORMAT) do |b|
28
+ perform # warm up
29
+
30
+ b.report('with agent:') { perform }
31
+
32
+ Atatus.stop
33
+ perform # warm up
34
+
35
+ b.report('without agent:') { perform }
36
+ end
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'time'
5
+ require 'json'
6
+
7
+ input = STDIN.read.split("\n")
8
+
9
+ git_sha, git_msg = `git log -n 1 --pretty="format:%H|||%s"`.split('|||')
10
+ git_date = `git log -n 1 --pretty="format:%ai"`
11
+ platform = Gem::Platform.local
12
+
13
+ def doc(payload)
14
+ puts({ index: { _index: "benchmark-ruby", _type: "_doc" } }.to_json)
15
+ puts(payload.to_json)
16
+ end
17
+
18
+ meta = {
19
+ executed_at: Time.new.iso8601,
20
+ 'git.commit' => git_sha,
21
+ 'git.date' => String(git_date).strip != '' && Time.parse(git_date).iso8601,
22
+ 'git.subject' => git_msg,
23
+ hostname: `hostname`.chomp,
24
+ engine: RUBY_ENGINE,
25
+ arch: platform.cpu,
26
+ os: platform.os,
27
+ ruby_version: "#{RUBY_ENGINE == 'jruby' ? 'j' : ''}#{RUBY_VERSION}"
28
+ }
29
+
30
+ results =
31
+ input
32
+ .grep(/^with/)
33
+ .map do |line|
34
+ title = line.match(/^(.*):/) { |m| m[1] }
35
+ user, system, total, real = line.scan(/[0-9\.]+/).map(&:to_f)
36
+ meta.merge(
37
+ title: title,
38
+ user: user,
39
+ system: system,
40
+ total: total,
41
+ real: real,
42
+ )
43
+ end
44
+
45
+ results.each { |result| doc result }
46
+
47
+ overhead =
48
+ (results[0][:total] - results[1][:total]) *
49
+ 1000 / # milliseconds
50
+ 10_000 # transactions
51
+
52
+ doc meta.merge(
53
+ title: 'overhead',
54
+ overhead: overhead
55
+ )
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby-prof'
4
+ require 'rack/test'
5
+
6
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
7
+ require 'atatus'
8
+
9
+ require './bench/app'
10
+ include App::Helpers
11
+
12
+ def perform(app, count: 1000)
13
+ app.start
14
+
15
+ transactions = count.times.map do |i|
16
+ Atatus.transaction "Transaction##{i}",
17
+ context: Atatus.build_context(app.mock_env) do
18
+ Atatus.span('Number one') { 'ok 1' }
19
+ Atatus.span('Number two') { 'ok 2' }
20
+ Atatus.span('Number three') { 'ok 3' }
21
+ end
22
+ end
23
+
24
+ app.stop
25
+ end
26
+
27
+ def do_bench(transaction_count: 1000, **config)
28
+ with_app(config) do |app|
29
+ profile = RubyProf::Profile.new
30
+ profile.exclude_common_methods!
31
+ profile.start
32
+ perform(app, count: transaction_count)
33
+ profile.stop
34
+ printer = RubyProf::GraphHtmlPrinter.new(profile)
35
+ printer.print(File.open('bench/tmp/out.html', 'w'))
36
+ end
37
+ end
38
+
39
+ do_bench(transaction_count: 10_000)
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'stackprof'
5
+ require 'rack/test'
6
+
7
+ require './bench/app'
8
+
9
+ def app
10
+ App
11
+ end
12
+
13
+ include Rack::Test::Methods
14
+
15
+ puts 'Running '
16
+ profile = StackProf.run(mode: :cpu) do
17
+ 10_000.times do
18
+ get '/'
19
+ end
20
+ end
21
+ puts ''
22
+
23
+ StackProf::Report.new(profile).print_text
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -ex
3
+
4
+ # Requires atatus/docs to be checked out at ../docs
5
+ ../docs/build_docs.pl --doc docs/index.asciidoc --chunk 1
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'atatus'
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require 'irb'
15
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,7 @@
1
+ #!/bin/bash -x
2
+ set -e
3
+
4
+ framework=$1
5
+
6
+ FRAMEWORK=$framework bundle update
7
+ FRAMEWORK=$framework ${@:2}
@@ -0,0 +1,325 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'atatus/version'
4
+ require 'atatus/internal_error'
5
+ require 'atatus/logging'
6
+
7
+ # Core
8
+ require 'atatus/agent'
9
+ require 'atatus/config'
10
+ require 'atatus/context'
11
+ require 'atatus/instrumenter'
12
+ require 'atatus/util'
13
+
14
+ require 'atatus/middleware'
15
+
16
+ require 'atatus/railtie' if defined?(::Rails::Railtie)
17
+
18
+ # Atatus
19
+ module Atatus # rubocop:disable Metrics/ModuleLength
20
+ class << self
21
+ ### Life cycle
22
+
23
+ # Starts the Atatus Agent
24
+ #
25
+ # @param config [Config] An instance of Config
26
+ # @return [Agent] The resulting [Agent]
27
+ def start(config = {})
28
+ Agent.start config
29
+ end
30
+
31
+ # Stops the Atatus Agent
32
+ def stop
33
+ Agent.stop
34
+ end
35
+
36
+ # @return [Boolean] Whether there's an [Agent] running
37
+ def running?
38
+ Agent.running?
39
+ end
40
+
41
+ # @return [Agent] Currently running [Agent] if any
42
+ def agent
43
+ Agent.instance
44
+ end
45
+
46
+ ### Metrics
47
+
48
+ # Returns the currently active transaction (if any)
49
+ #
50
+ # @return [Transaction] or `nil`
51
+ def current_transaction
52
+ agent&.current_transaction
53
+ end
54
+
55
+ # Returns the currently active span (if any)
56
+ #
57
+ # @return [Span] or `nil`
58
+ def current_span
59
+ agent&.current_span
60
+ end
61
+
62
+ # rubocop:disable Metrics/AbcSize
63
+ # Get a formatted string containing transaction, span, and trace ids.
64
+ # If a block is provided, the ids are yielded.
65
+ #
66
+ # @yield [String|nil, String|nil, String|nil] The transaction, span,
67
+ # and trace ids.
68
+ # @return [String] Unless block given
69
+ def log_ids
70
+ trace_id = (current_transaction || current_span)&.trace_id
71
+ if block_given?
72
+ return yield(current_transaction&.id, current_span&.id, trace_id)
73
+ end
74
+
75
+ ids = []
76
+ ids << "transaction.id=#{current_transaction.id}" if current_transaction
77
+ ids << "span.id=#{current_span.id}" if current_span
78
+ ids << "trace.id=#{trace_id}" if trace_id
79
+ ids.join(' ')
80
+ end
81
+ # rubocop:enable Metrics/AbcSize
82
+
83
+ # rubocop:disable Metrics/MethodLength
84
+ # Start a new transaction
85
+ #
86
+ # @param name [String] A description of the transaction, eg
87
+ # `ExamplesController#index`
88
+ # @param type [String] The kind of the transaction, eg `app.request.get` or
89
+ # `db.mysql2.query`
90
+ # @param context [Context] An optional [Context]
91
+ # @return [Transaction]
92
+ def start_transaction(
93
+ name = nil,
94
+ type = nil,
95
+ context: nil,
96
+ trace_context: nil
97
+ )
98
+ agent&.start_transaction(
99
+ name,
100
+ type,
101
+ context: context,
102
+ trace_context: trace_context
103
+ )
104
+ end
105
+ # rubocop:enable Metrics/MethodLength
106
+
107
+ # Ends the current transaction with `result`
108
+ #
109
+ # @param result [String] The result of the transaction
110
+ # @return [Transaction]
111
+ def end_transaction(result = nil)
112
+ agent&.end_transaction(result)
113
+ end
114
+
115
+ # rubocop:disable Metrics/MethodLength
116
+ # Wrap a block in a Transaction, ending it after the block
117
+ #
118
+ # @param name [String] A description of the transaction, eg
119
+ # `ExamplesController#index`
120
+ # @param type [String] The kind of the transaction, eg `app.request.get` or
121
+ # `db.mysql2.query`
122
+ # @param context [Context] An optional [Context]
123
+ # @yield [Transaction]
124
+ # @return result of block
125
+ def with_transaction(
126
+ name = nil,
127
+ type = nil,
128
+ context: nil,
129
+ trace_context: nil
130
+ )
131
+ unless block_given?
132
+ raise ArgumentError,
133
+ 'expected a block. Do you want `start_transaction\' instead?'
134
+ end
135
+
136
+ return yield(nil) unless agent
137
+
138
+ begin
139
+ transaction =
140
+ start_transaction(
141
+ name,
142
+ type,
143
+ context: context,
144
+ trace_context: trace_context
145
+ )
146
+ yield transaction
147
+ ensure
148
+ end_transaction
149
+ end
150
+ end
151
+ # rubocop:enable Metrics/MethodLength
152
+
153
+ # Start a new span
154
+ #
155
+ # @param name [String] A description of the span, eq `SELECT FROM "users"`
156
+ # @param type [String] The span type, eq `db`
157
+ # @param subtype [String] The span subtype, eq `postgresql`
158
+ # @param action [String] The span action type, eq `connect` or `query`
159
+ # @param context [Span::Context] Context information about the span
160
+ # @param include_stacktrace [Boolean] Whether or not to capture a stacktrace
161
+ # @return [Span]
162
+ def start_span(
163
+ name,
164
+ type = nil,
165
+ subtype: nil,
166
+ action: nil,
167
+ context: nil,
168
+ include_stacktrace: true,
169
+ trace_context: nil
170
+ )
171
+ agent&.start_span(
172
+ name,
173
+ type,
174
+ subtype: subtype,
175
+ action: action,
176
+ context: context,
177
+ trace_context: trace_context
178
+ ).tap do |span|
179
+ break unless span && include_stacktrace
180
+ break unless agent.config.span_frames_min_duration?
181
+
182
+ span.original_backtrace ||= caller
183
+ end
184
+ end
185
+ # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
186
+
187
+ # Ends the current span
188
+ #
189
+ # @return [Span]
190
+ def end_span
191
+ agent&.end_span
192
+ end
193
+
194
+ # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
195
+ # Wrap a block in a Span, ending it after the block
196
+ #
197
+ # @param name [String] A description of the span, eq `SELECT FROM "users"`
198
+ # @param type [String] The kind of span, eq `db.mysql2.query`
199
+ # @param context [Span::Context] Context information about the span
200
+ # @param include_stacktrace [Boolean] Whether or not to capture a stacktrace
201
+ # @yield [Span]
202
+ # @return Result of block
203
+ def with_span(
204
+ name,
205
+ type = nil,
206
+ subtype: nil,
207
+ action: nil,
208
+ context: nil,
209
+ include_stacktrace: true,
210
+ trace_context: nil
211
+ )
212
+ unless block_given?
213
+ raise ArgumentError,
214
+ 'expected a block. Do you want `start_span\' instead?'
215
+ end
216
+
217
+ return yield nil unless agent
218
+
219
+ begin
220
+ span =
221
+ start_span(
222
+ name,
223
+ type,
224
+ subtype: subtype,
225
+ action: action,
226
+ context: context,
227
+ include_stacktrace: include_stacktrace,
228
+ trace_context: trace_context
229
+ )
230
+ yield span
231
+ ensure
232
+ end_span
233
+ end
234
+ end
235
+ # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists
236
+
237
+ # Build a [Context] from a Rack `env`. The context may include information
238
+ # about the request, response, current user and more
239
+ #
240
+ # @param rack_env [Rack::Env] A Rack env
241
+ # @return [Context] The built context
242
+ def build_context(
243
+ rack_env: nil,
244
+ for_type: :transaction
245
+ )
246
+ agent&.build_context(rack_env: rack_env, for_type: for_type)
247
+ end
248
+
249
+ ### Errors
250
+
251
+ # Report and exception to APM
252
+ #
253
+ # @param exception [Exception] The exception
254
+ # @param context [Context] An optional [Context]
255
+ # @param handled [Boolean] Whether the exception was rescued
256
+ # @return [String] ID of the generated [Error]
257
+ def report(exception, context: nil, handled: true)
258
+ agent&.report(exception, context: context, handled: handled)
259
+ end
260
+
261
+ # Report a custom string error message to APM
262
+ #
263
+ # @param message [String] The message
264
+ # @param context [Context] An optional [Context]
265
+ # @return [String] ID of the generated [Error]
266
+ def report_message(message, context: nil, **attrs)
267
+ agent&.report_message(
268
+ message,
269
+ context: context,
270
+ backtrace: caller,
271
+ **attrs
272
+ )
273
+ end
274
+
275
+ ### Context
276
+
277
+ # Set a _label_ value for the current transaction
278
+ #
279
+ # @param key [String,Symbol] A key
280
+ # @param value [Object] A value
281
+ # @return [Object] The given value
282
+ def set_label(key, value)
283
+ case value
284
+ when TrueClass,
285
+ FalseClass,
286
+ Numeric,
287
+ NilClass,
288
+ String
289
+ agent&.set_label(key, value)
290
+ else
291
+ agent&.set_label(key, value.to_s)
292
+ end
293
+ end
294
+
295
+ # Provide further context for the current transaction
296
+ #
297
+ # @param custom [Hash] A hash with custom information. Can be nested.
298
+ # @return [Hash] The current custom context
299
+ def set_custom_context(custom)
300
+ agent&.set_custom_context(custom)
301
+ end
302
+
303
+ # Provide a user to the current transaction
304
+ #
305
+ # @param user [Object] An object representing a user
306
+ # @return [Object] Given user
307
+ def set_user(user)
308
+ agent&.set_user(user)
309
+ end
310
+
311
+ # Provide a filter to transform payloads before sending them off
312
+ #
313
+ # @param key [Symbol] Unique filter key
314
+ # @param callback [Object, Proc] A filter that responds to #call(payload)
315
+ # @yield [Hash] A filter. Used if provided. Otherwise using `callback`
316
+ # @return [Bool] true
317
+ def add_filter(key, callback = nil, &block)
318
+ if callback.nil? && !block_given?
319
+ raise ArgumentError, '#add_filter needs either `callback\' or a block'
320
+ end
321
+
322
+ agent&.add_filter(key, block || callback)
323
+ end
324
+ end
325
+ end