appsignal 2.10.8 → 2.11.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.semaphore/semaphore.yml +75 -61
  4. data/CHANGELOG.md +21 -0
  5. data/build_matrix.yml +13 -7
  6. data/ext/agent.yml +19 -19
  7. data/ext/appsignal_extension.c +10 -1
  8. data/ext/base.rb +11 -2
  9. data/gemfiles/padrino.gemfile +2 -2
  10. data/gemfiles/rails-4.2.gemfile +9 -2
  11. data/gemfiles/rails-5.0.gemfile +1 -0
  12. data/gemfiles/rails-5.1.gemfile +1 -0
  13. data/gemfiles/rails-5.2.gemfile +1 -0
  14. data/gemfiles/rails-6.0.gemfile +1 -0
  15. data/gemfiles/resque-1.gemfile +7 -0
  16. data/gemfiles/{resque.gemfile → resque-2.gemfile} +1 -1
  17. data/lib/appsignal.rb +21 -1
  18. data/lib/appsignal/capistrano.rb +2 -0
  19. data/lib/appsignal/config.rb +6 -2
  20. data/lib/appsignal/environment.rb +126 -0
  21. data/lib/appsignal/extension/jruby.rb +10 -0
  22. data/lib/appsignal/hooks.rb +2 -0
  23. data/lib/appsignal/hooks/active_job.rb +89 -0
  24. data/lib/appsignal/hooks/net_http.rb +2 -0
  25. data/lib/appsignal/hooks/puma.rb +2 -58
  26. data/lib/appsignal/hooks/redis.rb +2 -0
  27. data/lib/appsignal/hooks/resque.rb +60 -0
  28. data/lib/appsignal/hooks/sequel.rb +2 -0
  29. data/lib/appsignal/hooks/sidekiq.rb +18 -191
  30. data/lib/appsignal/integrations/object.rb +4 -0
  31. data/lib/appsignal/integrations/que.rb +1 -1
  32. data/lib/appsignal/integrations/resque.rb +9 -12
  33. data/lib/appsignal/integrations/resque_active_job.rb +9 -24
  34. data/lib/appsignal/probes/puma.rb +61 -0
  35. data/lib/appsignal/probes/sidekiq.rb +102 -0
  36. data/lib/appsignal/rack/js_exception_catcher.rb +5 -2
  37. data/lib/appsignal/transaction.rb +32 -7
  38. data/lib/appsignal/utils/deprecation_message.rb +5 -1
  39. data/lib/appsignal/version.rb +1 -1
  40. data/lib/puma/plugin/appsignal.rb +2 -1
  41. data/spec/lib/appsignal/cli/diagnose_spec.rb +2 -1
  42. data/spec/lib/appsignal/config_spec.rb +6 -1
  43. data/spec/lib/appsignal/environment_spec.rb +167 -0
  44. data/spec/lib/appsignal/hooks/activejob_spec.rb +458 -0
  45. data/spec/lib/appsignal/hooks/puma_spec.rb +2 -181
  46. data/spec/lib/appsignal/hooks/resque_spec.rb +185 -0
  47. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +292 -546
  48. data/spec/lib/appsignal/integrations/padrino_spec.rb +1 -1
  49. data/spec/lib/appsignal/integrations/que_spec.rb +25 -6
  50. data/spec/lib/appsignal/integrations/resque_active_job_spec.rb +20 -137
  51. data/spec/lib/appsignal/integrations/resque_spec.rb +20 -85
  52. data/spec/lib/appsignal/probes/puma_spec.rb +180 -0
  53. data/spec/lib/appsignal/probes/sidekiq_spec.rb +204 -0
  54. data/spec/lib/appsignal/rack/js_exception_catcher_spec.rb +9 -4
  55. data/spec/lib/appsignal/transaction_spec.rb +35 -20
  56. data/spec/lib/appsignal_spec.rb +22 -0
  57. data/spec/lib/puma/appsignal_spec.rb +1 -1
  58. data/spec/support/helpers/action_mailer_helpers.rb +25 -0
  59. data/spec/support/helpers/dependency_helper.rb +12 -0
  60. data/spec/support/helpers/env_helpers.rb +1 -1
  61. data/spec/support/helpers/environment_metdata_helper.rb +16 -0
  62. data/spec/support/helpers/transaction_helpers.rb +6 -0
  63. data/spec/support/stubs/sidekiq/api.rb +2 -2
  64. metadata +25 -5
@@ -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);
@@ -32,7 +32,8 @@ def report
32
32
  "version" => "#{rbconfig["ruby_version"]}-p#{rbconfig["PATCHLEVEL"]}"
33
33
  },
34
34
  "download" => {
35
- "checksum" => "unverified"
35
+ "checksum" => "unverified",
36
+ "http_proxy" => http_proxy
36
37
  },
37
38
  "build" => {
38
39
  "time" => Time.now.utc,
@@ -126,7 +127,11 @@ def download_archive(type)
126
127
  report["download"]["download_url"] = download_url
127
128
 
128
129
  begin
129
- return open(download_url, :ssl_ca_cert => CA_CERT_PATH)
130
+ return open(
131
+ download_url,
132
+ :ssl_ca_cert => CA_CERT_PATH,
133
+ :proxy => http_proxy
134
+ )
130
135
  rescue
131
136
  next
132
137
  end
@@ -174,3 +179,7 @@ def store_download_version_on_report
174
179
  path = File.expand_path(File.join(File.dirname(__FILE__), "appsignal.version"))
175
180
  report["build"]["agent_version"] = File.read(path).strip
176
181
  end
182
+
183
+ def http_proxy
184
+ Gem.configuration[:http_proxy] || ENV["http_proxy"] || ENV["HTTP_PROXY"]
185
+ end
@@ -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,89 @@
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
+ # @todo Add queue time support for Rails 6's enqueued_at. For both
19
+ # existing and new transactions.
20
+ module ActiveJobClassInstrumentation
21
+ def execute(job)
22
+ current_transaction = Appsignal::Transaction.current
23
+ transaction =
24
+ if current_transaction.nil_transaction?
25
+ # No standalone integration started before ActiveJob integration.
26
+ # We don't have a separate integration for this QueueAdapter like
27
+ # we do for Sidekiq.
28
+ #
29
+ # Prefer job_id from provider, instead of ActiveJob's internal ID.
30
+ Appsignal::Transaction.create(
31
+ job["provider_job_id"] || job["job_id"],
32
+ Appsignal::Transaction::BACKGROUND_JOB,
33
+ Appsignal::Transaction::GenericRequest.new({})
34
+ )
35
+ else
36
+ current_transaction
37
+ end
38
+
39
+ super
40
+ rescue Exception => exception # rubocop:disable Lint/RescueException
41
+ transaction.set_error(exception)
42
+ raise exception
43
+ ensure
44
+ if transaction
45
+ transaction.params =
46
+ Appsignal::Utils::HashSanitizer.sanitize(
47
+ job["arguments"],
48
+ Appsignal.config[:filter_parameters]
49
+ )
50
+
51
+ tags = { :queue => job["queue_name"] }
52
+ provider_job_id = job["provider_job_id"]
53
+ tags[:provider_job_id] = provider_job_id if provider_job_id
54
+ transaction.set_tags(tags)
55
+
56
+ transaction.set_action_if_nil(ActiveJobHelpers.action_name(job))
57
+ enqueued_at = job["enqueued_at"]
58
+ if enqueued_at # Present in Rails 6 and up
59
+ transaction.set_queue_start((Time.parse(enqueued_at).to_f * 1_000).to_i)
60
+ end
61
+
62
+ if current_transaction.nil_transaction?
63
+ # Only complete transaction if ActiveJob is not wrapped in
64
+ # another supported integration, such as Sidekiq.
65
+ Appsignal::Transaction.complete_current!
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ module ActiveJobHelpers
72
+ ACTION_MAILER_CLASSES = [
73
+ "ActionMailer::DeliveryJob",
74
+ "ActionMailer::Parameterized::DeliveryJob",
75
+ "ActionMailer::MailDeliveryJob"
76
+ ].freeze
77
+
78
+ def self.action_name(job)
79
+ case job["job_class"]
80
+ when *ACTION_MAILER_CLASSES
81
+ job["arguments"][0..1].join("#")
82
+ else
83
+ "#{job["job_class"]}#perform"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end