appsignal 2.8.4-java → 2.9.0-java

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