appsignal 2.10.10 → 2.11.0.beta.3

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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.semaphore/semaphore.yml +75 -61
  4. data/CHANGELOG.md +18 -3
  5. data/build_matrix.yml +13 -7
  6. data/ext/agent.yml +19 -19
  7. data/ext/appsignal_extension.c +10 -1
  8. data/gemfiles/padrino.gemfile +2 -2
  9. data/gemfiles/rails-4.2.gemfile +9 -2
  10. data/gemfiles/rails-5.0.gemfile +1 -0
  11. data/gemfiles/rails-5.1.gemfile +1 -0
  12. data/gemfiles/rails-5.2.gemfile +1 -0
  13. data/gemfiles/rails-6.0.gemfile +1 -0
  14. data/gemfiles/resque-1.gemfile +7 -0
  15. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  16. data/lib/appsignal.rb +21 -1
  17. data/lib/appsignal/capistrano.rb +2 -0
  18. data/lib/appsignal/config.rb +6 -2
  19. data/lib/appsignal/environment.rb +126 -0
  20. data/lib/appsignal/extension/jruby.rb +10 -0
  21. data/lib/appsignal/hooks.rb +2 -0
  22. data/lib/appsignal/hooks/active_job.rb +108 -0
  23. data/lib/appsignal/hooks/net_http.rb +2 -0
  24. data/lib/appsignal/hooks/puma.rb +2 -58
  25. data/lib/appsignal/hooks/redis.rb +2 -0
  26. data/lib/appsignal/hooks/resque.rb +60 -0
  27. data/lib/appsignal/hooks/sequel.rb +2 -0
  28. data/lib/appsignal/hooks/sidekiq.rb +18 -191
  29. data/lib/appsignal/integrations/object.rb +4 -0
  30. data/lib/appsignal/integrations/que.rb +1 -1
  31. data/lib/appsignal/integrations/resque.rb +9 -12
  32. data/lib/appsignal/integrations/resque_active_job.rb +9 -30
  33. data/lib/appsignal/probes/puma.rb +61 -0
  34. data/lib/appsignal/probes/sidekiq.rb +102 -0
  35. data/lib/appsignal/transaction.rb +32 -7
  36. data/lib/appsignal/utils/deprecation_message.rb +5 -1
  37. data/lib/appsignal/version.rb +1 -1
  38. data/lib/puma/plugin/appsignal.rb +2 -1
  39. data/spec/lib/appsignal/config_spec.rb +6 -1
  40. data/spec/lib/appsignal/environment_spec.rb +167 -0
  41. data/spec/lib/appsignal/hooks/activejob_spec.rb +521 -0
  42. data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
  43. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  44. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +292 -546
  45. data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
  46. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  47. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -179
  48. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  49. data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
  50. data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
  51. data/spec/lib/appsignal/transaction_spec.rb +35 -20
  52. data/spec/lib/appsignal_spec.rb +22 -0
  53. data/spec/lib/puma/appsignal_spec.rb +1 -1
  54. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  55. data/spec/support/helpers/dependency_helper.rb +9 -2
  56. data/spec/support/helpers/env_helpers.rb +1 -1
  57. data/spec/support/helpers/environment_metdata_helper.rb +16 -0
  58. data/spec/support/helpers/transaction_helpers.rb +6 -0
  59. data/spec/support/stubs/sidekiq/api.rb +2 -2
  60. metadata +24 -4
@@ -639,6 +639,14 @@ static VALUE running_in_container() {
639
639
  return appsignal_running_in_container() == 1 ? Qtrue : Qfalse;
640
640
  }
641
641
 
642
+ static VALUE set_environment_metadata(VALUE self, VALUE key, VALUE value) {
643
+ appsignal_set_environment_metadata(
644
+ make_appsignal_string(key),
645
+ make_appsignal_string(value)
646
+ );
647
+ return Qnil;
648
+ }
649
+
642
650
  void Init_appsignal_extension(void) {
643
651
  Appsignal = rb_define_module("Appsignal");
644
652
  Extension = rb_define_class_under(Appsignal, "Extension", rb_cObject);
@@ -697,9 +705,10 @@ void Init_appsignal_extension(void) {
697
705
  // Get JSON content of a data
698
706
  rb_define_method(Data, "to_s", data_to_s, 0);
699
707
 
700
- // Event hook installation
708
+ // Other helper methods
701
709
  rb_define_singleton_method(Extension, "install_allocation_event_hook", install_allocation_event_hook, 0);
702
710
  rb_define_singleton_method(Extension, "running_in_container?", running_in_container, 0);
711
+ rb_define_singleton_method(Extension, "set_environment_metadata", set_environment_metadata, 2);
703
712
 
704
713
  // Metrics
705
714
  rb_define_singleton_method(Extension, "set_gauge", set_gauge, 3);
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'padrino', '~> 0.13.0'
4
- gem 'rack', '~> 1.6'
3
+ gem 'padrino', "~> 0.15"
4
+ gem 'rack'
5
5
 
6
6
  gemspec :path => '../'
@@ -3,8 +3,15 @@ source 'https://rubygems.org'
3
3
  gem 'rails', '~> 4.2.0'
4
4
  gem 'mime-types', '~> 2.6'
5
5
 
6
- gemspec :path => '../'
6
+ ruby_version = Gem::Version.new(RUBY_VERSION)
7
+ if ruby_version < Gem::Version.new("2.3.0")
8
+ gem "sidekiq", "~> 4.0"
9
+ else
10
+ gem "sidekiq"
11
+ end
7
12
 
8
- if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.1.0")
13
+ if ruby_version < Gem::Version.new("2.1.0")
9
14
  gem 'nokogiri', '~> 1.6.0'
10
15
  end
16
+
17
+ gemspec :path => '../'
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'rails', '~> 5.0.0'
4
+ gem "sidekiq"
4
5
 
5
6
  gemspec :path => '../'
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'rails', '~> 5.1.0'
4
+ gem "sidekiq"
4
5
 
5
6
  gemspec :path => '../'
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'rails', '~> 5.2.0'
4
+ gem "sidekiq"
4
5
 
5
6
  gemspec :path => '../'
@@ -1,5 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gem 'rails', '~> 6.0.0'
4
+ gem "sidekiq"
4
5
 
5
6
  gemspec :path => '../'
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'resque', "~> 1.27.0"
4
+ gem 'sinatra'
5
+ gem 'mime-types', '~> 2.6'
6
+
7
+ gemspec :path => '../'
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'resque'
3
+ gem 'resque', "~> 2.0"
4
4
  gem 'sinatra'
5
5
  gem 'mime-types', '~> 2.6'
6
6
 
@@ -134,11 +134,17 @@ module Appsignal
134
134
 
135
135
  if config[:enable_allocation_tracking] && !Appsignal::System.jruby?
136
136
  Appsignal::Extension.install_allocation_event_hook
137
+ Appsignal::Environment.report_enabled("allocation_tracking")
137
138
  end
138
139
 
139
- GC::Profiler.enable if config[:enable_gc_instrumentation]
140
+ if config[:enable_gc_instrumentation]
141
+ GC::Profiler.enable
142
+ Appsignal::Environment.report_enabled("gc_instrumentation")
143
+ end
140
144
 
141
145
  Appsignal::Minutely.start if config[:enable_minutely_probes]
146
+
147
+ collect_environment_metadata
142
148
  else
143
149
  logger.info("Not starting, not active for #{config.env}")
144
150
  end
@@ -309,9 +315,23 @@ module Appsignal
309
315
  logger.warn "Unable to start logger with log path '#{path}'."
310
316
  logger.warn error
311
317
  end
318
+
319
+ def collect_environment_metadata
320
+ Appsignal::Environment.report("ruby_version") do
321
+ "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}"
322
+ end
323
+ Appsignal::Environment.report("ruby_engine") { RUBY_ENGINE }
324
+ if defined?(RUBY_ENGINE_VERSION)
325
+ Appsignal::Environment.report("ruby_engine_version") do
326
+ RUBY_ENGINE_VERSION
327
+ end
328
+ end
329
+ Appsignal::Environment.report_supported_gems
330
+ end
312
331
  end
313
332
  end
314
333
 
334
+ require "appsignal/environment"
315
335
  require "appsignal/system"
316
336
  require "appsignal/utils"
317
337
  require "appsignal/extension"
@@ -3,6 +3,8 @@
3
3
  require "appsignal"
4
4
  require "capistrano/version"
5
5
 
6
+ Appsignal::Environment.report_enabled("capistrano")
7
+
6
8
  if defined?(Capistrano::VERSION) && Gem::Version.new(Capistrano::VERSION) >= Gem::Version.new(3)
7
9
  # Capistrano 3+
8
10
  load File.expand_path("../integrations/capistrano/appsignal.cap", __FILE__)
@@ -18,6 +18,7 @@ module Appsignal
18
18
  :ignore_namespaces => [],
19
19
  :filter_parameters => [],
20
20
  :filter_session_data => [],
21
+ :send_environment_metadata => true,
21
22
  :send_params => true,
22
23
  :request_headers => %w[
23
24
  HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
@@ -62,6 +63,7 @@ module Appsignal
62
63
  "APPSIGNAL_IGNORE_NAMESPACES" => :ignore_namespaces,
63
64
  "APPSIGNAL_FILTER_PARAMETERS" => :filter_parameters,
64
65
  "APPSIGNAL_FILTER_SESSION_DATA" => :filter_session_data,
66
+ "APPSIGNAL_SEND_ENVIRONMENT_METADATA" => :send_environment_metadata,
65
67
  "APPSIGNAL_SEND_PARAMS" => :send_params,
66
68
  "APPSIGNAL_HTTP_PROXY" => :http_proxy,
67
69
  "APPSIGNAL_ENABLE_ALLOCATION_TRACKING" => :enable_allocation_tracking,
@@ -222,6 +224,7 @@ module Appsignal
222
224
  ENV["_APPSIGNAL_DNS_SERVERS"] = config_hash[:dns_servers].join(",")
223
225
  ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"] = config_hash[:files_world_accessible].to_s
224
226
  ENV["_APPSIGNAL_TRANSACTION_DEBUG_MODE"] = config_hash[:transaction_debug_mode].to_s
227
+ ENV["_APPSIGNAL_SEND_ENVIRONMENT_METADATA"] = config_hash[:send_environment_metadata].to_s
225
228
  ENV["_APP_REVISION"] = config_hash[:revision].to_s
226
229
  end
227
230
 
@@ -337,8 +340,9 @@ module Appsignal
337
340
  APPSIGNAL_SKIP_SESSION_DATA APPSIGNAL_ENABLE_FRONTEND_ERROR_CATCHING
338
341
  APPSIGNAL_ENABLE_ALLOCATION_TRACKING APPSIGNAL_ENABLE_GC_INSTRUMENTATION
339
342
  APPSIGNAL_RUNNING_IN_CONTAINER APPSIGNAL_ENABLE_HOST_METRICS
340
- APPSIGNAL_SEND_PARAMS APPSIGNAL_ENABLE_MINUTELY_PROBES
341
- APPSIGNAL_FILES_WORLD_ACCESSIBLE APPSIGNAL_TRANSACTION_DEBUG_MODE].each do |var|
343
+ APPSIGNAL_SEND_ENVIRONMENT_METADATA APPSIGNAL_SEND_PARAMS
344
+ APPSIGNAL_ENABLE_MINUTELY_PROBES APPSIGNAL_FILES_WORLD_ACCESSIBLE
345
+ APPSIGNAL_TRANSACTION_DEBUG_MODE].each do |var|
342
346
  env_var = ENV[var]
343
347
  next unless env_var
344
348
  config[ENV_TO_KEY_MAPPING[var]] = env_var.casecmp("true").zero?
@@ -0,0 +1,126 @@
1
+ module Appsignal
2
+ # @api private
3
+ class Environment
4
+ # Add environment metadata.
5
+ #
6
+ # The key and value of the environment metadata must be a String, even if
7
+ # it's actually of another type.
8
+ #
9
+ # The value of the environment metadata is given as a block that captures
10
+ # errors that might be raised while fetching the value. It will not
11
+ # re-raise errors, but instead log them using the {Appsignal.logger}. This
12
+ # ensures AppSignal will not cause an error in the application when
13
+ # collecting this metadata.
14
+ #
15
+ # @example Reporting a key and value
16
+ # Appsignal::Environment.report("ruby_version") { RUBY_VERSION }
17
+ #
18
+ # @example When a value is nil
19
+ # Appsignal::Environment.report("ruby_version") { nil }
20
+ # # Key and value do not get reported. A warning gets logged instead.
21
+ #
22
+ # @example When an error occurs
23
+ # Appsignal::Environment.report("ruby_version") { raise "uh oh" }
24
+ # # Error does not get reraised. A warning gets logged instead.
25
+ #
26
+ # @param key [String] The name of the key of the environment metadata value.
27
+ # @yieldreturn [String] The value of the key of the environment metadata.
28
+ # @return [void]
29
+ def self.report(key)
30
+ key =
31
+ case key
32
+ when String
33
+ key
34
+ else
35
+ Appsignal.logger.error "Unable to report on environment metadata: " \
36
+ "Unsupported value type for #{key.inspect}"
37
+ return
38
+ end
39
+
40
+ yielded_value =
41
+ begin
42
+ yield
43
+ rescue => e
44
+ Appsignal.logger.error \
45
+ "Unable to report on environment metadata #{key.inspect}:\n" \
46
+ "#{e.class}: #{e}"
47
+ return
48
+ end
49
+
50
+ value =
51
+ case yielded_value
52
+ when TrueClass, FalseClass
53
+ yielded_value.to_s
54
+ when String
55
+ yielded_value
56
+ else
57
+ Appsignal.logger.error "Unable to report on environment metadata " \
58
+ "#{key.inspect}: Unsupported value type for " \
59
+ "#{yielded_value.inspect}"
60
+ return
61
+ end
62
+
63
+ Appsignal::Extension.set_environment_metadata(key, value)
64
+ rescue => e
65
+ Appsignal.logger.error "Unable to report on environment metadata:\n" \
66
+ "#{e.class}: #{e}"
67
+ end
68
+
69
+ # @see report_supported_gems
70
+ SUPPORTED_GEMS = %w[
71
+ actioncable
72
+ activejob
73
+ capistrano
74
+ celluloid
75
+ data_mapper
76
+ delayed_job
77
+ mongo_ruby_driver
78
+ padrino
79
+ passenger
80
+ puma
81
+ que
82
+ rack
83
+ rails
84
+ rake
85
+ redis
86
+ resque
87
+ sequel
88
+ shoryuken
89
+ sidekiq
90
+ sinatra
91
+ unicorn
92
+ webmachine
93
+ ].freeze
94
+
95
+ # Report on the list of AppSignal supported gems
96
+ #
97
+ # This list is used to report if which AppSignal supported gems are present
98
+ # in this app and what version. This data will help AppSignal improve its
99
+ # support by knowing what gems and versions of gems it still needs to
100
+ # support or can drop support for.
101
+ #
102
+ # It will ask Bundler to report name and version information from the gems
103
+ # that are present in the app bundle.
104
+ def self.report_supported_gems
105
+ return unless defined?(Bundler) # Do nothing if Bundler is not present
106
+
107
+ bundle_gem_specs = ::Bundler.rubygems.all_specs
108
+ SUPPORTED_GEMS.each do |gem_name|
109
+ gem_spec = bundle_gem_specs.find { |spec| spec.name == gem_name }
110
+ next unless gem_spec
111
+
112
+ report("ruby_#{gem_name}_version") { gem_spec.version.to_s }
113
+ end
114
+ rescue => e
115
+ Appsignal.logger.error "Unable to report supported gems:\n" \
116
+ "#{e.class}: #{e}"
117
+ end
118
+
119
+ def self.report_enabled(feature)
120
+ Appsignal::Environment.report("ruby_#{feature}_enabled") { true }
121
+ rescue => e
122
+ Appsignal.logger.error "Unable to report integration enabled:\n" \
123
+ "#{e.class}: #{e}"
124
+ end
125
+ end
126
+ end
@@ -60,6 +60,9 @@ module Appsignal
60
60
  [:appsignal_string],
61
61
  :appsignal_string
62
62
  attach_function :appsignal_running_in_container, [], :bool
63
+ attach_function :appsignal_set_environment_metadata,
64
+ [:appsignal_string, :appsignal_string],
65
+ :void
63
66
 
64
67
  # Metrics methods
65
68
  attach_function :appsignal_set_gauge,
@@ -224,6 +227,13 @@ module Appsignal
224
227
  appsignal_running_in_container
225
228
  end
226
229
 
230
+ def set_environment_metadata(key, value)
231
+ appsignal_set_environment_metadata(
232
+ make_appsignal_string(key),
233
+ make_appsignal_string(value)
234
+ )
235
+ end
236
+
227
237
  def set_gauge(key, value, tags)
228
238
  appsignal_set_gauge(make_appsignal_string(key), value, tags.pointer)
229
239
  end
@@ -73,6 +73,7 @@ module Appsignal
73
73
  end
74
74
 
75
75
  require "appsignal/hooks/action_cable"
76
+ require "appsignal/hooks/active_job"
76
77
  require "appsignal/hooks/active_support_notifications"
77
78
  require "appsignal/hooks/celluloid"
78
79
  require "appsignal/hooks/delayed_job"
@@ -81,6 +82,7 @@ require "appsignal/hooks/passenger"
81
82
  require "appsignal/hooks/puma"
82
83
  require "appsignal/hooks/rake"
83
84
  require "appsignal/hooks/redis"
85
+ require "appsignal/hooks/resque"
84
86
  require "appsignal/hooks/sequel"
85
87
  require "appsignal/hooks/shoryuken"
86
88
  require "appsignal/hooks/sidekiq"
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Appsignal
4
+ class Hooks
5
+ # @api private
6
+ class ActiveJobHook < Appsignal::Hooks::Hook
7
+ register :active_job
8
+
9
+ def dependencies_present?
10
+ defined?(::ActiveJob)
11
+ end
12
+
13
+ def install
14
+ ::ActiveJob::Base
15
+ .extend ::Appsignal::Hooks::ActiveJobHook::ActiveJobClassInstrumentation
16
+ end
17
+
18
+ module ActiveJobClassInstrumentation
19
+ def execute(job)
20
+ job_status = nil
21
+ current_transaction = Appsignal::Transaction.current
22
+ transaction =
23
+ if current_transaction.nil_transaction?
24
+ # No standalone integration started before ActiveJob integration.
25
+ # We don't have a separate integration for this QueueAdapter like
26
+ # we do for Sidekiq.
27
+ #
28
+ # Prefer job_id from provider, instead of ActiveJob's internal ID.
29
+ Appsignal::Transaction.create(
30
+ job["provider_job_id"] || job["job_id"],
31
+ Appsignal::Transaction::BACKGROUND_JOB,
32
+ Appsignal::Transaction::GenericRequest.new({})
33
+ )
34
+ else
35
+ current_transaction
36
+ end
37
+
38
+ super
39
+ rescue Exception => exception # rubocop:disable Lint/RescueException
40
+ job_status = :failed
41
+ transaction.set_error(exception)
42
+ raise exception
43
+ ensure
44
+ tags = {}
45
+ queue = job["queue_name"]
46
+ tags[:queue] = queue if queue
47
+ priority = job["priority"]
48
+ tags[:priority] = priority if priority
49
+
50
+ if transaction
51
+ transaction.params =
52
+ Appsignal::Utils::HashSanitizer.sanitize(
53
+ job["arguments"],
54
+ Appsignal.config[:filter_parameters]
55
+ )
56
+
57
+ transaction_tags = tags
58
+ provider_job_id = job["provider_job_id"]
59
+ if provider_job_id
60
+ transaction_tags[:provider_job_id] = provider_job_id
61
+ end
62
+ transaction.set_tags(transaction_tags)
63
+
64
+ transaction.set_action_if_nil(ActiveJobHelpers.action_name(job))
65
+ enqueued_at = job["enqueued_at"]
66
+ if enqueued_at # Present in Rails 6 and up
67
+ transaction.set_queue_start((Time.parse(enqueued_at).to_f * 1_000).to_i)
68
+ end
69
+
70
+ if current_transaction.nil_transaction?
71
+ # Only complete transaction if ActiveJob is not wrapped in
72
+ # another supported integration, such as Sidekiq.
73
+ Appsignal::Transaction.complete_current!
74
+ end
75
+ end
76
+
77
+ if job_status
78
+ ActiveJobHelpers.increment_counter "queue_job_count", 1,
79
+ tags.merge(:status => job_status)
80
+ end
81
+ ActiveJobHelpers.increment_counter "queue_job_count", 1,
82
+ tags.merge(:status => :processed)
83
+ end
84
+ end
85
+
86
+ module ActiveJobHelpers
87
+ ACTION_MAILER_CLASSES = [
88
+ "ActionMailer::DeliveryJob",
89
+ "ActionMailer::Parameterized::DeliveryJob",
90
+ "ActionMailer::MailDeliveryJob"
91
+ ].freeze
92
+
93
+ def self.action_name(job)
94
+ case job["job_class"]
95
+ when *ACTION_MAILER_CLASSES
96
+ job["arguments"][0..1].join("#")
97
+ else
98
+ "#{job["job_class"]}#perform"
99
+ end
100
+ end
101
+
102
+ def self.increment_counter(key, value, tags = {})
103
+ Appsignal.increment_counter "active_job_#{key}", value, tags
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end