appsignal 2.8.4 → 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/.rubocop_todo.yml +7 -16
- data/.travis.yml +4 -1
- data/CHANGELOG.md +16 -0
- data/README.md +23 -0
- data/Rakefile +10 -7
- data/appsignal.gemspec +3 -0
- data/build_matrix.yml +5 -1
- data/ext/Rakefile +23 -16
- data/ext/agent.yml +37 -37
- data/ext/base.rb +86 -24
- data/ext/extconf.rb +33 -26
- data/gemfiles/rails-6.0.gemfile +5 -0
- data/lib/appsignal.rb +14 -489
- data/lib/appsignal/cli/diagnose.rb +84 -4
- data/lib/appsignal/cli/diagnose/paths.rb +0 -5
- data/lib/appsignal/cli/diagnose/utils.rb +17 -0
- data/lib/appsignal/cli/helpers.rb +6 -0
- data/lib/appsignal/cli/install.rb +13 -7
- data/lib/appsignal/config.rb +1 -2
- data/lib/appsignal/event_formatter.rb +4 -5
- data/lib/appsignal/event_formatter/moped/query_formatter.rb +60 -59
- data/lib/appsignal/extension.rb +2 -2
- data/lib/appsignal/helpers/instrumentation.rb +485 -0
- data/lib/appsignal/helpers/metrics.rb +55 -0
- data/lib/appsignal/hooks.rb +9 -8
- data/lib/appsignal/hooks/puma.rb +65 -9
- data/lib/appsignal/hooks/sidekiq.rb +90 -0
- data/lib/appsignal/integrations/mongo_ruby_driver.rb +7 -0
- data/lib/appsignal/integrations/railtie.rb +2 -1
- data/lib/appsignal/marker.rb +2 -3
- data/lib/appsignal/minutely.rb +164 -14
- data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -1
- data/lib/appsignal/system.rb +16 -18
- data/lib/appsignal/utils/rails_helper.rb +16 -0
- data/lib/appsignal/version.rb +1 -1
- data/spec/lib/appsignal/cli/diagnose_spec.rb +129 -22
- data/spec/lib/appsignal/cli/install_spec.rb +6 -1
- data/spec/lib/appsignal/config_spec.rb +3 -3
- data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +6 -0
- data/spec/lib/appsignal/event_formatter_spec.rb +168 -69
- data/spec/lib/appsignal/hooks/puma_spec.rb +129 -0
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +147 -0
- data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +24 -1
- data/spec/lib/appsignal/minutely_spec.rb +251 -21
- data/spec/lib/appsignal/system_spec.rb +0 -35
- data/spec/lib/appsignal/utils/hash_sanitizer_spec.rb +39 -31
- data/spec/lib/appsignal/utils/json_spec.rb +7 -3
- data/spec/lib/appsignal_spec.rb +27 -2
- data/spec/spec_helper.rb +13 -0
- data/spec/support/helpers/log_helpers.rb +6 -0
- data/spec/support/project_fixture/config/appsignal.yml +1 -0
- data/spec/support/stubs/sidekiq/api.rb +4 -0
- 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::
|
35
|
+
Appsignal.logger.debug "Initializing Appsignal::Rack::SinatraBaseInstrumentation"
|
36
36
|
@app = app
|
37
37
|
@options = options
|
38
38
|
@raise_errors_on = raise_errors?(@app)
|
data/lib/appsignal/system.rb
CHANGED
@@ -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
|
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
|
63
|
-
if ldd_version && versionify(ldd_version
|
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
|
data/lib/appsignal/version.rb
CHANGED
@@ -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 "
|
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 "
|
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/
|
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)
|
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 =>
|
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
|
14
|
-
|
15
|
-
|
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
|
-
|
30
|
-
|
34
|
+
def format(_payload)
|
35
|
+
end
|
36
|
+
end
|
31
37
|
|
32
38
|
describe Appsignal::EventFormatter do
|
33
|
-
|
34
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
64
|
-
|
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
|
-
|
68
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
77
|
-
|
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
|
-
|
81
|
-
|
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
|
-
|
84
|
-
|
124
|
+
def format(_payload)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
85
128
|
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
91
|
-
|
92
|
-
.
|
93
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
"
|
101
|
-
|
102
|
-
|
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
|
-
|
105
|
-
|
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
|
-
|
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
|
-
|
111
|
-
|
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
|
-
|
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
|
-
|
120
|
-
|
121
|
-
|
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
|
-
|
125
|
-
|
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
|