appsignal 1.0.7 → 1.1.0.beta.1

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 (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