appsignal 2.11.0.beta.2-java → 2.11.1.beta.2-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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/.semaphore/semaphore.yml +57 -1
  3. data/CHANGELOG.md +28 -0
  4. data/README.md +11 -5
  5. data/Rakefile +27 -9
  6. data/appsignal.gemspec +1 -1
  7. data/build_matrix.yml +2 -2
  8. data/ext/Rakefile +2 -0
  9. data/ext/agent.yml +17 -25
  10. data/ext/appsignal_extension.c +1 -1
  11. data/ext/base.rb +7 -0
  12. data/ext/extconf.rb +2 -0
  13. data/lib/appsignal.rb +1 -0
  14. data/lib/appsignal/auth_check.rb +4 -2
  15. data/lib/appsignal/cli/diagnose.rb +1 -1
  16. data/lib/appsignal/config.rb +82 -17
  17. data/lib/appsignal/extension.rb +6 -5
  18. data/lib/appsignal/extension/jruby.rb +6 -5
  19. data/lib/appsignal/hooks.rb +24 -0
  20. data/lib/appsignal/hooks/action_mailer.rb +22 -0
  21. data/lib/appsignal/hooks/active_job.rb +53 -5
  22. data/lib/appsignal/hooks/active_support_notifications.rb +72 -0
  23. data/lib/appsignal/hooks/puma.rb +0 -1
  24. data/lib/appsignal/hooks/sidekiq.rb +1 -2
  25. data/lib/appsignal/integrations/delayed_job_plugin.rb +1 -1
  26. data/lib/appsignal/probes.rb +7 -0
  27. data/lib/appsignal/probes/puma.rb +1 -1
  28. data/lib/appsignal/probes/sidekiq.rb +3 -1
  29. data/lib/appsignal/utils/deprecation_message.rb +1 -1
  30. data/lib/appsignal/version.rb +1 -1
  31. data/spec/lib/appsignal/auth_check_spec.rb +23 -0
  32. data/spec/lib/appsignal/capistrano2_spec.rb +1 -1
  33. data/spec/lib/appsignal/capistrano3_spec.rb +1 -1
  34. data/spec/lib/appsignal/cli/diagnose_spec.rb +42 -0
  35. data/spec/lib/appsignal/config_spec.rb +39 -1
  36. data/spec/lib/appsignal/extension/jruby_spec.rb +31 -28
  37. data/spec/lib/appsignal/extension_install_failure_spec.rb +23 -0
  38. data/spec/lib/appsignal/hooks/action_mailer_spec.rb +54 -0
  39. data/spec/lib/appsignal/hooks/active_support_notifications/finish_with_state_shared_examples.rb +35 -0
  40. data/spec/lib/appsignal/hooks/active_support_notifications/instrument_shared_examples.rb +145 -0
  41. data/spec/lib/appsignal/hooks/active_support_notifications/start_finish_shared_examples.rb +69 -0
  42. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +9 -137
  43. data/spec/lib/appsignal/hooks/activejob_spec.rb +143 -10
  44. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +3 -14
  45. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +7 -5
  46. data/spec/lib/appsignal/hooks_spec.rb +57 -0
  47. data/spec/lib/appsignal/marker_spec.rb +1 -1
  48. data/spec/spec_helper.rb +5 -0
  49. data/spec/support/helpers/config_helpers.rb +3 -2
  50. data/spec/support/helpers/dependency_helper.rb +4 -0
  51. data/spec/support/helpers/transaction_helpers.rb +1 -1
  52. data/spec/support/testing.rb +19 -19
  53. metadata +19 -7
@@ -179,11 +179,12 @@ module Appsignal
179
179
  :appsignal_string
180
180
 
181
181
  Appsignal.extension_loaded = true
182
- rescue LoadError => err
183
- Appsignal.logger.error(
184
- "Failed to load extension (#{err}), please email us at " \
185
- "support@appsignal.com"
186
- )
182
+ rescue LoadError => error
183
+ error_message = "ERROR: AppSignal failed to load extension. " \
184
+ "Please run `appsignal diagnose` and email us at support@appsignal.com\n" \
185
+ "#{error.class}: #{error.message}"
186
+ Appsignal.logger.error(error_message)
187
+ Kernel.warn error_message
187
188
  Appsignal.extension_loaded = false
188
189
  end
189
190
 
@@ -69,10 +69,34 @@ module Appsignal
69
69
  text.size > 200 ? "#{text[0...197]}..." : text
70
70
  end
71
71
  end
72
+
73
+ # Alias Probes constants that have moved to their own module in version
74
+ # 2.11.0.
75
+ def self.const_missing(name)
76
+ case name
77
+ when :SidekiqProbe
78
+ callers = caller
79
+ Appsignal::Utils::DeprecationMessage.message \
80
+ "The constant Appsignal::Hooks::SidekiqProbe has been deprecated. " \
81
+ "Please update the constant name to Appsignal::Probes::SidekiqProbe " \
82
+ "in the following file to remove this message.\n#{callers.first}"
83
+ Appsignal::Probes::SidekiqProbe
84
+ when :PumaProbe
85
+ callers = caller
86
+ Appsignal::Utils::DeprecationMessage.message \
87
+ "The constant Appsignal::Hooks::PumaProbe has been deprecated. " \
88
+ "Please update the constant name to Appsignal::Probes::PumaProbe " \
89
+ "in the following file to remove this message.\n#{callers.first}"
90
+ Appsignal::Probes::PumaProbe
91
+ else
92
+ super
93
+ end
94
+ end
72
95
  end
73
96
  end
74
97
 
75
98
  require "appsignal/hooks/action_cable"
99
+ require "appsignal/hooks/action_mailer"
76
100
  require "appsignal/hooks/active_job"
77
101
  require "appsignal/hooks/active_support_notifications"
78
102
  require "appsignal/hooks/celluloid"
@@ -0,0 +1,22 @@
1
+ module Appsignal
2
+ class Hooks
3
+ class ActionMailerHook < Appsignal::Hooks::Hook
4
+ register :action_mailer
5
+
6
+ def dependencies_present?
7
+ defined?(::ActionMailer)
8
+ end
9
+
10
+ def install
11
+ ActiveSupport::Notifications
12
+ .subscribe("process.action_mailer") do |_, _, _, _, payload|
13
+ Appsignal.increment_counter(
14
+ :action_mailer_process,
15
+ 1,
16
+ :mailer => payload[:mailer], :action => payload[:action]
17
+ )
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -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
@@ -23,6 +23,21 @@ module Appsignal
23
23
  end
24
24
  end
25
25
 
26
+ instrumenter = ::ActiveSupport::Notifications::Instrumenter
27
+
28
+ if instrumenter.method_defined?(:start) && instrumenter.method_defined?(:finish)
29
+ install_start_finish
30
+ else
31
+ install_instrument
32
+ end
33
+
34
+ # rubocop:disable Style/GuardClause
35
+ if instrumenter.method_defined?(:finish_with_state)
36
+ install_finish_with_state
37
+ end
38
+ end
39
+
40
+ def install_instrument
26
41
  ::ActiveSupport::Notifications::Instrumenter.class_eval do
27
42
  alias instrument_without_appsignal instrument
28
43
 
@@ -46,6 +61,63 @@ module Appsignal
46
61
  end
47
62
  end
48
63
  end
64
+
65
+ def install_start_finish
66
+ ::ActiveSupport::Notifications::Instrumenter.class_eval do
67
+ alias start_without_appsignal start
68
+
69
+ def start(name, payload = {})
70
+ # Events that start with a bang are internal to Rails
71
+ instrument_this = name[0] != BANG
72
+
73
+ Appsignal::Transaction.current.start_event if instrument_this
74
+
75
+ start_without_appsignal(name, payload)
76
+ end
77
+
78
+ alias finish_without_appsignal finish
79
+
80
+ def finish(name, payload = {})
81
+ # Events that start with a bang are internal to Rails
82
+ instrument_this = name[0] != BANG
83
+
84
+ if instrument_this
85
+ title, body, body_format = Appsignal::EventFormatter.format(name, payload)
86
+ Appsignal::Transaction.current.finish_event(
87
+ name.to_s,
88
+ title,
89
+ body,
90
+ body_format
91
+ )
92
+ end
93
+
94
+ finish_without_appsignal(name, payload)
95
+ end
96
+ end
97
+ end
98
+
99
+ def install_finish_with_state
100
+ ::ActiveSupport::Notifications::Instrumenter.class_eval do
101
+ alias finish_with_state_without_appsignal finish_with_state
102
+
103
+ def finish_with_state(listeners_state, name, payload = {})
104
+ # Events that start with a bang are internal to Rails
105
+ instrument_this = name[0] != BANG
106
+
107
+ if instrument_this
108
+ title, body, body_format = Appsignal::EventFormatter.format(name, payload)
109
+ Appsignal::Transaction.current.finish_event(
110
+ name.to_s,
111
+ title,
112
+ body,
113
+ body_format
114
+ )
115
+ end
116
+
117
+ finish_with_state_without_appsignal(listeners_state, name, payload)
118
+ end
119
+ end
120
+ end
49
121
  end
50
122
  end
51
123
  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.2".freeze
4
+ VERSION = "2.11.1.beta.2".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