appsignal 2.4.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 73a704494489e09f2d92ffc41f9628aef70e9974
4
- data.tar.gz: 267fe55728850ffe4802ea0761a748c809235de5
3
+ metadata.gz: 9969df5b54cc4d39fa5c1b3e5572715adbe9b912
4
+ data.tar.gz: a6ea0af4047d5cde79e766bdf34180b3600c2f97
5
5
  SHA512:
6
- metadata.gz: cd3aa5683ed5c745a828caa4b4971cc1795056c91d990d114cd8e95737ce9bc02419b5255c91761cb3b5bec4aa1fdee9c59b47c6588f52a3f27679342ca2a14e
7
- data.tar.gz: d2f42c59c32fc1dcfe7be0e64466d0afafc81e4d355b26c5b9618436ed9946b67e8988a960d689ada112798551833c4cc81fe6038a0de9c80487164f00ac3fc5
6
+ metadata.gz: 036c096b3039a580a5d75e472f0a0382e3e0b3f277b77ce8c5af16e4f0de539ee20a379c956c8715d7ee700b33392a3eca474d539b9a5697a24c6ff592a5cbd2
7
+ data.tar.gz: 574d5a9c4a20eb7dd8def062b90ec0978f5f0ff78e977a796e5a14043659405971d31ef4824df4c8be8554924f103f6c0699c5516f05f4bd5aa2ce552f6eb4df
data/.gitignore CHANGED
@@ -25,6 +25,7 @@ bundle_and_spec_all_rvm
25
25
  ext/libappsignal.*
26
26
  ext/appsignal-agent
27
27
  ext/appsignal.h
28
+ ext/appsignal.platform
28
29
  ext/appsignal.version
29
30
  ext/Makefile
30
31
  ext/*.bundle
@@ -13,7 +13,7 @@ Lint/HandleExceptions:
13
13
 
14
14
  # Offense count: 41
15
15
  Metrics/AbcSize:
16
- Max: 59
16
+ Max: 59.92
17
17
 
18
18
  # Offense count: 5
19
19
  # Configuration parameters: CountComments.
@@ -33,6 +33,7 @@ gemfile:
33
33
  - "gemfiles/sinatra.gemfile"
34
34
  - "gemfiles/grape.gemfile"
35
35
  - "gemfiles/webmachine.gemfile"
36
+ - "gemfiles/que.gemfile"
36
37
 
37
38
  matrix:
38
39
  fast_finish: true
@@ -1,3 +1,11 @@
1
+ # 2.4.1
2
+ * Add Que integration. PR #361
3
+ * Support Sidekiq delayed extension job action names better. Now action names
4
+ are reported as their class and class method name (`MyClass.method`), rather
5
+ than `Sidekiq::Extensions::DelayedClass#perform` for all jobs through that
6
+ extension. PR #362
7
+ * Support Sidekiq Enterprise encrypted values. PR #365
8
+
1
9
  # 2.4.0
2
10
  - Add separate GNU linux build. PR #351 and
3
11
  Commit d1763f4dcb685608468a73f3192226f60f66b217
data/Rakefile CHANGED
@@ -17,6 +17,7 @@ GEMFILES = %w(
17
17
  sinatra
18
18
  grape
19
19
  webmachine
20
+ que
20
21
  ).freeze
21
22
 
22
23
  RUBY_VERSIONS = %w(
@@ -6,19 +6,13 @@ require "zlib"
6
6
  require "rubygems/package"
7
7
  require "yaml"
8
8
  require File.expand_path("../../lib/appsignal/version.rb", __FILE__)
9
+ require File.expand_path("../../lib/appsignal/system.rb", __FILE__)
9
10
 
10
11
  EXT_PATH = File.expand_path("..", __FILE__).freeze
11
12
  AGENT_CONFIG = YAML.load(File.read(File.join(EXT_PATH, "agent.yml"))).freeze
12
13
 
13
- local_os = Gem::Platform.local.os
14
- chosen_os =
15
- # Detect musl platforms
16
- # Use `export APPSIGNAL_BUILD_FOR_MUSL=1` if the detection doesn't work.
17
- if ENV["APPSIGNAL_BUILD_FOR_MUSL"] || (local_os =~ /linux/ && `ldd --version 2>&1` =~ /musl/)
18
- "linux-musl"
19
- end
20
- OS = chosen_os || local_os
21
- ARCH = "#{Gem::Platform.local.cpu}-#{OS}".freeze
14
+ PLATFORM = Appsignal::System.agent_platform
15
+ ARCH = "#{Gem::Platform.local.cpu}-#{PLATFORM}".freeze
22
16
  CA_CERT_PATH = File.join(EXT_PATH, "../resources/cacert.pem").freeze
23
17
 
24
18
  def ext_path(path)
@@ -36,8 +30,15 @@ def installation_failed(reason)
36
30
  end
37
31
  end
38
32
 
33
+ def write_agent_platform
34
+ File.open(File.join(EXT_PATH, "appsignal.platform"), "w") do |file|
35
+ file.write PLATFORM
36
+ end
37
+ end
38
+
39
39
  def install
40
40
  logger.info "Installing appsignal agent #{Appsignal::VERSION} for Ruby #{RUBY_VERSION} on #{RUBY_PLATFORM}"
41
+ write_agent_platform
41
42
 
42
43
  if RUBY_PLATFORM =~ /java/
43
44
  installation_failed(
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'que'
4
+
5
+ gemspec :path => '../'
@@ -285,6 +285,8 @@ module Appsignal
285
285
  save :language, "ruby"
286
286
  puts_and_save :package_version, "Gem version", Appsignal::VERSION
287
287
  puts_and_save :agent_version, "Agent version", Appsignal::Extension.agent_version
288
+ puts_and_save :agent_platform, "Agent platform",
289
+ Appsignal::System.installed_agent_platform
288
290
  puts_and_save :package_install_path, "Gem install path", gem_path
289
291
  puts_and_save :extension_loaded, "Extension loaded", Appsignal.extension_loaded
290
292
  end
@@ -297,7 +299,7 @@ module Appsignal
297
299
  puts_and_save :architecture, "Architecture", rbconfig["host_cpu"]
298
300
 
299
301
  os_label = os = rbconfig["host_os"]
300
- os_label = "#{os_label} (Microsoft Windows is not supported.)" if Gem.win_platform?
302
+ os_label = "#{os} (Microsoft Windows is not supported.)" if Gem.win_platform?
301
303
  save :os, os
302
304
  puts_value "Operating System", os_label
303
305
 
@@ -102,3 +102,4 @@ require "appsignal/hooks/unicorn"
102
102
  require "appsignal/hooks/mongo_ruby_driver"
103
103
  require "appsignal/hooks/webmachine"
104
104
  require "appsignal/hooks/data_mapper"
105
+ require "appsignal/hooks/que"
@@ -0,0 +1,21 @@
1
+ module Appsignal
2
+ class Hooks
3
+ # @api private
4
+ class QueHook < Appsignal::Hooks::Hook
5
+ register :que
6
+
7
+ def dependencies_present?
8
+ defined?(::Que::Job)
9
+ end
10
+
11
+ def install
12
+ require "appsignal/integrations/que"
13
+ ::Que::Job.send(:include, Appsignal::Integrations::QuePlugin)
14
+
15
+ ::Que.error_notifier = proc do |error, _job|
16
+ Appsignal::Transaction.current.set_error(error)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,64 +1,144 @@
1
+ require "yaml"
2
+
1
3
  module Appsignal
2
4
  class Hooks
5
+ class SidekiqHook < Appsignal::Hooks::Hook
6
+ register :sidekiq
7
+
8
+ def dependencies_present?
9
+ defined?(::Sidekiq)
10
+ end
11
+
12
+ def install
13
+ ::Sidekiq.configure_server do |config|
14
+ config.server_middleware do |chain|
15
+ chain.add Appsignal::Hooks::SidekiqPlugin
16
+ end
17
+ end
18
+ end
19
+ end
20
+
3
21
  # @api private
4
22
  class SidekiqPlugin
5
23
  include Appsignal::Hooks::Helpers
6
24
 
7
- def job_keys
8
- @job_keys ||= Set.new(%w(
9
- class args retried_at failed_at
10
- error_message error_class backtrace
11
- error_backtrace enqueued_at retry
12
- jid retry created_at wrapped
13
- ))
14
- end
25
+ JOB_KEYS = Set.new(%w(
26
+ args backtrace class created_at enqueued_at error_backtrace error_class
27
+ error_message failed_at jid retried_at retry wrapped
28
+ )).freeze
15
29
 
16
30
  def call(_worker, item, _queue)
17
- args =
18
- if item["class"] == "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
19
- item["args"].first["arguments"]
20
- else
21
- item["args"]
22
- end
23
- params = Appsignal::Utils::ParamsSanitizer.sanitize args,
24
- :filter_parameters => Appsignal.config[:filter_parameters]
31
+ transaction = Appsignal::Transaction.create(
32
+ SecureRandom.uuid,
33
+ Appsignal::Transaction::BACKGROUND_JOB,
34
+ Appsignal::Transaction::GenericRequest.new(
35
+ :queue_start => item["enqueued_at"]
36
+ )
37
+ )
25
38
 
26
- Appsignal.monitor_transaction(
27
- "perform_job.sidekiq",
28
- :class => item["wrapped"] || item["class"],
29
- :method => "perform",
30
- :metadata => formatted_metadata(item),
31
- :params => params,
32
- :queue_start => item["enqueued_at"],
33
- :queue_time => (Time.now.to_f - item["enqueued_at"].to_f) * 1000
34
- ) do
35
- yield
39
+ Appsignal.instrument "perform_job.sidekiq" do
40
+ begin
41
+ yield
42
+ rescue Exception => exception # rubocop:disable Lint/RescueException
43
+ transaction.set_error(exception)
44
+ raise exception
45
+ end
46
+ end
47
+ ensure
48
+ if transaction
49
+ transaction.set_action_if_nil(formatted_action_name(item))
50
+ transaction.params = filtered_arguments(item)
51
+ formatted_metadata(item).each do |key, value|
52
+ transaction.set_metadata key, value
53
+ end
54
+ transaction.set_http_or_background_queue_start
55
+ Appsignal::Transaction.complete_current!
36
56
  end
37
57
  end
38
58
 
59
+ private
60
+
61
+ def formatted_action_name(job)
62
+ sidekiq_action_name = parse_action_name(job)
63
+ return sidekiq_action_name if sidekiq_action_name =~ /\.|#/
64
+ "#{sidekiq_action_name}#perform"
65
+ end
66
+
67
+ def filtered_arguments(job)
68
+ Appsignal::Utils::ParamsSanitizer.sanitize(
69
+ parse_arguments(job),
70
+ :filter_parameters => Appsignal.config[:filter_parameters]
71
+ )
72
+ end
73
+
39
74
  def formatted_metadata(item)
40
- {}.tap do |hsh|
41
- item.each do |key, val|
42
- hsh[key] = truncate(string_or_inspect(val)) unless job_keys.include?(key)
75
+ {}.tap do |hash|
76
+ (item || {}).each do |key, value|
77
+ next if JOB_KEYS.include?(key)
78
+ hash[key] = truncate(string_or_inspect(value))
43
79
  end
44
80
  end
45
81
  end
46
- end
47
82
 
48
- class SidekiqHook < Appsignal::Hooks::Hook
49
- register :sidekiq
50
-
51
- def dependencies_present?
52
- defined?(::Sidekiq)
83
+ # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L316-L334
84
+ def parse_action_name(job)
85
+ args = job["args"]
86
+ case job["class"]
87
+ when /\ASidekiq::Extensions::Delayed/
88
+ safe_load(job["args"][0], job["class"]) do |target, method, _|
89
+ "#{target}.#{method}"
90
+ end
91
+ when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
92
+ job_class = job["wrapped"] || args[0]
93
+ if "ActionMailer::DeliveryJob" == job_class
94
+ # MailerClass#mailer_method
95
+ args[0]["arguments"][0..1].join("#")
96
+ else
97
+ job_class
98
+ end
99
+ else
100
+ job["class"]
101
+ end
53
102
  end
54
103
 
55
- def install
56
- ::Sidekiq.configure_server do |config|
57
- config.server_middleware do |chain|
58
- chain.add Appsignal::Hooks::SidekiqPlugin
104
+ # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L336-L358
105
+ def parse_arguments(job)
106
+ args = job["args"]
107
+ case job["class"]
108
+ when /\ASidekiq::Extensions::Delayed/
109
+ safe_load(args[0], args) do |_, _, arg|
110
+ arg
111
+ end
112
+ when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
113
+ is_wrapped = job["wrapped"]
114
+ job_args = is_wrapped ? args[0]["arguments"] : []
115
+ if "ActionMailer::DeliveryJob" == (is_wrapped || args[0])
116
+ # Remove MailerClass, mailer_method and "deliver_now"
117
+ job_args.drop(3)
118
+ else
119
+ job_args
59
120
  end
121
+ else
122
+ # Sidekiq Enterprise argument encryption.
123
+ # More information: https://github.com/mperham/sidekiq/wiki/Ent-Encryption
124
+ if job["encrypt".freeze]
125
+ # No point in showing 150+ bytes of random garbage
126
+ args[-1] = "[encrypted data]".freeze
127
+ end
128
+ args
60
129
  end
61
130
  end
131
+
132
+ # Based on: https://github.com/mperham/sidekiq/blob/63ee43353bd3b753beb0233f64865e658abeb1c3/lib/sidekiq/api.rb#L403-L412
133
+ def safe_load(content, default)
134
+ yield(*YAML.load(content))
135
+ rescue => error
136
+ # Sidekiq issue #1761: in dev mode, it's possible to have jobs enqueued
137
+ # which haven't been loaded into memory yet so the YAML can't be
138
+ # loaded.
139
+ Appsignal.logger.warn "Unable to load YAML: #{error.message}"
140
+ default
141
+ end
62
142
  end
63
143
  end
64
144
  end
@@ -0,0 +1,43 @@
1
+ module Appsignal
2
+ module Integrations
3
+ module QuePlugin
4
+ def self.included(base)
5
+ base.class_eval do
6
+ def _run_with_appsignal
7
+ env = {
8
+ :metadata => {
9
+ :id => attrs[:job_id],
10
+ :queue => attrs[:queue],
11
+ :run_at => attrs[:run_at].to_s,
12
+ :priority => attrs[:priority],
13
+ :attempts => attrs[:error_count].to_i
14
+ },
15
+ :params => attrs[:args]
16
+ }
17
+
18
+ request = Appsignal::Transaction::GenericRequest.new(env)
19
+
20
+ transaction = Appsignal::Transaction.create(
21
+ SecureRandom.uuid,
22
+ Appsignal::Transaction::BACKGROUND_JOB,
23
+ request
24
+ )
25
+
26
+ begin
27
+ Appsignal.instrument("perform_job.que") { _run_without_appsignal }
28
+ rescue Exception => error # rubocop:disable Lint/RescueException
29
+ transaction.set_error(error)
30
+ raise error
31
+ ensure
32
+ transaction.set_action "#{attrs[:job_class]}#run"
33
+ Appsignal::Transaction.complete_current!
34
+ end
35
+ end
36
+
37
+ alias_method :_run_without_appsignal, :_run
38
+ alias_method :_run, :_run_with_appsignal
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -5,8 +5,62 @@ module Appsignal
5
5
  #
6
6
  # @api private
7
7
  module System
8
+ MUSL_TARGET = "linux-musl".freeze
9
+ GEM_EXT_PATH = File.expand_path("../../../ext", __FILE__).freeze
10
+
8
11
  def self.heroku?
9
12
  ENV.key? "DYNO".freeze
10
13
  end
14
+
15
+ # Returns the platform for which the agent was installed.
16
+ #
17
+ # This value is saved when the gem is installed in `ext/extconf.rb`.
18
+ # We use this value to build the diagnose report with the installed
19
+ # platform, rather than the detected platform in {.agent_platform} during
20
+ # the diagnose run.
21
+ #
22
+ # @api private
23
+ # @return [String]
24
+ def self.installed_agent_platform
25
+ platform_file = File.join(GEM_EXT_PATH, "appsignal.platform")
26
+ return unless File.exist?(platform_file)
27
+ File.read(platform_file)
28
+ end
29
+
30
+ # Detect agent and extension platform build
31
+ #
32
+ # Used by `ext/extconf.rb` to select which build it should download and
33
+ # install.
34
+ #
35
+ # Use `export APPSIGNAL_BUILD_FOR_MUSL=1` if the detection doesn't work
36
+ # and to force selection of the musl build.
37
+ #
38
+ # @api private
39
+ # @return [String]
40
+ def self.agent_platform
41
+ return MUSL_TARGET if ENV["APPSIGNAL_BUILD_FOR_MUSL"]
42
+
43
+ local_os = Gem::Platform.local.os
44
+ if local_os =~ /linux/
45
+ ldd_output = ldd_version_output
46
+ return MUSL_TARGET if ldd_output.include? "musl"
47
+ ldd_version = ldd_output.match(/\d+\.\d+/)
48
+ if ldd_version && versionify(ldd_version[0]) < versionify("2.15")
49
+ return MUSL_TARGET
50
+ end
51
+ end
52
+
53
+ local_os
54
+ end
55
+
56
+ # @api private
57
+ def self.versionify(version)
58
+ Gem::Version.new(version)
59
+ end
60
+
61
+ # @api private
62
+ def self.ldd_version_output
63
+ `ldd --version 2>&1`
64
+ end
11
65
  end
12
66
  end
@@ -54,6 +54,12 @@ module Appsignal
54
54
  rescue => e
55
55
  Appsignal.logger.error("Failed to complete transaction ##{current.transaction_id}. #{e.message}")
56
56
  ensure
57
+ clear_current_transaction!
58
+ end
59
+
60
+ # Remove current transaction from current Thread.
61
+ # @api private
62
+ def clear_current_transaction!
57
63
  Thread.current[:appsignal_transaction] = nil
58
64
  end
59
65
 
@@ -312,6 +318,7 @@ module Appsignal
312
318
  finish_event(name, title, body, body_format)
313
319
  end
314
320
 
321
+ # @api private
315
322
  def to_h
316
323
  JSON.parse(@ext.to_json)
317
324
  end