appsignal 2.11.0.beta.1 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.semaphore/semaphore.yml +57 -1
  3. data/CHANGELOG.md +25 -0
  4. data/README.md +4 -4
  5. data/Rakefile +16 -4
  6. data/appsignal.gemspec +1 -1
  7. data/build_matrix.yml +2 -2
  8. data/ext/Rakefile +2 -0
  9. data/ext/agent.yml +19 -19
  10. data/ext/base.rb +7 -0
  11. data/ext/extconf.rb +2 -0
  12. data/lib/appsignal.rb +1 -0
  13. data/lib/appsignal/auth_check.rb +4 -2
  14. data/lib/appsignal/cli/diagnose.rb +1 -1
  15. data/lib/appsignal/config.rb +82 -17
  16. data/lib/appsignal/extension.rb +6 -5
  17. data/lib/appsignal/extension/jruby.rb +6 -5
  18. data/lib/appsignal/hooks.rb +23 -0
  19. data/lib/appsignal/hooks/active_job.rb +53 -5
  20. data/lib/appsignal/hooks/puma.rb +0 -1
  21. data/lib/appsignal/hooks/sidekiq.rb +1 -2
  22. data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
  23. data/lib/appsignal/probes.rb +7 -0
  24. data/lib/appsignal/probes/puma.rb +1 -1
  25. data/lib/appsignal/probes/sidekiq.rb +3 -1
  26. data/lib/appsignal/utils/deprecation_message.rb +1 -1
  27. data/lib/appsignal/version.rb +1 -1
  28. data/spec/lib/appsignal/auth_check_spec.rb +23 -0
  29. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  30. data/spec/lib/appsignal/capistrano3_spec.rb +1 -1
  31. data/spec/lib/appsignal/cli/diagnose_spec.rb +42 -0
  32. data/spec/lib/appsignal/config_spec.rb +39 -1
  33. data/spec/lib/appsignal/extension/jruby_spec.rb +31 -28
  34. data/spec/lib/appsignal/extension_install_failure_spec.rb +23 -0
  35. data/spec/lib/appsignal/hooks/activejob_spec.rb +143 -10
  36. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +3 -14
  37. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +7 -5
  38. data/spec/lib/appsignal/hooks_spec.rb +57 -0
  39. data/spec/lib/appsignal/marker_spec.rb +1 -1
  40. data/spec/spec_helper.rb +5 -0
  41. data/spec/support/helpers/config_helpers.rb +3 -2
  42. data/spec/support/helpers/transaction_helpers.rb +1 -1
  43. data/spec/support/testing.rb +19 -19
  44. metadata +8 -5
@@ -15,10 +15,9 @@ module Appsignal
15
15
  .extend ::Appsignal::Hooks::ActiveJobHook::ActiveJobClassInstrumentation
16
16
  end
17
17
 
18
- # @todo Add queue time support for Rails 6's enqueued_at. For both
19
- # existing and new transactions.
20
18
  module ActiveJobClassInstrumentation
21
19
  def execute(job)
20
+ job_status = nil
22
21
  current_transaction = Appsignal::Transaction.current
23
22
  transaction =
24
23
  if current_transaction.nil_transaction?
@@ -38,6 +37,7 @@ module Appsignal
38
37
 
39
38
  super
40
39
  rescue Exception => exception # rubocop:disable Lint/RescueException
40
+ job_status = :failed
41
41
  transaction.set_error(exception)
42
42
  raise exception
43
43
  ensure
@@ -48,10 +48,13 @@ module Appsignal
48
48
  Appsignal.config[:filter_parameters]
49
49
  )
50
50
 
51
- tags = { :queue => job["queue_name"] }
51
+ transaction_tags = ActiveJobHelpers.transaction_tags_for(job)
52
+ transaction_tags["active_job_id"] = job["job_id"]
52
53
  provider_job_id = job["provider_job_id"]
53
- tags[:provider_job_id] = provider_job_id if provider_job_id
54
- transaction.set_tags(tags)
54
+ if provider_job_id
55
+ transaction_tags[:provider_job_id] = provider_job_id
56
+ end
57
+ transaction.set_tags(transaction_tags)
55
58
 
56
59
  transaction.set_action_if_nil(ActiveJobHelpers.action_name(job))
57
60
  enqueued_at = job["enqueued_at"]
@@ -65,6 +68,16 @@ module Appsignal
65
68
  Appsignal::Transaction.complete_current!
66
69
  end
67
70
  end
71
+
72
+ metrics = ActiveJobHelpers.metrics_for(job)
73
+ metrics.each do |(metric_name, tags)|
74
+ if job_status
75
+ ActiveJobHelpers.increment_counter metric_name, 1,
76
+ tags.merge(:status => job_status)
77
+ end
78
+ ActiveJobHelpers.increment_counter metric_name, 1,
79
+ tags.merge(:status => :processed)
80
+ end
68
81
  end
69
82
  end
70
83
 
@@ -83,6 +96,41 @@ module Appsignal
83
96
  "#{job["job_class"]}#perform"
84
97
  end
85
98
  end
99
+
100
+ # Returns an array of metrics with tags used to report the job metrics
101
+ #
102
+ # If job ONLY has a queue, it will return `queue_job_count` with tags.
103
+ # If job has a queue AND priority, it will ALSO return
104
+ # `queue_priority_job_count` with tags.
105
+ #
106
+ # @return [Array] Array of metrics with tags to report.
107
+ def self.metrics_for(job)
108
+ tags = { :queue => job["queue_name"] }
109
+ metrics = [["queue_job_count", tags]]
110
+
111
+ priority = job["priority"]
112
+ if priority
113
+ metrics << [
114
+ "queue_priority_job_count",
115
+ tags.merge(:priority => priority)
116
+ ]
117
+ end
118
+
119
+ metrics
120
+ end
121
+
122
+ def self.transaction_tags_for(job)
123
+ tags = {}
124
+ queue = job["queue_name"]
125
+ tags[:queue] = queue if queue
126
+ priority = job["priority"]
127
+ tags[:priority] = priority if priority
128
+ tags
129
+ end
130
+
131
+ def self.increment_counter(key, value, tags = {})
132
+ Appsignal.increment_counter "active_job_#{key}", value, tags
133
+ end
86
134
  end
87
135
  end
88
136
  end
@@ -24,7 +24,6 @@ module Appsignal
24
24
  # runs in the Puma main process.
25
25
  # For more information:
26
26
  # https://docs.appsignal.com/ruby/integrations/puma.html
27
- require "appsignal/probes/puma"
28
27
  Appsignal::Minutely.probes.register :puma, ::Appsignal::Probes::PumaProbe
29
28
  end
30
29
 
@@ -12,7 +12,6 @@ module Appsignal
12
12
  end
13
13
 
14
14
  def install
15
- require "appsignal/probes/sidekiq"
16
15
  Appsignal::Minutely.probes.register :sidekiq, Appsignal::Probes::SidekiqProbe
17
16
 
18
17
  ::Sidekiq.configure_server do |config|
@@ -35,7 +34,7 @@ module Appsignal
35
34
  def call(_worker, item, _queue)
36
35
  job_status = nil
37
36
  transaction = Appsignal::Transaction.create(
38
- SecureRandom.uuid,
37
+ item["jid"],
39
38
  Appsignal::Transaction::BACKGROUND_JOB,
40
39
  Appsignal::Transaction::GenericRequest.new(
41
40
  :queue_start => item["enqueued_at"]
@@ -64,7 +64,7 @@ module Appsignal
64
64
  dot_split = default_name.split(".")
65
65
  return default_name if dot_split.length == 2
66
66
 
67
- ["unknown"]
67
+ "#{default_name}#perform"
68
68
  end
69
69
 
70
70
  def self.extract_value(object_or_hash, field, default_value = nil, convert_to_s = false)
@@ -0,0 +1,7 @@
1
+ module Appsignal
2
+ module Probes
3
+ end
4
+ end
5
+
6
+ require "appsignal/probes/puma"
7
+ require "appsignal/probes/sidekiq"
@@ -1,11 +1,11 @@
1
1
  module Appsignal
2
2
  module Probes
3
- # @api private
4
3
  class PumaProbe
5
4
  def initialize
6
5
  @hostname = Appsignal.config[:hostname] || Socket.gethostname
7
6
  end
8
7
 
8
+ # @api private
9
9
  def call
10
10
  puma_stats = fetch_puma_stats
11
11
  return unless puma_stats
@@ -1,9 +1,10 @@
1
1
  module Appsignal
2
2
  module Probes
3
- # @api private
4
3
  class SidekiqProbe
4
+ # @api private
5
5
  attr_reader :config
6
6
 
7
+ # @api private
7
8
  def self.dependencies_present?
8
9
  Gem::Version.new(::Redis::VERSION) >= Gem::Version.new("3.3.5")
9
10
  end
@@ -16,6 +17,7 @@ module Appsignal
16
17
  require "sidekiq/api"
17
18
  end
18
19
 
20
+ # @api private
19
21
  def call
20
22
  track_redis_info
21
23
  track_stats
@@ -2,7 +2,7 @@ module Appsignal
2
2
  module Utils
3
3
  module DeprecationMessage
4
4
  def self.message(message, logger = Appsignal.logger)
5
- $stderr.puts "appsignal WARNING: #{message}"
5
+ Kernel.warn "appsignal WARNING: #{message}"
6
6
  logger.warn message
7
7
  end
8
8
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "2.11.0.beta.1".freeze
4
+ VERSION = "2.11.0".freeze
5
5
  end
@@ -28,6 +28,29 @@ describe Appsignal::AuthCheck do
28
28
  end.join("&")
29
29
  end
30
30
 
31
+ describe ".new" do
32
+ describe "with logger argument" do
33
+ let(:err_stream) { std_stream }
34
+ let(:stderr) { err_stream.read }
35
+ let(:log_stream) { std_stream }
36
+ let(:log) { log_contents(log_stream) }
37
+
38
+ it "logs and prints a deprecation message" do
39
+ Appsignal.logger = test_logger(log_stream)
40
+
41
+ capture_std_streams(std_stream, err_stream) do
42
+ Appsignal::AuthCheck.new(config, Appsignal.logger)
43
+ end
44
+
45
+ deprecation_message =
46
+ "`Appsignal::AuthCheck.new`'s `logger` argument " \
47
+ "will be removed in the next major version."
48
+ expect(stderr).to include "appsignal WARNING: #{deprecation_message}"
49
+ expect(log).to contains_log :warn, deprecation_message
50
+ end
51
+ end
52
+ end
53
+
31
54
  describe "#perform" do
32
55
  subject { auth_check.perform }
33
56
 
@@ -10,7 +10,7 @@ if DependencyHelper.capistrano2_present?
10
10
  let(:capistrano_config) do
11
11
  Capistrano::Configuration.new.tap do |c|
12
12
  c.set(:rails_env, "production")
13
- c.set(:repository, "master")
13
+ c.set(:repository, "main")
14
14
  c.set(:deploy_to, "/home/username/app")
15
15
  c.set(:current_release, "")
16
16
  c.set(:current_revision, "503ce0923ed177a3ce000005")
@@ -15,7 +15,7 @@ if DependencyHelper.capistrano3_present?
15
15
  c.set(:log_level, :error)
16
16
  c.set(:logger, logger)
17
17
  c.set(:rails_env, "production")
18
- c.set(:repository, "master")
18
+ c.set(:repository, "main")
19
19
  c.set(:deploy_to, "/home/username/app")
20
20
  c.set(:current_release, "")
21
21
  c.set(:current_revision, "503ce0923ed177a3ce000005")
@@ -290,6 +290,8 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
290
290
  jruby = Appsignal::System.jruby?
291
291
  expect(output).to include(
292
292
  "Extension installation report",
293
+ "Installation result",
294
+ " Status: success",
293
295
  "Language details",
294
296
  " Implementation: #{jruby ? "jruby" : "ruby"}",
295
297
  " Ruby version: #{"#{rbconfig["ruby_version"]}-p#{rbconfig["PATCHLEVEL"]}"}",
@@ -310,6 +312,46 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
310
312
  )
311
313
  end
312
314
 
315
+ context "with error in install report" do
316
+ let(:error) { RuntimeError.new("some error") }
317
+ before do
318
+ allow(File).to receive(:read).and_call_original
319
+ expect(File).to receive(:read)
320
+ .with(File.expand_path("../../../../../ext/install.report", __FILE__))
321
+ .and_return(
322
+ YAML.dump(
323
+ "result" => {
324
+ "status" => "error",
325
+ "error" => "RuntimeError: some error",
326
+ "backtrace" => error.backtrace
327
+ }
328
+ )
329
+ )
330
+ end
331
+
332
+ it "sends an error" do
333
+ run
334
+ expect(received_report["installation"]).to match(
335
+ "result" => {
336
+ "status" => "error",
337
+ "error" => "RuntimeError: some error",
338
+ "backtrace" => error.backtrace
339
+ }
340
+ )
341
+ end
342
+
343
+ it "prints the error" do
344
+ run
345
+
346
+ expect(output).to include(
347
+ "Extension installation report",
348
+ "Installation result",
349
+ "Status: error\n Error: RuntimeError: some error"
350
+ )
351
+ expect(output).to_not include("Raw report:")
352
+ end
353
+ end
354
+
313
355
  context "without install report" do
314
356
  let(:error) { RuntimeError.new("foo") }
315
357
  before do
@@ -1,4 +1,18 @@
1
1
  describe Appsignal::Config do
2
+ describe "config keys" do
3
+ it "all config keys have an environment variable version registered" do
4
+ config = Appsignal::Config
5
+ mapped_env_keys = config::ENV_TO_KEY_MAPPING.keys.sort
6
+ configured_env_keys = (
7
+ config::ENV_STRING_KEYS +
8
+ config::ENV_BOOLEAN_KEYS +
9
+ config::ENV_ARRAY_KEYS
10
+ ).sort
11
+
12
+ expect(mapped_env_keys).to eql(configured_env_keys)
13
+ end
14
+ end
15
+
2
16
  describe "#initialize" do
3
17
  describe "environment" do
4
18
  context "when environment is nil" do
@@ -244,6 +258,27 @@ describe Appsignal::Config do
244
258
  end
245
259
  end
246
260
 
261
+ context "with an overriden config file" do
262
+ let(:config) do
263
+ project_fixture_config("production", {}, Appsignal.logger, File.join(project_fixture_path, "config", "appsignal.yml"))
264
+ end
265
+
266
+ it "is valid and active" do
267
+ expect(config.valid?).to be_truthy
268
+ expect(config.active?).to be_truthy
269
+ end
270
+
271
+ context "with an invalid overriden config file" do
272
+ let(:config) do
273
+ project_fixture_config("production", {}, Appsignal.logger, File.join(project_fixture_path, "config", "missing.yml"))
274
+ end
275
+
276
+ it "is not valid" do
277
+ expect(config.valid?).to be_falsy
278
+ end
279
+ end
280
+ end
281
+
247
282
  context "with the config file causing an error" do
248
283
  let(:config_path) do
249
284
  File.expand_path(
@@ -397,6 +432,7 @@ describe Appsignal::Config do
397
432
  :debug => true
398
433
  )
399
434
  end
435
+ let(:working_directory_path) { File.join(tmp_dir, "test_working_directory_path") }
400
436
  let(:env_config) do
401
437
  {
402
438
  :running_in_container => true,
@@ -413,7 +449,8 @@ describe Appsignal::Config do
413
449
  :files_world_accessible => false,
414
450
  :request_headers => %w[accept accept-charset],
415
451
  :revision => "v2.5.1",
416
- :send_environment_metadata => false
452
+ :send_environment_metadata => false,
453
+ :working_directory_path => working_directory_path
417
454
  }
418
455
  end
419
456
  before do
@@ -431,6 +468,7 @@ describe Appsignal::Config do
431
468
  ENV["APPSIGNAL_FILES_WORLD_ACCESSIBLE"] = "false"
432
469
  ENV["APPSIGNAL_REQUEST_HEADERS"] = "accept,accept-charset"
433
470
  ENV["APPSIGNAL_SEND_ENVIRONMENT_METADATA"] = "false"
471
+ ENV["APPSIGNAL_WORKING_DIRECTORY_PATH"] = working_directory_path
434
472
  ENV["APP_REVISION"] = "v2.5.1"
435
473
  end
436
474
 
@@ -1,42 +1,45 @@
1
- if Appsignal::System.jruby?
2
- describe Appsignal::Extension::Jruby do
3
- let(:extension) { Appsignal::Extension }
1
+ describe "JRuby extension", :jruby do
2
+ let(:extension) { Appsignal::Extension }
3
+ let(:jruby_module) { Appsignal::Extension::Jruby }
4
4
 
5
- describe "string conversions" do
6
- it "keeps the same value during string type conversions" do
7
- # UTF-8 string with NULL
8
- # Tests if the conversions between the conversions without breaking on
9
- # NULL terminated strings in C.
10
- string = "Merry Christmas! \u0000 🎄"
5
+ it "creates a JRuby extension module" do
6
+ expect(Appsignal::Extension::Jruby).to be_kind_of(Module)
7
+ end
11
8
 
12
- appsignal_string = extension.make_appsignal_string(string)
13
- ruby_string = extension.make_ruby_string(appsignal_string)
9
+ describe "string conversions" do
10
+ it "keeps the same value during string type conversions" do
11
+ # UTF-8 string with NULL
12
+ # Tests if the conversions between the conversions without breaking on
13
+ # NULL terminated strings in C.
14
+ string = "Merry Christmas! \u0000 🎄"
14
15
 
15
- expect(ruby_string).to eq("Merry Christmas! \u0000 🎄")
16
- end
17
- end
16
+ appsignal_string = extension.make_appsignal_string(string)
17
+ ruby_string = extension.make_ruby_string(appsignal_string)
18
18
 
19
- it "loads libappsignal with FFI" do
20
- expect(described_class.ffi_libraries.map(&:name).first).to include "libappsignal"
19
+ expect(ruby_string).to eq("Merry Christmas! \u0000 🎄")
21
20
  end
21
+ end
22
22
 
23
- describe ".lib_extension" do
24
- subject { described_class.lib_extension }
23
+ it "loads libappsignal with FFI" do
24
+ expect(jruby_module.ffi_libraries.map(&:name).first).to include "libappsignal"
25
+ end
26
+
27
+ describe ".lib_extension" do
28
+ subject { jruby_module.lib_extension }
25
29
 
26
- context "when on a darwin system" do
27
- before { expect(Appsignal::System).to receive(:agent_platform).and_return("darwin") }
30
+ context "when on a darwin system" do
31
+ before { expect(Appsignal::System).to receive(:agent_platform).and_return("darwin") }
28
32
 
29
- it "returns the extension for darwin" do
30
- is_expected.to eq "dylib"
31
- end
33
+ it "returns the extension for darwin" do
34
+ is_expected.to eq "dylib"
32
35
  end
36
+ end
33
37
 
34
- context "when on a linux system" do
35
- before { expect(Appsignal::System).to receive(:agent_platform).and_return("linux") }
38
+ context "when on a linux system" do
39
+ before { expect(Appsignal::System).to receive(:agent_platform).and_return("linux") }
36
40
 
37
- it "returns the lib extension for linux" do
38
- is_expected.to eq "so"
39
- end
41
+ it "returns the lib extension for linux" do
42
+ is_expected.to eq "so"
40
43
  end
41
44
  end
42
45
  end
@@ -0,0 +1,23 @@
1
+ describe Appsignal::Extension, :extension_installation_failure do
2
+ context "when the extension library cannot be loaded" do
3
+ # This test breaks the installation on purpose and is not run by default.
4
+ # See `rake test:failure`. If this test was run, run `rake
5
+ # extension:install` again to fix the extension installation.
6
+ it "prints and logs an error" do
7
+ # ENV var to make sure installation fails on purpurse
8
+ ENV["_TEST_APPSIGNAL_EXTENSION_FAILURE"] = "true"
9
+ `rake extension:install` # Run installation
10
+
11
+ require "open3"
12
+ _stdout, stderr, _status = Open3.capture3("bin/appsignal --version")
13
+ expect(stderr).to include("ERROR: AppSignal failed to load extension")
14
+ error_message =
15
+ if DependencyHelper.running_jruby?
16
+ "cannot open shared object file"
17
+ else
18
+ "LoadError: cannot load such file"
19
+ end
20
+ expect(stderr).to include(error_message)
21
+ end
22
+ end
23
+ end