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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +5 -21
- data/Rakefile +2 -0
- data/circle.yml +2 -1
- data/ext/agent.yml +7 -7
- data/ext/appsignal_extension.c +3 -5
- data/ext/extconf.rb +6 -15
- data/gemfiles/grape.gemfile +7 -0
- data/lib/appsignal/config.rb +2 -5
- data/lib/appsignal/event_formatter.rb +0 -2
- data/lib/appsignal/event_formatter/active_record/sql_formatter.rb +47 -1
- data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +29 -0
- data/lib/appsignal/event_formatter/moped/query_formatter.rb +7 -6
- data/lib/appsignal/hooks.rb +33 -0
- data/lib/appsignal/hooks/net_http.rb +1 -1
- data/lib/appsignal/hooks/sequel.rb +7 -4
- data/lib/appsignal/hooks/sidekiq.rb +10 -19
- data/lib/appsignal/integrations/capistrano/appsignal.cap +1 -1
- data/lib/appsignal/integrations/delayed_job_plugin.rb +20 -11
- data/lib/appsignal/integrations/grape.rb +44 -0
- data/lib/appsignal/integrations/mongo_ruby_driver.rb +5 -9
- data/lib/appsignal/integrations/railtie.rb +4 -0
- data/lib/appsignal/integrations/resque.rb +1 -1
- data/lib/appsignal/js_exception_transaction.rb +2 -3
- data/lib/appsignal/subscriber.rb +2 -3
- data/lib/appsignal/transaction.rb +2 -8
- data/lib/appsignal/transmitter.rb +1 -1
- data/lib/appsignal/utils.rb +7 -43
- data/lib/appsignal/version.rb +1 -1
- data/lib/tasks/diag.rake +75 -0
- data/spec/lib/appsignal/capistrano3_spec.rb +1 -21
- data/spec/lib/appsignal/config_spec.rb +0 -12
- data/spec/lib/appsignal/event_formatter/active_record/instantiation_formatter_spec.rb +1 -1
- data/spec/lib/appsignal/event_formatter/active_record/sql_formatter_spec.rb +186 -14
- data/spec/lib/appsignal/event_formatter/elastic_search/search_formatter_spec.rb +54 -0
- data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +4 -4
- data/spec/lib/appsignal/extension_spec.rb +1 -1
- data/spec/lib/appsignal/hooks/delayed_job_spec.rb +49 -14
- data/spec/lib/appsignal/hooks/sequel_spec.rb +1 -1
- data/spec/lib/appsignal/hooks/sidekiq_spec.rb +29 -62
- data/spec/lib/appsignal/hooks_spec.rb +115 -0
- data/spec/lib/appsignal/integrations/grape_spec.rb +94 -0
- data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +5 -8
- data/spec/lib/appsignal/integrations/resque_spec.rb +0 -1
- data/spec/lib/appsignal/js_exception_transaction_spec.rb +0 -1
- data/spec/lib/appsignal/subscriber_spec.rb +5 -23
- data/spec/lib/appsignal/transaction_spec.rb +0 -21
- data/spec/lib/appsignal/utils_spec.rb +1 -68
- data/spec/spec_helper.rb +16 -0
- data/spec/support/helpers/env_helpers.rb +0 -1
- data/spec/support/stubs/delayed_job.rb +0 -0
- metadata +15 -11
- data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +0 -88
- data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +0 -13
- data/spec/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter_spec.rb +0 -115
- 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(:
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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 =>
|
28
|
-
:queue =>
|
29
|
-
:priority =>
|
30
|
-
:attempts =>
|
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
|
-
:
|
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
|
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
|
-
|
49
|
-
|
50
|
-
0
|
45
|
+
event.command_name.to_s,
|
46
|
+
%Q(#{event.database_name} | #{result} | #{command})
|
51
47
|
)
|
52
48
|
end
|
53
49
|
end
|
@@ -28,7 +28,7 @@ module Appsignal
|
|
28
28
|
@transaction_index,
|
29
29
|
@data['name'],
|
30
30
|
@data['message'],
|
31
|
-
|
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
|
-
|
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
|
data/lib/appsignal/subscriber.rb
CHANGED
@@ -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
|
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
|
-
|
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 ?
|
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
|
-
|
56
|
+
JSON.generate(payload, :quirks_mode => true),
|
57
57
|
Zlib::BEST_SPEED
|
58
58
|
)
|
59
59
|
end
|
data/lib/appsignal/utils.rb
CHANGED
@@ -1,61 +1,25 @@
|
|
1
1
|
module Appsignal
|
2
2
|
module Utils
|
3
|
-
def self.sanitize(params, only_top_level=false
|
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[
|
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
|
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
|
20
|
-
end
|
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
|
data/lib/appsignal/version.rb
CHANGED
data/lib/tasks/diag.rake
ADDED
@@ -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
|
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
|
|