appsignal 1.0.7 → 1.1.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +5 -21
  4. data/Rakefile +2 -0
  5. data/circle.yml +2 -1
  6. data/ext/agent.yml +7 -7
  7. data/ext/appsignal_extension.c +3 -5
  8. data/ext/extconf.rb +6 -15
  9. data/gemfiles/grape.gemfile +7 -0
  10. data/lib/appsignal/config.rb +2 -5
  11. data/lib/appsignal/event_formatter.rb +0 -2
  12. data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +47 -1
  13. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +29 -0
  14. data/lib/appsignal/event_formatter/moped/query_formatter.rb +7 -6
  15. data/lib/appsignal/hooks.rb +33 -0
  16. data/lib/appsignal/hooks/net_http.rb +1 -1
  17. data/lib/appsignal/hooks/sequel.rb +7 -4
  18. data/lib/appsignal/hooks/sidekiq.rb +10 -19
  19. data/lib/appsignal/integrations/capistrano/appsignal.cap +1 -1
  20. data/lib/appsignal/integrations/delayed_job_plugin.rb +20 -11
  21. data/lib/appsignal/integrations/grape.rb +44 -0
  22. data/lib/appsignal/integrations/mongo_ruby_driver.rb +5 -9
  23. data/lib/appsignal/integrations/railtie.rb +4 -0
  24. data/lib/appsignal/integrations/resque.rb +1 -1
  25. data/lib/appsignal/js_exception_transaction.rb +2 -3
  26. data/lib/appsignal/subscriber.rb +2 -3
  27. data/lib/appsignal/transaction.rb +2 -8
  28. data/lib/appsignal/transmitter.rb +1 -1
  29. data/lib/appsignal/utils.rb +7 -43
  30. data/lib/appsignal/version.rb +1 -1
  31. data/lib/tasks/diag.rake +75 -0
  32. data/spec/lib/appsignal/capistrano3_spec.rb +1 -21
  33. data/spec/lib/appsignal/config_spec.rb +0 -12
  34. data/spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb +1 -1
  35. data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +186 -14
  36. data/spec/lib/appsignal/event_formatter/elastic_search/search_formatter_spec.rb +54 -0
  37. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +4 -4
  38. data/spec/lib/appsignal/extension_spec.rb +1 -1
  39. data/spec/lib/appsignal/hooks/delayed_job_spec.rb +49 -14
  40. data/spec/lib/appsignal/hooks/sequel_spec.rb +1 -1
  41. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +29 -62
  42. data/spec/lib/appsignal/hooks_spec.rb +115 -0
  43. data/spec/lib/appsignal/integrations/grape_spec.rb +94 -0
  44. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +5 -8
  45. data/spec/lib/appsignal/integrations/resque_spec.rb +0 -1
  46. data/spec/lib/appsignal/js_exception_transaction_spec.rb +0 -1
  47. data/spec/lib/appsignal/subscriber_spec.rb +5 -23
  48. data/spec/lib/appsignal/transaction_spec.rb +0 -21
  49. data/spec/lib/appsignal/utils_spec.rb +1 -68
  50. data/spec/spec_helper.rb +16 -0
  51. data/spec/support/helpers/env_helpers.rb +0 -1
  52. data/spec/support/stubs/delayed_job.rb +0 -0
  53. metadata +15 -11
  54. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +0 -88
  55. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +0 -13
  56. data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +0 -115
  57. data/spec/lib/appsignal/event_formatter/sequel/sql_formatter_spec.rb +0 -22
@@ -1,6 +1,6 @@
1
1
  namespace :appsignal do
2
2
  task :deploy do
3
- env = fetch(:stage, fetch(:rails_env, fetch(:rack_env, 'production')))
3
+ env = fetch(:rails_env, fetch(:rack_env, 'production'))
4
4
  user = ENV['USER'] || ENV['USERNAME']
5
5
  revision = fetch(:appsignal_revision, fetch(:current_revision))
6
6
  logger = fetch(:logger, Logger.new($stdout))
@@ -1,6 +1,8 @@
1
1
  module Appsignal
2
2
  class Hooks
3
3
  class DelayedJobPlugin < ::Delayed::Plugin
4
+ extend Appsignal::Hooks::Helpers
5
+
4
6
  callbacks do |lifecycle|
5
7
  lifecycle.around(:invoke_job) do |job, &block|
6
8
  invoke_with_instrumentation(job, block)
@@ -12,24 +14,31 @@ module Appsignal
12
14
  end
13
15
 
14
16
  def self.invoke_with_instrumentation(job, block)
15
- class_and_method_name = if job.payload_object.respond_to?(:appsignal_name)
16
- job.payload_object.appsignal_name
17
- else
18
- job.name
19
- end
20
- class_name, method_name = class_and_method_name.split('#')
17
+ if job.respond_to?(:payload_object)
18
+ # Direct Delayed Job
19
+ class_and_method_name = extract_value(job.payload_object, :appsignal_name, job.name)
20
+ class_name, method_name = class_and_method_name.split('#')
21
+ args = extract_value(job.payload_object, :args, {})
22
+ job_data = job
23
+ elsif job.respond_to?(:job_data)
24
+ # Via ActiveJob
25
+ class_name, method_name = job.job_data[:name].split('#')
26
+ args = job.job_data[:args] || {}
27
+ job_data = job.job_data
28
+ end
21
29
 
22
30
  Appsignal.monitor_transaction(
23
31
  'perform_job.delayed_job',
24
32
  :class => class_name,
25
33
  :method => method_name,
26
34
  :metadata => {
27
- :id => job.id.to_s,
28
- :queue => job.queue,
29
- :priority => job.priority || 0,
30
- :attempts => job.attempts || 0
35
+ :id => extract_value(job_data, :id, nil, true),
36
+ :queue => extract_value(job_data, :queue),
37
+ :priority => extract_value(job_data, :priority, 0),
38
+ :attempts => extract_value(job_data, :attempts, 0)
31
39
  },
32
- :queue_start => job.created_at
40
+ :params => format_args(args),
41
+ :queue_start => extract_value(job_data, :created_at)
33
42
  ) do
34
43
  block.call(job)
35
44
  end
@@ -0,0 +1,44 @@
1
+ module Appsignal
2
+ module Grape
3
+ class Middleware < ::Grape::Middleware::Base
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ if Appsignal.active?
10
+ call_with_appsignal_monitoring(env)
11
+ else
12
+ @app.call(env)
13
+ end
14
+ end
15
+
16
+ def call_with_appsignal_monitoring(env)
17
+ request = ::Rack::Request.new(env)
18
+ transaction = Appsignal::Transaction.create(
19
+ SecureRandom.uuid,
20
+ Appsignal::Transaction::HTTP_REQUEST,
21
+ request
22
+ )
23
+ begin
24
+ @app.call(env)
25
+ rescue => error
26
+ transaction.set_error(error)
27
+ raise error
28
+ ensure
29
+ api_endpoint = env['api.endpoint']
30
+ if api_endpoint && options = api_endpoint.options
31
+ method = options[:method].first
32
+ klass = options[:for]
33
+ action = options[:path].first
34
+ transaction.set_action("#{method}::#{klass}##{action}")
35
+ end
36
+ transaction.set_http_or_background_queue_start
37
+ transaction.set_metadata('path', request.path)
38
+ transaction.set_metadata('method', env['REQUEST_METHOD'])
39
+ Appsignal::Transaction.complete_current!
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,19 +1,16 @@
1
1
  module Appsignal
2
2
  class Hooks
3
3
  class MongoMonitorSubscriber
4
+
4
5
  # Called by Mongo::Monitor when query starts
5
6
  def started(event)
6
7
  transaction = Appsignal::Transaction.current
7
8
  return if transaction.nil_transaction?
8
9
  return if transaction.paused?
9
10
 
10
- # Format the command
11
- command = Appsignal::EventFormatter::MongoRubyDriver::QueryFormatter
12
- .format(event.command_name, event.command)
13
-
14
11
  # Store the query on the transaction, we need it when the event finishes
15
12
  store = transaction.store('mongo_driver')
16
- store[event.request_id] = command
13
+ store[event.request_id] = Appsignal::Utils.sanitize(event.command)
17
14
 
18
15
  # Start this event
19
16
  Appsignal::Extension.start_event(transaction.transaction_index)
@@ -39,15 +36,14 @@ module Appsignal
39
36
 
40
37
  # Get the query from the transaction store
41
38
  store = transaction.store('mongo_driver')
42
- command = store.delete(event.request_id) || {}
39
+ command = store[event.request_id].inspect
43
40
 
44
41
  # Finish the event in the extension.
45
42
  Appsignal::Extension.finish_event(
46
43
  transaction.transaction_index,
47
44
  'query.mongodb',
48
- "#{event.command_name.to_s} | #{event.database_name} | #{result}",
49
- Appsignal::Utils.json_generate(command),
50
- 0
45
+ event.command_name.to_s,
46
+ %Q(#{event.database_name} | #{result} | #{command})
51
47
  )
52
48
  end
53
49
  end
@@ -36,6 +36,10 @@ module Appsignal
36
36
 
37
37
  Appsignal.start
38
38
  end
39
+
40
+ rake_tasks do
41
+ load 'tasks/diag.rake'
42
+ end
39
43
  end
40
44
  end
41
45
  end
@@ -2,7 +2,7 @@ module Appsignal
2
2
  module Integrations
3
3
  module ResquePlugin
4
4
  def around_perform_resque_plugin(*args)
5
- Appsignal.monitor_single_transaction(
5
+ Appsignal.monitor_transaction(
6
6
  'perform_job.resque',
7
7
  :class => self.to_s,
8
8
  :method => 'perform'
@@ -28,7 +28,7 @@ module Appsignal
28
28
  @transaction_index,
29
29
  @data['name'],
30
30
  @data['message'],
31
- Appsignal::Utils.json_generate(@data['backtrace'])
31
+ JSON.generate(@data['backtrace'])
32
32
  )
33
33
  end
34
34
 
@@ -43,7 +43,7 @@ module Appsignal
43
43
  Appsignal::Extension.set_transaction_sample_data(
44
44
  @transaction_index,
45
45
  key.to_s,
46
- Appsignal::Utils.json_generate(data)
46
+ JSON.generate(data)
47
47
  )
48
48
  rescue JSON::GeneratorError=>e
49
49
  Appsignal.logger.error("JSON generate error (#{e.message}) for '#{data.inspect}'")
@@ -53,7 +53,6 @@ module Appsignal
53
53
 
54
54
  def complete!
55
55
  Appsignal::Extension.finish_transaction(@transaction_index)
56
- Appsignal::Extension.complete_transaction(@transaction_index)
57
56
  end
58
57
  end
59
58
  end
@@ -45,13 +45,12 @@ module Appsignal
45
45
  return unless transaction = Appsignal::Transaction.current
46
46
  return if transaction.nil_transaction? || transaction.paused?
47
47
 
48
- title, body, body_format = Appsignal::EventFormatter.format(name, payload)
48
+ title, body = Appsignal::EventFormatter.format(name, payload)
49
49
  Appsignal::Extension.finish_event(
50
50
  transaction.transaction_index,
51
51
  name,
52
52
  title || BLANK,
53
- body || BLANK,
54
- body_format || 0
53
+ body || BLANK
55
54
  )
56
55
  end
57
56
  end
@@ -113,7 +113,7 @@ module Appsignal
113
113
  Appsignal::Extension.set_transaction_sample_data(
114
114
  transaction_index,
115
115
  key.to_s,
116
- Appsignal::Utils.json_generate(data)
116
+ JSON.generate(data)
117
117
  )
118
118
  rescue JSON::GeneratorError=>e
119
119
  Appsignal.logger.error("JSON generate error (#{e.message}) for '#{data.inspect}'")
@@ -124,7 +124,6 @@ module Appsignal
124
124
  :params => sanitized_params,
125
125
  :environment => sanitized_environment,
126
126
  :session_data => sanitized_session_data,
127
- :metadata => metadata,
128
127
  :tags => sanitized_tags
129
128
  }.each do |key, data|
130
129
  set_sample_data(key, data)
@@ -142,7 +141,7 @@ module Appsignal
142
141
  transaction_index,
143
142
  error.class.name,
144
143
  error.message,
145
- backtrace ? Appsignal::Utils.json_generate(backtrace) : ''
144
+ backtrace ? JSON.generate(backtrace) : ''
146
145
  )
147
146
  rescue JSON::GeneratorError=>e
148
147
  Appsignal.logger.error("JSON generate error (#{e.message}) for '#{backtrace.inspect}'")
@@ -213,11 +212,6 @@ module Appsignal
213
212
  Appsignal::ParamsSanitizer.sanitize(session.to_hash)
214
213
  end
215
214
 
216
- def metadata
217
- return unless request.env
218
- request.env[:metadata]
219
- end
220
-
221
215
  # Only keep tags if they meet the following criteria:
222
216
  # * Key is a symbol or string with less then 100 chars
223
217
  # * Value is a symbol or string with less then 100 chars
@@ -53,7 +53,7 @@ module Appsignal
53
53
  request['Content-Type'] = CONTENT_TYPE
54
54
  request['Content-Encoding'] = CONTENT_ENCODING
55
55
  request.body = Zlib::Deflate.deflate(
56
- Appsignal::Utils.json_generate(payload),
56
+ JSON.generate(payload, :quirks_mode => true),
57
57
  Zlib::BEST_SPEED
58
58
  )
59
59
  end
@@ -1,61 +1,25 @@
1
1
  module Appsignal
2
2
  module Utils
3
- def self.sanitize(params, only_top_level=false, key_sanitizer=nil)
3
+ def self.sanitize(params, only_top_level=false)
4
4
  if params.is_a?(Hash)
5
5
  {}.tap do |hsh|
6
6
  params.each do |key, val|
7
- hsh[self.sanitize_key(key, key_sanitizer)] = if only_top_level
8
- '?'
9
- else
10
- sanitize(val, only_top_level, key_sanitizer=nil)
11
- end
7
+ hsh[key] = only_top_level ? '?' : sanitize(val, only_top_level)
12
8
  end
13
9
  end
14
10
  elsif params.is_a?(Array)
15
11
  if only_top_level
16
- sanitize(params[0], only_top_level, key_sanitizer=nil)
12
+ sanitize(params[0], only_top_level)
13
+ elsif params.first.is_a?(String)
14
+ ['?']
17
15
  else
18
16
  params.map do |item|
19
- sanitize(item, only_top_level, key_sanitizer=nil)
20
- end.uniq
17
+ sanitize(item, only_top_level)
18
+ end
21
19
  end
22
20
  else
23
21
  '?'
24
22
  end
25
23
  end
26
-
27
- def self.sanitize_key(key, sanitizer)
28
- case sanitizer
29
- when :mongodb then key.to_s.gsub(/(\..+)/, '.?')
30
- else key
31
- end
32
- end
33
-
34
- def self.json_generate(body)
35
- JSON.generate(jsonify(body))
36
- end
37
-
38
- def self.jsonify(value)
39
- case value
40
- when String
41
- encode_utf8(value)
42
- when Numeric, NilClass, TrueClass, FalseClass
43
- value
44
- when Hash
45
- Hash[value.map { |k, v| [jsonify(k), jsonify(v)] }]
46
- when Array
47
- value.map { |v| jsonify(v) }
48
- else
49
- jsonify(value.to_s)
50
- end
51
- end
52
-
53
- def self.encode_utf8(value)
54
- value.encode(
55
- 'utf-8'.freeze,
56
- :invalid => :replace,
57
- :undef => :replace
58
- )
59
- end
60
24
  end
61
25
  end
@@ -1,5 +1,5 @@
1
1
  require 'yaml'
2
2
 
3
3
  module Appsignal
4
- VERSION = '1.0.7'
4
+ VERSION = '1.1.0.beta.1'
5
5
  end
@@ -0,0 +1,75 @@
1
+ require 'appsignal'
2
+
3
+ namespace :appsignal do
4
+ namespace :diag do
5
+
6
+ desc "Shows the AppSignal gem version"
7
+ task :gem_version do
8
+ puts "Gem version: #{Appsignal::VERSION}"
9
+ end
10
+
11
+ desc "Shows the agent version"
12
+ task :agent_version do
13
+ puts "Agent version: #{Appsignal::Extension.agent_version}"
14
+ end
15
+
16
+ desc "Attempt to start appsignal"
17
+ task :start_appsignal do
18
+ Appsignal.start
19
+ end
20
+
21
+ desc "Checks if config is present and shows the values"
22
+ task :config => :start_appsignal do
23
+ Appsignal.config.config_hash.each do |key, val|
24
+ puts "Config #{key}: #{val}"
25
+ end
26
+ end
27
+
28
+ desc "Checks if required paths are writeable"
29
+ task :paths_writable => :start_appsignal do
30
+ possible_paths = [
31
+ Appsignal.config.root_path,
32
+ Appsignal.config.log_file_path
33
+ ]
34
+
35
+ puts "Checking if required paths are writable:"
36
+ possible_paths.each do |path|
37
+ result = File.writable?(path) ? 'Ok' : 'Failed'
38
+ puts "#{path} ...#{result}"
39
+ end
40
+ puts "\n"
41
+ end
42
+
43
+ desc "Check if API key is valid"
44
+ task :check_api_key => :start_appsignal do
45
+ auth_check = ::Appsignal::AuthCheck.new(Appsignal.config, Appsignal.logger)
46
+ status, result = auth_check.perform_with_result
47
+ if status == '200'
48
+ puts "Checking API key: Ok"
49
+ else
50
+ puts "Checking API key: Failed"
51
+ end
52
+ end
53
+
54
+ desc "Check the ext installation log"
55
+ task :check_ext_install do
56
+ require 'bundler/cli'
57
+ require "bundler/cli/common"
58
+ path = Bundler::CLI::Common.select_spec('appsignal').full_gem_path
59
+ log_path = "#{path.strip}/ext/install.log"
60
+ puts "Showing last lines of extension install log: #{log_path}"
61
+ puts File.read(log_path)
62
+ puts "\n"
63
+ end
64
+
65
+ task :run => [
66
+ "diag:gem_version",
67
+ "diag:agent_version",
68
+ "diag:start_appsignal",
69
+ "diag:config",
70
+ "diag:check_api_key",
71
+ "diag:paths_writable",
72
+ "diag:check_ext_install"
73
+ ]
74
+ end
75
+ end
@@ -80,29 +80,9 @@ if capistrano3_present?
80
80
  )
81
81
  end
82
82
  end
83
-
84
- context "when stage is used instead of rack_env / rails_env" do
85
- before do
86
- @capistrano_config.delete(:rails_env)
87
- @capistrano_config.set(:stage, 'stage_production')
88
- end
89
-
90
- it "should be instantiated with the right params" do
91
- Appsignal::Config.should_receive(:new).with(
92
- project_fixture_path,
93
- 'stage_production',
94
- {:name => 'AppName'},
95
- kind_of(Logger)
96
- )
97
- end
98
- end
99
83
  end
100
84
 
101
- after do
102
- invoke('appsignal:deploy')
103
- @capistrano_config.delete(:stage)
104
- @capistrano_config.delete(:rack_env)
105
- end
85
+ after { invoke('appsignal:deploy') }
106
86
  end
107
87
 
108
88
  context "send marker" do
@@ -98,18 +98,6 @@ describe Appsignal::Config do
98
98
  ENV['APPSIGNAL_HTTP_PROXY'].should == 'http://localhost'
99
99
  ENV['APPSIGNAL_IGNORE_ACTIONS'].should == 'action1,action2'
100
100
  ENV['APPSIGNAL_RUNNING_IN_CONTAINER'].should == 'false'
101
- ENV['APPSIGNAL_WORKING_DIR_PATH'].should be_nil
102
- end
103
-
104
- context "if working_dir_path is set" do
105
- before do
106
- subject.config_hash[:working_dir_path] = '/tmp/appsignal2'
107
- subject.write_to_environment
108
- end
109
-
110
- it "should write the current config to env vars" do
111
- ENV['APPSIGNAL_WORKING_DIR_PATH'].should == '/tmp/appsignal2'
112
- end
113
101
  end
114
102
  end
115
103