appsignal 2.8.4-java → 2.9.0-java

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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.rubocop_todo.yml +7 -16
  4. data/.travis.yml +4 -1
  5. data/CHANGELOG.md +16 -0
  6. data/README.md +23 -0
  7. data/Rakefile +10 -7
  8. data/appsignal.gemspec +3 -0
  9. data/build_matrix.yml +5 -1
  10. data/ext/Rakefile +23 -16
  11. data/ext/agent.yml +37 -37
  12. data/ext/base.rb +86 -24
  13. data/ext/extconf.rb +33 -26
  14. data/gemfiles/rails-6.0.gemfile +5 -0
  15. data/lib/appsignal.rb +14 -489
  16. data/lib/appsignal/cli/diagnose.rb +84 -4
  17. data/lib/appsignal/cli/diagnose/paths.rb +0 -5
  18. data/lib/appsignal/cli/diagnose/utils.rb +17 -0
  19. data/lib/appsignal/cli/helpers.rb +6 -0
  20. data/lib/appsignal/cli/install.rb +13 -7
  21. data/lib/appsignal/config.rb +1 -2
  22. data/lib/appsignal/event_formatter.rb +4 -5
  23. data/lib/appsignal/event_formatter/moped/query_formatter.rb +60 -59
  24. data/lib/appsignal/extension.rb +2 -2
  25. data/lib/appsignal/helpers/instrumentation.rb +485 -0
  26. data/lib/appsignal/helpers/metrics.rb +55 -0
  27. data/lib/appsignal/hooks.rb +9 -8
  28. data/lib/appsignal/hooks/puma.rb +65 -9
  29. data/lib/appsignal/hooks/sidekiq.rb +90 -0
  30. data/lib/appsignal/integrations/mongo_ruby_driver.rb +7 -0
  31. data/lib/appsignal/integrations/railtie.rb +2 -1
  32. data/lib/appsignal/marker.rb +2 -3
  33. data/lib/appsignal/minutely.rb +164 -14
  34. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -1
  35. data/lib/appsignal/system.rb +16 -18
  36. data/lib/appsignal/utils/rails_helper.rb +16 -0
  37. data/lib/appsignal/version.rb +1 -1
  38. data/spec/lib/appsignal/cli/diagnose_spec.rb +129 -22
  39. data/spec/lib/appsignal/cli/install_spec.rb +6 -1
  40. data/spec/lib/appsignal/config_spec.rb +3 -3
  41. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +6 -0
  42. data/spec/lib/appsignal/event_formatter_spec.rb +168 -69
  43. data/spec/lib/appsignal/hooks/puma_spec.rb +129 -0
  44. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +147 -0
  45. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +24 -1
  46. data/spec/lib/appsignal/minutely_spec.rb +251 -21
  47. data/spec/lib/appsignal/system_spec.rb +0 -35
  48. data/spec/lib/appsignal/utils/hash_sanitizer_spec.rb +39 -31
  49. data/spec/lib/appsignal/utils/json_spec.rb +7 -3
  50. data/spec/lib/appsignal_spec.rb +27 -2
  51. data/spec/spec_helper.rb +13 -0
  52. data/spec/support/helpers/log_helpers.rb +6 -0
  53. data/spec/support/project_fixture/config/appsignal.yml +1 -0
  54. data/spec/support/stubs/sidekiq/api.rb +4 -0
  55. metadata +8 -2
@@ -32,7 +32,7 @@ module Appsignal
32
32
  attr_reader :raise_errors_on
33
33
 
34
34
  def initialize(app, options = {})
35
- Appsignal.logger.debug "Initializing Appsignal::Rack::SinatraInstrumentation"
35
+ Appsignal.logger.debug "Initializing Appsignal::Rack::SinatraBaseInstrumentation"
36
36
  @app = app
37
37
  @options = options
38
38
  @raise_errors_on = raise_errors?(@app)
@@ -16,21 +16,6 @@ module Appsignal
16
16
  ENV.key? "DYNO".freeze
17
17
  end
18
18
 
19
- # Returns the architecture for which the agent was installed.
20
- #
21
- # This value is saved when the gem is installed in `ext/extconf.rb`.
22
- # We use this value to build the diagnose report with the installed
23
- # CPU type and platform, rather than the detected architecture in
24
- # {.agent_platform} during the diagnose run.
25
- #
26
- # @api private
27
- # @return [String]
28
- def self.installed_agent_architecture
29
- architecture_file = File.join(GEM_EXT_PATH, "appsignal.architecture")
30
- return unless File.exist?(architecture_file)
31
- File.read(architecture_file)
32
- end
33
-
34
19
  # Detect agent and extension platform build
35
20
  #
36
21
  # Used by `ext/extconf.rb` to select which build it should download and
@@ -42,7 +27,7 @@ module Appsignal
42
27
  # @api private
43
28
  # @return [String]
44
29
  def self.agent_platform
45
- return MUSL_TARGET if ENV["APPSIGNAL_BUILD_FOR_MUSL"]
30
+ return MUSL_TARGET if force_musl_build?
46
31
 
47
32
  host_os = RbConfig::CONFIG["host_os"].downcase
48
33
  local_os =
@@ -59,8 +44,8 @@ module Appsignal
59
44
  if local_os =~ /linux/
60
45
  ldd_output = ldd_version_output
61
46
  return MUSL_TARGET if ldd_output.include? "musl"
62
- ldd_version = ldd_output.match(/\d+\.\d+/)
63
- if ldd_version && versionify(ldd_version[0]) < versionify("2.15")
47
+ ldd_version = extract_ldd_version(ldd_output)
48
+ if ldd_version && versionify(ldd_version) < versionify("2.15")
64
49
  return MUSL_TARGET
65
50
  end
66
51
  end
@@ -68,6 +53,13 @@ module Appsignal
68
53
  local_os
69
54
  end
70
55
 
56
+ # Returns whether or not the musl build was forced by the user.
57
+ #
58
+ # @api private
59
+ def self.force_musl_build?
60
+ %w[true 1].include?(ENV["APPSIGNAL_BUILD_FOR_MUSL"])
61
+ end
62
+
71
63
  # @api private
72
64
  def self.versionify(version)
73
65
  Gem::Version.new(version)
@@ -78,6 +70,12 @@ module Appsignal
78
70
  `ldd --version 2>&1`
79
71
  end
80
72
 
73
+ # @api private
74
+ def self.extract_ldd_version(string)
75
+ ldd_version = string.match(/\d+\.\d+/)
76
+ ldd_version && ldd_version[0]
77
+ end
78
+
81
79
  def self.jruby?
82
80
  RUBY_PLATFORM == "java"
83
81
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ module Utils
5
+ module RailsHelper
6
+ def self.detected_rails_app_name
7
+ rails_class = Rails.application.class
8
+ if rails_class.respond_to? :module_parent_name # Rails 6
9
+ rails_class.module_parent_name
10
+ else # Older Rails versions
11
+ rails_class.parent_name
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "2.8.4".freeze
4
+ VERSION = "2.9.0".freeze
5
5
  end
@@ -190,8 +190,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
190
190
  it "outputs version numbers" do
191
191
  expect(output).to include \
192
192
  "Gem version: #{Appsignal::VERSION}",
193
- "Agent version: #{Appsignal::Extension.agent_version}",
194
- "Agent architecture: #{Appsignal::System.installed_agent_architecture}"
193
+ "Agent version: #{Appsignal::Extension.agent_version}"
195
194
  end
196
195
 
197
196
  it "transmits version numbers in report" do
@@ -200,7 +199,6 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
200
199
  "language" => "ruby",
201
200
  "package_version" => Appsignal::VERSION,
202
201
  "agent_version" => Appsignal::Extension.agent_version,
203
- "agent_architecture" => Appsignal::System.installed_agent_architecture,
204
202
  "extension_loaded" => true
205
203
  }
206
204
  )
@@ -238,6 +236,131 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
238
236
  end
239
237
  end
240
238
 
239
+ describe "installation report" do
240
+ let(:rbconfig) { RbConfig::CONFIG }
241
+
242
+ it "adds the installation report to the diagnostics report" do
243
+ run
244
+ jruby = DependencyHelper.running_jruby?
245
+ expect(received_report["installation"]).to match(
246
+ "result" => {
247
+ "status" => "success"
248
+ },
249
+ "language" => {
250
+ "name" => "ruby",
251
+ "version" => "#{rbconfig["ruby_version"]}-p#{rbconfig["PATCHLEVEL"]}",
252
+ "implementation" => jruby ? "jruby" : "ruby"
253
+ },
254
+ "download" => {
255
+ "download_url" => kind_of(String),
256
+ "checksum" => "verified"
257
+ },
258
+ "build" => {
259
+ "time" => kind_of(String),
260
+ "package_path" => File.expand_path("../../../../../", __FILE__),
261
+ "architecture" => rbconfig["host_cpu"],
262
+ "target" => Appsignal::System.agent_platform,
263
+ "musl_override" => false,
264
+ "library_type" => jruby ? "dynamic" : "static",
265
+ "source" => "remote",
266
+ "dependencies" => kind_of(Hash),
267
+ "flags" => kind_of(Hash),
268
+ "agent_version" => Appsignal::Extension.agent_version
269
+ },
270
+ "host" => {
271
+ "root_user" => false,
272
+ "dependencies" => kind_of(Hash)
273
+ }
274
+ )
275
+ end
276
+
277
+ it "prints the extension installation report" do
278
+ run
279
+ jruby = Appsignal::System.jruby?
280
+ expect(output).to include(
281
+ "Extension installation report",
282
+ "Language details",
283
+ " Implementation: #{jruby ? "jruby" : "ruby"}",
284
+ " Ruby version: #{"#{rbconfig["ruby_version"]}-p#{rbconfig["PATCHLEVEL"]}"}",
285
+ "Download details",
286
+ " Download URL: https://",
287
+ " Checksum: verified",
288
+ "Build details",
289
+ " Install time: 20",
290
+ " Architecture: #{rbconfig["host_cpu"]}",
291
+ " Target: #{Appsignal::System.agent_platform}",
292
+ " Musl override: false",
293
+ " Library type: #{jruby ? "dynamic" : "static"}",
294
+ " Dependencies: {",
295
+ " Flags: {",
296
+ "Host details",
297
+ " Root user: false",
298
+ " Dependencies: {"
299
+ )
300
+ end
301
+
302
+ context "without install report" do
303
+ let(:error) { RuntimeError.new("foo") }
304
+ before do
305
+ allow(File).to receive(:read).and_call_original
306
+ expect(File).to receive(:read)
307
+ .with(File.expand_path("../../../../../ext/install.report", __FILE__))
308
+ .and_raise(error)
309
+ end
310
+
311
+ it "sends an error" do
312
+ run
313
+ expect(received_report["installation"]).to match(
314
+ "parsing_error" => {
315
+ "error" => "RuntimeError: foo",
316
+ "backtrace" => error.backtrace
317
+ }
318
+ )
319
+ end
320
+
321
+ it "prints the error" do
322
+ run
323
+ expect(output).to include(
324
+ "Extension installation report",
325
+ " Error found while parsing the report.",
326
+ " Error: RuntimeError: foo"
327
+ )
328
+ expect(output).to_not include("Raw report:")
329
+ end
330
+ end
331
+
332
+ context "when report is invalid YAML" do
333
+ let(:raw_report) { "foo:\nbar" }
334
+ before do
335
+ allow(File).to receive(:read).and_call_original
336
+ expect(File).to receive(:read)
337
+ .with(File.expand_path("../../../../../ext/install.report", __FILE__))
338
+ .and_return(raw_report)
339
+ end
340
+
341
+ it "sends an error" do
342
+ run
343
+ expect(received_report["installation"]).to match(
344
+ "parsing_error" => {
345
+ "error" => kind_of(String),
346
+ "backtrace" => kind_of(Array),
347
+ "raw" => raw_report
348
+ }
349
+ )
350
+ end
351
+
352
+ it "prints the error" do
353
+ run
354
+ expect(output).to include(
355
+ "Extension installation report",
356
+ " Error found while parsing the report.",
357
+ " Error: ",
358
+ " Raw report:\n#{raw_report}"
359
+ )
360
+ end
361
+ end
362
+ end
363
+
241
364
  describe "agent diagnostics" do
242
365
  let(:working_directory_stat) { File.stat("/tmp/appsignal") }
243
366
 
@@ -471,7 +594,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
471
594
  context "when not root user" do
472
595
  it "outputs false" do
473
596
  run
474
- expect(output).to include "root user: false"
597
+ expect(output).to include "Root user: false"
475
598
  end
476
599
 
477
600
  it "transmits root: false in report" do
@@ -487,7 +610,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
487
610
  end
488
611
 
489
612
  it "outputs true, with warning" do
490
- expect(output).to include "root user: true (not recommended)"
613
+ expect(output).to include "Root user: true (not recommended)"
491
614
  end
492
615
 
493
616
  it "transmits root: true in report" do
@@ -834,7 +957,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
834
957
  expect(received_report["paths"].keys).to match_array(
835
958
  %w[
836
959
  package_install_path root_path working_dir log_dir_path
837
- ext/install.log ext/mkmf.log appsignal.log
960
+ ext/mkmf.log appsignal.log
838
961
  ]
839
962
  )
840
963
  end
@@ -1138,22 +1261,6 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
1138
1261
  end
1139
1262
  end
1140
1263
 
1141
- describe "install.log" do
1142
- it_behaves_like "diagnose file" do
1143
- let(:filename) { File.join("ext", "install.log") }
1144
- before do
1145
- expect_any_instance_of(Appsignal::CLI::Diagnose::Paths).to receive(:gem_path)
1146
- .at_least(:once)
1147
- .and_return(parent_directory)
1148
- end
1149
- end
1150
-
1151
- it "outputs header" do
1152
- run
1153
- expect(output).to include("Extension install log")
1154
- end
1155
- end
1156
-
1157
1264
  describe "mkmf.log" do
1158
1265
  it_behaves_like "diagnose file" do
1159
1266
  let(:filename) { File.join("ext", "mkmf.log") }
@@ -243,7 +243,12 @@ describe Appsignal::CLI::Install do
243
243
 
244
244
  if rails_present?
245
245
  context "with rails" do
246
- let(:installation_instructions) { ["Installing for Ruby on Rails"] }
246
+ let(:installation_instructions) do
247
+ [
248
+ "Installing for Ruby on Rails",
249
+ "Your app's name is: 'MyApp'"
250
+ ]
251
+ end
247
252
  let(:app_name) { "MyApp" }
248
253
  let(:config_dir) { File.join(tmp_dir, "config") }
249
254
  let(:environments_dir) { File.join(config_dir, "environments") }
@@ -130,7 +130,7 @@ describe Appsignal::Config do
130
130
  :enable_allocation_tracking => true,
131
131
  :enable_gc_instrumentation => false,
132
132
  :enable_host_metrics => true,
133
- :enable_minutely_probes => false,
133
+ :enable_minutely_probes => true,
134
134
  :ca_file_path => File.join(resources_dir, "cacert.pem"),
135
135
  :dns_servers => [],
136
136
  :files_world_accessible => true,
@@ -218,7 +218,8 @@ describe Appsignal::Config do
218
218
  :active => true,
219
219
  :push_api_key => "abc",
220
220
  :name => "TestApp",
221
- :request_headers => kind_of(Array)
221
+ :request_headers => kind_of(Array),
222
+ :enable_minutely_probes => false
222
223
  )
223
224
  end
224
225
 
@@ -492,7 +493,6 @@ describe Appsignal::Config do
492
493
  expect(ENV["_APPSIGNAL_IGNORE_NAMESPACES"]).to eq "admin,private_namespace"
493
494
  expect(ENV["_APPSIGNAL_RUNNING_IN_CONTAINER"]).to eq "false"
494
495
  expect(ENV["_APPSIGNAL_ENABLE_HOST_METRICS"]).to eq "true"
495
- expect(ENV["_APPSIGNAL_ENABLE_MINUTELY_PROBES"]).to eq "false"
496
496
  expect(ENV["_APPSIGNAL_HOSTNAME"]).to eq ""
497
497
  expect(ENV["_APPSIGNAL_PROCESS_NAME"]).to include "rspec"
498
498
  expect(ENV["_APPSIGNAL_CA_FILE_PATH"]).to eq File.join(resources_dir, "cacert.pem")
@@ -16,6 +16,12 @@ describe Appsignal::EventFormatter::Moped::QueryFormatter do
16
16
  it { is_expected.to be_nil }
17
17
  end
18
18
 
19
+ context "when ops is nil in the payload" do
20
+ let(:payload) { { :ops => nil } }
21
+
22
+ it { is_expected.to be_nil }
23
+ end
24
+
19
25
  context "Moped::Protocol::Command" do
20
26
  let(:op) do
21
27
  double(
@@ -10,9 +10,10 @@ class MockFormatter < Appsignal::EventFormatter
10
10
  end
11
11
  end
12
12
 
13
- class MissingFormatMockFormatter < Appsignal::EventFormatter
14
- def transform(_payload)
15
- end
13
+ class MockFormatterDouble < MockFormatter
14
+ end
15
+
16
+ class MissingFormatMockFormatter
16
17
  end
17
18
 
18
19
  class IncorrectFormatMockFormatter < Appsignal::EventFormatter
@@ -20,109 +21,207 @@ class IncorrectFormatMockFormatter < Appsignal::EventFormatter
20
21
  end
21
22
  end
22
23
 
24
+ class IncorrectFormatMock2Formatter < Appsignal::EventFormatter
25
+ def format(_payload, _foo = nil)
26
+ end
27
+ end
28
+
23
29
  class MockDependentFormatter < Appsignal::EventFormatter
24
30
  def initialize
25
31
  NonsenseDependency.something
26
32
  end
27
- end
28
33
 
29
- Appsignal::EventFormatter.register "mock", MockFormatter
30
- Appsignal::EventFormatter.register "mock.dependent", MockDependentFormatter
34
+ def format(_payload)
35
+ end
36
+ end
31
37
 
32
38
  describe Appsignal::EventFormatter do
33
- before do
34
- klass.register("mock", MockFormatter)
39
+ let(:klass) { described_class }
40
+ around do |example|
41
+ original_formatters = described_class.formatters
42
+ example.run
43
+ described_class.class_variable_set(:@@formatters, original_formatters)
35
44
  end
36
45
 
37
- let(:klass) { Appsignal::EventFormatter }
38
-
39
- let(:deprecated_formatter) do
40
- Class.new(Appsignal::EventFormatter) do
41
- register "mock.deprecated"
42
-
43
- def format(_payload)
44
- end
45
- end
46
- end
46
+ describe ".register" do
47
+ it "registers a formatter" do
48
+ expect(klass.registered?("mock")).to be_falsy
47
49
 
48
- context "registering and unregistering formatters" do
49
- let(:out_stream) { std_stream }
50
- let(:output) { out_stream.read }
50
+ klass.register "mock", MockFormatter
51
51
 
52
- it "registers a formatter" do
53
52
  expect(klass.formatters["mock"]).to be_instance_of(MockFormatter)
53
+ expect(klass.formatter_classes["mock"]).to eq MockFormatter
54
54
  end
55
55
 
56
- it "knows whether a formatter is registered" do
57
- expect(klass.registered?("mock")).to be_truthy
58
- expect(klass.registered?("mock", MockFormatter)).to be_truthy
59
- expect(klass.registered?("mock", Hash)).to be_falsy
60
- expect(klass.registered?("nonsense")).to be_falsy
56
+ context "when a formatter with the name already exists" do
57
+ it "does not register the formatter again" do
58
+ logs = capture_logs do
59
+ klass.register("mock.twice", MockFormatter)
60
+ klass.register("mock.twice", MockFormatter)
61
+ end
62
+ expect(klass.registered?("mock.twice")).to be_truthy
63
+ expect(logs).to contains_log :warn, \
64
+ "Formatter for 'mock.twice' already registered, not registering 'MockFormatter'"
65
+ end
61
66
  end
62
67
 
63
- it "doesn't register formatters that raise a name error in the initializer" do
64
- expect(klass.registered?("mock.dependent")).to be_falsy
68
+ context "when there is an error initializing the formatter" do
69
+ it "does not register the formatter and logs an error" do
70
+ logs = capture_logs do
71
+ described_class.register "mock.dependent", MockDependentFormatter
72
+ end
73
+ expect(klass.registered?("mock.dependent")).to be_falsy
74
+ expect(logs).to contains_log :error, \
75
+ "'uninitialized constant MockDependentFormatter::NonsenseDependency' " \
76
+ "when initializing mock.dependent event formatter"
77
+ end
65
78
  end
66
79
 
67
- it "registers a custom formatter" do
68
- klass.register("mock.specific", MockFormatter)
80
+ context "when formatter has no format/1 method" do
81
+ context "when the formatter has no format method" do
82
+ it "does not register the formatter and logs an error" do
83
+ logs = capture_logs do
84
+ described_class.register "mock.missing", MissingFormatMockFormatter
85
+ end
86
+ expect(klass.registered?("mock.missing")).to be_falsy
87
+ expect(logs).to contains_log :error, \
88
+ "'MissingFormatMockFormatter does not have a format(payload) " \
89
+ "method' when initializing mock.missing event formatter"
90
+ end
91
+ end
69
92
 
70
- expect(klass.formatter_classes["mock.specific"]).to eq MockFormatter
71
- expect(klass.registered?("mock.specific")).to be_truthy
72
- expect(klass.formatters["mock.specific"]).to be_instance_of(MockFormatter)
73
- expect(klass.formatters["mock.specific"].body).to eq "some value"
74
- end
93
+ context "when the formatter has an format/0 method" do
94
+ it "does not register the formatter and logs an error" do
95
+ logs = capture_logs do
96
+ described_class.register "mock.incorrect", IncorrectFormatMockFormatter
97
+ end
98
+ expect(klass.registered?("mock.incorrect")).to be_falsy
99
+ expect(logs).to contains_log :error, \
100
+ "'IncorrectFormatMockFormatter does not have a format(payload) " \
101
+ "method' when initializing mock.incorrect event formatter"
102
+ end
103
+ end
75
104
 
76
- it "does not know a formatter that's not registered" do
77
- expect(klass.formatters["nonsense"]).to be_nil
105
+ context "when formatter has an format/2 method" do
106
+ it "does not register the formatter and logs an error" do
107
+ logs = capture_logs do
108
+ described_class.register "mock.incorrect", IncorrectFormatMock2Formatter
109
+ end
110
+ expect(klass.registered?("mock.incorrect")).to be_falsy
111
+ expect(logs).to contains_log :error, \
112
+ "'IncorrectFormatMock2Formatter does not have a format(payload) " \
113
+ "method' when initializing mock.incorrect event formatter"
114
+ end
115
+ end
78
116
  end
79
117
 
80
- it "unregistering a formatter if the registered one has the same class" do
81
- klass.register("mock.unregister", MockFormatter)
118
+ context "when registering deprecated formatters" do
119
+ let(:stdout_stream) { std_stream }
120
+ let(:deprecated_formatter) do
121
+ Class.new(Appsignal::EventFormatter) do
122
+ register "mock.deprecated"
82
123
 
83
- klass.unregister("mock.unregister", Hash)
84
- expect(klass.registered?("mock.unregister")).to be_truthy
124
+ def format(_payload)
125
+ end
126
+ end
127
+ end
85
128
 
86
- klass.unregister("mock.unregister", MockFormatter)
87
- expect(klass.registered?("mock.unregister")).to be_falsy
88
- end
129
+ it "registers deprecated formatters and logs & prints a warning" do
130
+ message = "Formatter for 'mock.deprecated' is using a deprecated registration method. " \
131
+ "This event formatter will not be loaded. " \
132
+ "Please update the formatter according to the documentation at: " \
133
+ "https://docs.appsignal.com/ruby/instrumentation/event-formatters.html"
134
+
135
+ logs = capture_logs do
136
+ capture_stdout(stdout_stream) { deprecated_formatter }
137
+ end
138
+ expect(logs).to contains_log :warn, message
139
+ expect(stdout_stream.read).to include "appsignal WARNING: #{message}"
140
+
141
+ expect(klass.deprecated_formatter_classes.keys).to include("mock.deprecated")
142
+ end
143
+
144
+ it "initializes deprecated formatters" do
145
+ capture_stdout(stdout_stream) { deprecated_formatter }
146
+ klass.initialize_deprecated_formatters
89
147
 
90
- it "does not register two formatters for the same name" do
91
- expect(Appsignal.logger).to receive(:warn)
92
- .with("Formatter for 'mock.twice' already registered, not registering 'MockFormatter'")
93
- klass.register("mock.twice", MockFormatter)
94
- klass.register("mock.twice", MockFormatter)
148
+ expect(klass.registered?("mock.deprecated")).to be_truthy
149
+ expect(klass.formatters["mock.deprecated"]).to be_instance_of(deprecated_formatter)
150
+ expect(klass.deprecated_formatter_classes["mock.deprecated"]).to eq(deprecated_formatter)
151
+ end
95
152
  end
153
+ end
96
154
 
97
- it "does not register deprecated formatters" do
98
- message = "Formatter for 'mock.deprecated' is using a deprecated registration method. " \
99
- "This event formatter will not be loaded. " \
100
- "Please update the formatter according to the documentation at: " \
101
- "https://docs.appsignal.com/ruby/instrumentation/event-formatters.html"
102
- expect(Appsignal.logger).to receive(:warn).with(message)
155
+ describe ".registered?" do
156
+ context "when checking by name" do
157
+ context "when there is a formatter with that name" do
158
+ it "returns true" do
159
+ klass.register "mock", MockFormatter
160
+ expect(klass.registered?("mock")).to be_truthy
161
+ end
162
+ end
163
+
164
+ context "when there is no formatter with that name" do
165
+ it "returns false" do
166
+ expect(klass.registered?("nonsense")).to be_falsy
167
+ end
168
+ end
169
+ end
103
170
 
104
- capture_stdout(out_stream) { deprecated_formatter }
105
- expect(output).to include "appsignal WARNING: #{message}"
171
+ context "when checking by name and class" do
172
+ context "when there is a formatter with that name and class" do
173
+ it "returns true" do
174
+ klass.register "mock", MockFormatter
175
+ expect(klass.registered?("mock", MockFormatter)).to be_truthy
176
+ end
177
+ end
106
178
 
107
- expect(Appsignal::EventFormatter.deprecated_formatter_classes.keys).to include("mock.deprecated")
179
+ context "when there is no formatter with that name and class" do
180
+ it "returns false" do
181
+ klass.register "mock", MockFormatterDouble
182
+ expect(klass.registered?("mock", MockFormatter)).to be_falsy
183
+ end
184
+ end
108
185
  end
186
+ end
187
+
188
+ describe ".unregister" do
189
+ context "when a formatter with the name is registered" do
190
+ it "unregisters the formatter has the same class" do
191
+ klass.register("mock.unregister", MockFormatter)
192
+ expect(klass.registered?("mock.unregister")).to be_truthy
109
193
 
110
- it "initializes deprecated formatters" do
111
- # Silence deprecation warning
112
- capture_stdout(out_stream) { deprecated_formatter }
113
- Appsignal::EventFormatter.initialize_deprecated_formatters
194
+ klass.unregister("mock.unregister", Hash)
195
+ expect(klass.registered?("mock.unregister")).to be_truthy
114
196
 
115
- expect(klass.registered?("mock.deprecated")).to be_truthy
197
+ klass.unregister("mock.unregister", MockFormatter)
198
+ expect(klass.registered?("mock.unregister")).to be_falsy
199
+ end
200
+ end
201
+
202
+ context "when a formatter with the same name and class is not registered" do
203
+ it "unregisters nothing" do
204
+ expect do
205
+ expect do
206
+ klass.unregister("nonse.unregister", MockFormatter)
207
+ end.to_not(change { klass.formatters })
208
+ end.to_not(change { klass.formatter_classes })
209
+ end
116
210
  end
117
211
  end
118
212
 
119
- context "calling formatters" do
120
- it "returns nil if there is no formatter registered" do
121
- expect(klass.format("nonsense", {})).to be_nil
213
+ describe ".format" do
214
+ context "when no formatter with the name is registered" do
215
+ it "returns nil" do
216
+ expect(klass.format("nonsense", {})).to be_nil
217
+ end
122
218
  end
123
219
 
124
- it "calls the formatter if it is registered and use a value set in the initializer" do
125
- expect(klass.format("mock", {})).to eq ["title", "some value"]
220
+ context "when a formatter with the name is registered" do
221
+ it "calls the formatter and use a value set in the initializer" do
222
+ klass.register "mock", MockFormatter
223
+ expect(klass.format("mock", {})).to eq ["title", "some value"]
224
+ end
126
225
  end
127
226
  end
128
227
  end