appsignal 3.0.0.beta.1-java → 3.0.3-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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +1 -1
  3. data/.semaphore/semaphore.yml +88 -88
  4. data/CHANGELOG.md +41 -1
  5. data/Rakefile +12 -4
  6. data/appsignal.gemspec +7 -5
  7. data/build_matrix.yml +11 -11
  8. data/ext/agent.yml +17 -17
  9. data/gemfiles/no_dependencies.gemfile +0 -7
  10. data/lib/appsignal.rb +1 -2
  11. data/lib/appsignal/config.rb +1 -1
  12. data/lib/appsignal/extension.rb +50 -0
  13. data/lib/appsignal/helpers/instrumentation.rb +69 -5
  14. data/lib/appsignal/hooks.rb +16 -0
  15. data/lib/appsignal/hooks/action_cable.rb +10 -2
  16. data/lib/appsignal/hooks/sidekiq.rb +9 -142
  17. data/lib/appsignal/integrations/object.rb +21 -43
  18. data/lib/appsignal/integrations/railtie.rb +0 -4
  19. data/lib/appsignal/integrations/sidekiq.rb +171 -0
  20. data/lib/appsignal/minutely.rb +6 -0
  21. data/lib/appsignal/transaction.rb +2 -2
  22. data/lib/appsignal/version.rb +1 -1
  23. data/spec/lib/appsignal/config_spec.rb +2 -0
  24. data/spec/lib/appsignal/extension_install_failure_spec.rb +0 -7
  25. data/spec/lib/appsignal/extension_spec.rb +43 -9
  26. data/spec/lib/appsignal/hooks/action_cable_spec.rb +88 -0
  27. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +60 -458
  28. data/spec/lib/appsignal/hooks_spec.rb +41 -0
  29. data/spec/lib/appsignal/integrations/object_spec.rb +91 -4
  30. data/spec/lib/appsignal/integrations/sidekiq_spec.rb +524 -0
  31. data/spec/lib/appsignal/transaction_spec.rb +17 -0
  32. data/spec/lib/appsignal/utils/data_spec.rb +133 -87
  33. data/spec/lib/appsignal_spec.rb +162 -47
  34. data/spec/lib/puma/appsignal_spec.rb +28 -0
  35. data/spec/spec_helper.rb +22 -0
  36. data/spec/support/testing.rb +11 -1
  37. metadata +9 -8
  38. data/gemfiles/rails-4.0.gemfile +0 -6
  39. data/gemfiles/rails-4.1.gemfile +0 -6
@@ -69,6 +69,22 @@ module Appsignal
69
69
  text.size > 200 ? "#{text[0...197]}..." : text
70
70
  end
71
71
  end
72
+
73
+ # Alias integration constants that have moved to their own module.
74
+ def self.const_missing(name)
75
+ case name
76
+ when :SidekiqPlugin
77
+ require "appsignal/integrations/sidekiq"
78
+ callers = caller
79
+ Appsignal::Utils::DeprecationMessage.message \
80
+ "The constant Appsignal::Hooks::SidekiqPlugin has been deprecated. " \
81
+ "Please update the constant name to Appsignal::Integrations::SidekiqMiddleware " \
82
+ "in the following file to remove this message.\n#{callers.first}"
83
+ Appsignal::Integrations::SidekiqMiddleware
84
+ else
85
+ super
86
+ end
87
+ end
72
88
  end
73
89
  end
74
90
 
@@ -25,7 +25,11 @@ module Appsignal
25
25
  def install_callbacks
26
26
  ActionCable::Channel::Base.set_callback :subscribe, :around, :prepend => true do |channel, inner|
27
27
  # The request is only the original websocket request
28
- env = channel.connection.env
28
+ connection = channel.connection
29
+ # #env is not available on the Rails ConnectionStub class used in the
30
+ # Rails app test suite. If we call `#env` it causes an error to occur
31
+ # in apps' test suites.
32
+ env = connection.respond_to?(:env) ? connection.env : {}
29
33
  request = ActionDispatch::Request.new(env)
30
34
  env[Appsignal::Hooks::ActionCableHook::REQUEST_ID] ||=
31
35
  request.request_id || SecureRandom.uuid
@@ -53,7 +57,11 @@ module Appsignal
53
57
 
54
58
  ActionCable::Channel::Base.set_callback :unsubscribe, :around, :prepend => true do |channel, inner|
55
59
  # The request is only the original websocket request
56
- env = channel.connection.env
60
+ connection = channel.connection
61
+ # #env is not available on the Rails ConnectionStub class used in the
62
+ # Rails app test suite. If we call `#env` it causes an error to occur
63
+ # in apps' test suites.
64
+ env = connection.respond_to?(:env) ? connection.env : {}
57
65
  request = ActionDispatch::Request.new(env)
58
66
  env[Appsignal::Hooks::ActionCableHook::REQUEST_ID] ||=
59
67
  request.request_id || SecureRandom.uuid
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "yaml"
4
-
5
3
  module Appsignal
6
4
  class Hooks
7
5
  class SidekiqHook < Appsignal::Hooks::Hook
@@ -12,153 +10,22 @@ module Appsignal
12
10
  end
13
11
 
14
12
  def install
13
+ require "appsignal/integrations/sidekiq"
15
14
  Appsignal::Minutely.probes.register :sidekiq, Appsignal::Probes::SidekiqProbe
16
15
 
17
16
  ::Sidekiq.configure_server do |config|
18
- config.server_middleware do |chain|
19
- chain.add Appsignal::Hooks::SidekiqPlugin
20
- end
21
- end
22
- end
23
- end
24
-
25
- # @api private
26
- class SidekiqPlugin # rubocop:disable Metrics/ClassLength
27
- include Appsignal::Hooks::Helpers
28
-
29
- EXCLUDED_JOB_KEYS = %w[
30
- args backtrace class created_at enqueued_at error_backtrace error_class
31
- error_message failed_at jid retried_at retry wrapped
32
- ].freeze
33
-
34
- def call(_worker, item, _queue)
35
- job_status = nil
36
- transaction = Appsignal::Transaction.create(
37
- item["jid"],
38
- Appsignal::Transaction::BACKGROUND_JOB,
39
- Appsignal::Transaction::GenericRequest.new(
40
- :queue_start => item["enqueued_at"]
41
- )
42
- )
43
-
44
- Appsignal.instrument "perform_job.sidekiq" do
45
- begin
46
- yield
47
- rescue Exception => exception # rubocop:disable Lint/RescueException
48
- job_status = :failed
49
- transaction.set_error(exception)
50
- raise exception
51
- end
52
- end
53
- ensure
54
- if transaction
55
- transaction.set_action_if_nil(formatted_action_name(item))
56
-
57
- params = filtered_arguments(item)
58
- transaction.params = params if params
59
-
60
- formatted_metadata(item).each do |key, value|
61
- transaction.set_metadata key, value
62
- end
63
- transaction.set_http_or_background_queue_start
64
- Appsignal::Transaction.complete_current!
65
- queue = item["queue"] || "unknown"
66
- if job_status
67
- increment_counter "queue_job_count", 1,
68
- :queue => queue,
69
- :status => job_status
70
- end
71
- increment_counter "queue_job_count", 1,
72
- :queue => queue,
73
- :status => :processed
74
- end
75
- end
76
-
77
- private
78
-
79
- def increment_counter(key, value, tags = {})
80
- Appsignal.increment_counter "sidekiq_#{key}", value, tags
81
- end
17
+ config.error_handlers << \
18
+ Appsignal::Integrations::SidekiqErrorHandler.new
82
19
 
83
- def formatted_action_name(job)
84
- sidekiq_action_name = parse_action_name(job)
85
- return unless sidekiq_action_name
86
-
87
- complete_action = sidekiq_action_name =~ /\.|#/
88
- return sidekiq_action_name if complete_action
89
-
90
- "#{sidekiq_action_name}#perform"
91
- end
92
-
93
- def filtered_arguments(job)
94
- arguments = parse_arguments(job)
95
- return unless arguments
96
-
97
- Appsignal::Utils::HashSanitizer.sanitize(
98
- arguments,
99
- Appsignal.config[:filter_parameters]
100
- )
101
- end
102
-
103
- def formatted_metadata(item)
104
- {}.tap do |hash|
105
- (item || {}).each do |key, value|
106
- next if EXCLUDED_JOB_KEYS.include?(key)
107
-
108
- hash[key] = truncate(string_or_inspect(value))
109
- end
110
- end
111
- end
112
-
113
- # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L316-L334
114
- def parse_action_name(job)
115
- args = job.fetch("args", [])
116
- job_class = job["class"]
117
- case job_class
118
- when "Sidekiq::Extensions::DelayedModel"
119
- safe_load(args[0], job_class) do |target, method, _|
120
- "#{target.class}##{method}"
121
- end
122
- when /\ASidekiq::Extensions::Delayed/
123
- safe_load(args[0], job_class) do |target, method, _|
124
- "#{target}.#{method}"
125
- end
126
- else
127
- job_class
128
- end
129
- end
130
-
131
- # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L336-L358
132
- def parse_arguments(job)
133
- args = job.fetch("args", [])
134
- case job["class"]
135
- when /\ASidekiq::Extensions::Delayed/
136
- safe_load(args[0], args) do |_, _, arg|
137
- arg
138
- end
139
- when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
140
- nil # Set in the ActiveJob integration
141
- else
142
- # Sidekiq Enterprise argument encryption.
143
- # More information: https://github.com/mperham/sidekiq/wiki/Ent-Encryption
144
- if job["encrypt".freeze]
145
- # No point in showing 150+ bytes of random garbage
146
- args[-1] = "[encrypted data]".freeze
20
+ config.server_middleware do |chain|
21
+ if chain.respond_to? :prepend
22
+ chain.prepend Appsignal::Integrations::SidekiqMiddleware
23
+ else
24
+ chain.add Appsignal::Integrations::SidekiqMiddleware
25
+ end
147
26
  end
148
- args
149
27
  end
150
28
  end
151
-
152
- # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L403-L412
153
- def safe_load(content, default)
154
- yield(*YAML.load(content))
155
- rescue => error
156
- # Sidekiq issue #1761: in dev mode, it's possible to have jobs enqueued
157
- # which haven't been loaded into memory yet so the YAML can't be
158
- # loaded.
159
- Appsignal.logger.warn "Unable to load YAML: #{error.message}"
160
- default
161
- end
162
29
  end
163
30
  end
164
31
  end
@@ -5,56 +5,34 @@ if defined?(Appsignal)
5
5
  end
6
6
 
7
7
  class Object
8
- if Appsignal::System.ruby_2_7_or_newer?
9
- def self.appsignal_instrument_class_method(method_name, options = {})
10
- singleton_class.send \
11
- :alias_method, "appsignal_uninstrumented_#{method_name}", method_name
12
- singleton_class.send(:define_method, method_name) do |*args, **kwargs, &block|
13
- name = options.fetch(:name) do
14
- "#{method_name}.class_method.#{appsignal_reverse_class_name}.other"
15
- end
16
- Appsignal.instrument name do
17
- send "appsignal_uninstrumented_#{method_name}", *args, **kwargs, &block
18
- end
8
+ def self.appsignal_instrument_class_method(method_name, options = {})
9
+ singleton_class.send \
10
+ :alias_method, "appsignal_uninstrumented_#{method_name}", method_name
11
+ singleton_class.send(:define_method, method_name) do |*args, &block|
12
+ name = options.fetch(:name) do
13
+ "#{method_name}.class_method.#{appsignal_reverse_class_name}.other"
19
14
  end
20
- end
21
-
22
- def self.appsignal_instrument_method(method_name, options = {})
23
- alias_method "appsignal_uninstrumented_#{method_name}", method_name
24
- define_method method_name do |*args, **kwargs, &block|
25
- name = options.fetch(:name) do
26
- "#{method_name}.#{appsignal_reverse_class_name}.other"
27
- end
28
- Appsignal.instrument name do
29
- send "appsignal_uninstrumented_#{method_name}", *args, **kwargs, &block
30
- end
15
+ Appsignal.instrument name do
16
+ send "appsignal_uninstrumented_#{method_name}", *args, &block
31
17
  end
32
18
  end
33
- else
34
- def self.appsignal_instrument_class_method(method_name, options = {})
35
- singleton_class.send \
36
- :alias_method, "appsignal_uninstrumented_#{method_name}", method_name
37
- singleton_class.send(:define_method, method_name) do |*args, &block|
38
- name = options.fetch(:name) do
39
- "#{method_name}.class_method.#{appsignal_reverse_class_name}.other"
40
- end
41
- Appsignal.instrument name do
42
- send "appsignal_uninstrumented_#{method_name}", *args, &block
43
- end
44
- end
19
+
20
+ if singleton_class.respond_to?(:ruby2_keywords, true) # rubocop:disable Style/GuardClause
21
+ singleton_class.send(:ruby2_keywords, method_name)
45
22
  end
23
+ end
46
24
 
47
- def self.appsignal_instrument_method(method_name, options = {})
48
- alias_method "appsignal_uninstrumented_#{method_name}", method_name
49
- define_method method_name do |*args, &block|
50
- name = options.fetch(:name) do
51
- "#{method_name}.#{appsignal_reverse_class_name}.other"
52
- end
53
- Appsignal.instrument name do
54
- send "appsignal_uninstrumented_#{method_name}", *args, &block
55
- end
25
+ def self.appsignal_instrument_method(method_name, options = {})
26
+ alias_method "appsignal_uninstrumented_#{method_name}", method_name
27
+ define_method method_name do |*args, &block|
28
+ name = options.fetch(:name) do
29
+ "#{method_name}.#{appsignal_reverse_class_name}.other"
30
+ end
31
+ Appsignal.instrument name do
32
+ send "appsignal_uninstrumented_#{method_name}", *args, &block
56
33
  end
57
34
  end
35
+ ruby2_keywords method_name if respond_to?(:ruby2_keywords, true)
58
36
  end
59
37
 
60
38
  def self.appsignal_reverse_class_name
@@ -30,10 +30,6 @@ module Appsignal
30
30
  Appsignal::Rack::RailsInstrumentation
31
31
  )
32
32
 
33
- if Appsignal.config[:enable_frontend_error_catching]
34
- app.middleware.insert_before(Appsignal::Rack::RailsInstrumentation)
35
- end
36
-
37
33
  Appsignal.start
38
34
  end
39
35
  end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Appsignal
6
+ module Integrations
7
+ # Error handler for Sidekiq to report errors from jobs and internal Sidekiq
8
+ # errors.
9
+ #
10
+ # @api private
11
+ class SidekiqErrorHandler
12
+ def call(exception, sidekiq_context)
13
+ transaction = Appsignal::Transaction.current
14
+
15
+ if transaction.nil_transaction?
16
+ # Sidekiq error outside of the middleware scope.
17
+ # Can be a job JSON parse error or some other error happening in
18
+ # Sidekiq.
19
+ transaction = Appsignal::Transaction.create(
20
+ SecureRandom.uuid, # Newly generated job id
21
+ Appsignal::Transaction::BACKGROUND_JOB,
22
+ Appsignal::Transaction::GenericRequest.new({})
23
+ )
24
+ transaction.set_action_if_nil("SidekiqInternal")
25
+ transaction.set_metadata("sidekiq_error", sidekiq_context[:context])
26
+ transaction.params = { :jobstr => sidekiq_context[:jobstr] }
27
+ end
28
+
29
+ transaction.set_error(exception)
30
+ Appsignal::Transaction.complete_current!
31
+ end
32
+ end
33
+
34
+ # @api private
35
+ class SidekiqMiddleware # rubocop:disable Metrics/ClassLength
36
+ include Appsignal::Hooks::Helpers
37
+
38
+ EXCLUDED_JOB_KEYS = %w[
39
+ args backtrace class created_at enqueued_at error_backtrace error_class
40
+ error_message failed_at jid retried_at retry wrapped
41
+ ].freeze
42
+
43
+ def call(_worker, item, _queue)
44
+ job_status = nil
45
+ transaction = Appsignal::Transaction.create(
46
+ item["jid"],
47
+ Appsignal::Transaction::BACKGROUND_JOB,
48
+ Appsignal::Transaction::GenericRequest.new(
49
+ :queue_start => item["enqueued_at"]
50
+ )
51
+ )
52
+
53
+ Appsignal.instrument "perform_job.sidekiq" do
54
+ yield
55
+ end
56
+ rescue Exception => exception # rubocop:disable Lint/RescueException
57
+ job_status = :failed
58
+ raise exception
59
+ ensure
60
+ if transaction
61
+ transaction.set_action_if_nil(formatted_action_name(item))
62
+
63
+ params = filtered_arguments(item)
64
+ transaction.params = params if params
65
+
66
+ formatted_metadata(item).each do |key, value|
67
+ transaction.set_metadata key, value
68
+ end
69
+ transaction.set_http_or_background_queue_start
70
+ Appsignal::Transaction.complete_current! unless exception
71
+
72
+ queue = item["queue"] || "unknown"
73
+ if job_status
74
+ increment_counter "queue_job_count", 1,
75
+ :queue => queue,
76
+ :status => job_status
77
+ end
78
+ increment_counter "queue_job_count", 1,
79
+ :queue => queue,
80
+ :status => :processed
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def increment_counter(key, value, tags = {})
87
+ Appsignal.increment_counter "sidekiq_#{key}", value, tags
88
+ end
89
+
90
+ def formatted_action_name(job)
91
+ sidekiq_action_name = parse_action_name(job)
92
+ return unless sidekiq_action_name
93
+
94
+ complete_action = sidekiq_action_name =~ /\.|#/
95
+ return sidekiq_action_name if complete_action
96
+
97
+ "#{sidekiq_action_name}#perform"
98
+ end
99
+
100
+ def filtered_arguments(job)
101
+ arguments = parse_arguments(job)
102
+ return unless arguments
103
+
104
+ Appsignal::Utils::HashSanitizer.sanitize(
105
+ arguments,
106
+ Appsignal.config[:filter_parameters]
107
+ )
108
+ end
109
+
110
+ def formatted_metadata(item)
111
+ {}.tap do |hash|
112
+ (item || {}).each do |key, value|
113
+ next if EXCLUDED_JOB_KEYS.include?(key)
114
+
115
+ hash[key] = truncate(string_or_inspect(value))
116
+ end
117
+ end
118
+ end
119
+
120
+ # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L316-L334
121
+ def parse_action_name(job)
122
+ args = job.fetch("args", [])
123
+ job_class = job["class"]
124
+ case job_class
125
+ when "Sidekiq::Extensions::DelayedModel"
126
+ safe_load(args[0], job_class) do |target, method, _|
127
+ "#{target.class}##{method}"
128
+ end
129
+ when /\ASidekiq::Extensions::Delayed/
130
+ safe_load(args[0], job_class) do |target, method, _|
131
+ "#{target}.#{method}"
132
+ end
133
+ else
134
+ job_class
135
+ end
136
+ end
137
+
138
+ # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L336-L358
139
+ def parse_arguments(job)
140
+ args = job.fetch("args", [])
141
+ case job["class"]
142
+ when /\ASidekiq::Extensions::Delayed/
143
+ safe_load(args[0], args) do |_, _, arg|
144
+ arg
145
+ end
146
+ when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
147
+ nil # Set in the ActiveJob integration
148
+ else
149
+ # Sidekiq Enterprise argument encryption.
150
+ # More information: https://github.com/mperham/sidekiq/wiki/Ent-Encryption
151
+ if job["encrypt".freeze]
152
+ # No point in showing 150+ bytes of random garbage
153
+ args[-1] = "[encrypted data]".freeze
154
+ end
155
+ args
156
+ end
157
+ end
158
+
159
+ # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L403-L412
160
+ def safe_load(content, default)
161
+ yield(*YAML.load(content))
162
+ rescue => error
163
+ # Sidekiq issue #1761: in dev mode, it's possible to have jobs enqueued
164
+ # which haven't been loaded into memory yet so the YAML can't be
165
+ # loaded.
166
+ Appsignal.logger.warn "Unable to load YAML: #{error.message}"
167
+ default
168
+ end
169
+ end
170
+ end
171
+ end