appsignal 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +19 -0
- data/.rvmrc +1 -0
- data/.travis.yml +30 -0
- data/Gemfile +3 -0
- data/LICENCE +20 -0
- data/README.md +48 -0
- data/Rakefile +52 -0
- data/appsignal.gemspec +33 -0
- data/bin/appsignal +13 -0
- data/config/appsignal.yml +8 -0
- data/gemfiles/3.0.gemfile +16 -0
- data/gemfiles/3.1.gemfile +16 -0
- data/gemfiles/3.2.gemfile +16 -0
- data/gemfiles/edge.gemfile +16 -0
- data/lib/appsignal.rb +45 -0
- data/lib/appsignal/agent.rb +104 -0
- data/lib/appsignal/auth_check.rb +19 -0
- data/lib/appsignal/capistrano.rb +41 -0
- data/lib/appsignal/cli.rb +118 -0
- data/lib/appsignal/config.rb +30 -0
- data/lib/appsignal/exception_notification.rb +25 -0
- data/lib/appsignal/marker.rb +35 -0
- data/lib/appsignal/middleware.rb +30 -0
- data/lib/appsignal/railtie.rb +19 -0
- data/lib/appsignal/transaction.rb +77 -0
- data/lib/appsignal/transaction/faulty_request_formatter.rb +30 -0
- data/lib/appsignal/transaction/params_sanitizer.rb +36 -0
- data/lib/appsignal/transaction/regular_request_formatter.rb +11 -0
- data/lib/appsignal/transaction/slow_request_formatter.rb +34 -0
- data/lib/appsignal/transaction/transaction_formatter.rb +93 -0
- data/lib/appsignal/transmitter.rb +53 -0
- data/lib/appsignal/version.rb +3 -0
- data/lib/generators/appsignal/USAGE +8 -0
- data/lib/generators/appsignal/appsignal_generator.rb +70 -0
- data/lib/generators/appsignal/templates/appsignal.yml +4 -0
- data/log/.gitkeep +0 -0
- data/resources/cacert.pem +3849 -0
- data/spec/appsignal/agent_spec.rb +259 -0
- data/spec/appsignal/auth_check_spec.rb +36 -0
- data/spec/appsignal/capistrano_spec.rb +81 -0
- data/spec/appsignal/cli_spec.rb +124 -0
- data/spec/appsignal/config_spec.rb +40 -0
- data/spec/appsignal/exception_notification_spec.rb +12 -0
- data/spec/appsignal/inactive_railtie_spec.rb +30 -0
- data/spec/appsignal/marker_spec.rb +83 -0
- data/spec/appsignal/middleware_spec.rb +73 -0
- data/spec/appsignal/railtie_spec.rb +54 -0
- data/spec/appsignal/transaction/faulty_request_formatter_spec.rb +49 -0
- data/spec/appsignal/transaction/params_sanitizer_spec.rb +68 -0
- data/spec/appsignal/transaction/regular_request_formatter_spec.rb +14 -0
- data/spec/appsignal/transaction/slow_request_formatter_spec.rb +76 -0
- data/spec/appsignal/transaction/transaction_formatter_spec.rb +178 -0
- data/spec/appsignal/transaction_spec.rb +191 -0
- data/spec/appsignal/transmitter_spec.rb +64 -0
- data/spec/appsignal_spec.rb +66 -0
- data/spec/generators/appsignal/appsignal_generator_spec.rb +222 -0
- data/spec/spec_helper.rb +85 -0
- data/spec/support/delegate_matcher.rb +39 -0
- metadata +247 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Appsignal
|
2
|
+
class AuthCheck
|
3
|
+
delegate :uri, :to => :transmitter
|
4
|
+
attr_reader :config
|
5
|
+
attr_accessor :transmitter
|
6
|
+
ACTION = 'auth'
|
7
|
+
|
8
|
+
def initialize(environment)
|
9
|
+
@config = Appsignal::Config.new(Rails.root, environment).load
|
10
|
+
end
|
11
|
+
|
12
|
+
def perform
|
13
|
+
self.transmitter = Appsignal::Transmitter.new(
|
14
|
+
@config[:endpoint], ACTION, @config[:api_key]
|
15
|
+
)
|
16
|
+
transmitter.transmit({})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'capistrano'
|
2
|
+
require 'rails'
|
3
|
+
require 'appsignal/version'
|
4
|
+
require 'appsignal/config'
|
5
|
+
require 'appsignal/transmitter'
|
6
|
+
require 'appsignal/marker'
|
7
|
+
|
8
|
+
module Appsignal
|
9
|
+
class Capistrano
|
10
|
+
def self.tasks(config)
|
11
|
+
config.load do
|
12
|
+
after "deploy", "appsignal:deploy"
|
13
|
+
after "deploy:migrations", "appsignal:deploy"
|
14
|
+
|
15
|
+
namespace :appsignal do
|
16
|
+
task :deploy do
|
17
|
+
rails_env = fetch(:rails_env, 'production')
|
18
|
+
user = ENV['USER'] || ENV['USERNAME']
|
19
|
+
|
20
|
+
marker_data = {
|
21
|
+
:revision => current_revision,
|
22
|
+
:repository => repository,
|
23
|
+
:user => user
|
24
|
+
}
|
25
|
+
|
26
|
+
marker = Marker.new(marker_data, ENV['PWD'], rails_env, logger)
|
27
|
+
if config.dry_run
|
28
|
+
logger.info "Dry run: Deploy marker not actually sent."
|
29
|
+
else
|
30
|
+
marker.transmit
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if Capistrano::Configuration.instance
|
40
|
+
Appsignal::Capistrano.tasks(Capistrano::Configuration.instance)
|
41
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'logger'
|
3
|
+
require 'yaml'
|
4
|
+
require 'rails'
|
5
|
+
require 'appsignal/version'
|
6
|
+
require 'appsignal/config'
|
7
|
+
require 'appsignal/marker'
|
8
|
+
require 'appsignal/transmitter'
|
9
|
+
|
10
|
+
module Appsignal
|
11
|
+
class CLI
|
12
|
+
AVAILABLE_COMMANDS = %w( notify_of_deploy )
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def run(argv=ARGV)
|
16
|
+
unless File.exists?(File.join(ENV['PWD'], 'config/appsignal.yml'))
|
17
|
+
puts 'No config file present at config/appsignal.yml'
|
18
|
+
puts 'Log in to https://appsignal.com to get instructions on how to generate the config file.'
|
19
|
+
exit(1)
|
20
|
+
end
|
21
|
+
options = {}
|
22
|
+
global = global_option_parser(options)
|
23
|
+
commands = command_option_parser(options)
|
24
|
+
|
25
|
+
global.order!(argv)
|
26
|
+
command = argv.shift
|
27
|
+
if command then
|
28
|
+
if AVAILABLE_COMMANDS.include?(command) then
|
29
|
+
commands[command].parse!(argv)
|
30
|
+
case options[:command]
|
31
|
+
when :notify_of_deploy
|
32
|
+
notify_of_deploy(options)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
puts "Command '#{command}' does not exist, run appsignal -h to see the help"
|
36
|
+
exit(1)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
# Print help
|
40
|
+
puts global
|
41
|
+
exit(0)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def logger
|
46
|
+
Logger.new($stdout)
|
47
|
+
end
|
48
|
+
|
49
|
+
def global_option_parser(options)
|
50
|
+
OptionParser.new do |o|
|
51
|
+
o.banner = %Q{Usage: appsignal <command> [options]}
|
52
|
+
|
53
|
+
o.on '-v', '--version', "Print version and exit" do |arg|
|
54
|
+
puts "Appsignal #{Appsignal::VERSION}"
|
55
|
+
exit(0)
|
56
|
+
end
|
57
|
+
|
58
|
+
o.on '-h', '--help', "Show help and exit" do
|
59
|
+
puts o
|
60
|
+
exit(0)
|
61
|
+
end
|
62
|
+
|
63
|
+
o.separator ''
|
64
|
+
o.separator "Available commands: #{AVAILABLE_COMMANDS.join(', ')}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def command_option_parser(options)
|
69
|
+
{
|
70
|
+
'notify_of_deploy' => OptionParser.new do |o|
|
71
|
+
o.banner = %Q{Usage: appsignal notify_of_deploy [options] }
|
72
|
+
options[:command] = :notify_of_deploy
|
73
|
+
|
74
|
+
o.on '--revision=<revision>', "The revision you're deploying" do |arg|
|
75
|
+
options[:revision] = arg
|
76
|
+
end
|
77
|
+
|
78
|
+
o.on '--repository=<repository>', "The location of the main code repository" do |arg|
|
79
|
+
options[:repository] = arg
|
80
|
+
end
|
81
|
+
|
82
|
+
o.on '--user=<user>', "The name of the user that's deploying" do |arg|
|
83
|
+
options[:user] = arg
|
84
|
+
end
|
85
|
+
|
86
|
+
o.on '--environment=<rails_env>', "The environment you're deploying to" do |arg|
|
87
|
+
options[:environment] = arg
|
88
|
+
end
|
89
|
+
end
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
def notify_of_deploy(options)
|
94
|
+
validate_required_options([:revision, :repository, :user, :environment], options)
|
95
|
+
Appsignal::Marker.new(
|
96
|
+
{
|
97
|
+
:revision => options[:revision],
|
98
|
+
:repository => options[:repository],
|
99
|
+
:user => options[:user]
|
100
|
+
},
|
101
|
+
ENV['PWD'],
|
102
|
+
options[:environment],
|
103
|
+
logger
|
104
|
+
).transmit
|
105
|
+
end
|
106
|
+
|
107
|
+
def validate_required_options(required_options, options)
|
108
|
+
missing = required_options.select do |required_option|
|
109
|
+
options[required_option].blank?
|
110
|
+
end
|
111
|
+
if missing.any?
|
112
|
+
puts "Missing options: #{missing.join(', ')}"
|
113
|
+
exit(1)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Appsignal
|
2
|
+
class Config
|
3
|
+
attr_accessor :root_path, :rails_env
|
4
|
+
|
5
|
+
def initialize(root_path, rails_env, logger=Appsignal.logger)
|
6
|
+
@root_path = root_path
|
7
|
+
@rails_env = rails_env
|
8
|
+
@logger = logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def load
|
12
|
+
file = File.join(@root_path, 'config/appsignal.yml')
|
13
|
+
unless File.exists?(file)
|
14
|
+
@logger.error "config not found at: #{file}"
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
config = YAML.load_file(file)[@rails_env]
|
19
|
+
unless config
|
20
|
+
@logger.error "config for '#{@rails_env}' not found"
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
config = {:ignore_exceptions => [],
|
25
|
+
:endpoint => 'https://push.appsignal.com/1',
|
26
|
+
:slow_request_threshold => 200
|
27
|
+
}.merge(config.symbolize_keys)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Appsignal
|
2
|
+
class MissingController
|
3
|
+
def method_missing(*args, &block)
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class ExceptionNotification
|
8
|
+
attr_reader :env, :exception, :kontroller, :request, :backtrace
|
9
|
+
|
10
|
+
def initialize(env, exception)
|
11
|
+
@exception = exception
|
12
|
+
@backtrace = Rails.respond_to?(:backtrace_cleaner) ?
|
13
|
+
Rails.backtrace_cleaner.send(:filter, exception.backtrace) :
|
14
|
+
exception.backtrace
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@exception.class.name
|
19
|
+
end
|
20
|
+
|
21
|
+
def message
|
22
|
+
@exception.message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Appsignal
|
2
|
+
class Marker
|
3
|
+
attr_reader :marker_data, :config, :logger
|
4
|
+
ACTION = 'markers'
|
5
|
+
|
6
|
+
def initialize(marker_data, root_path, rails_env, logger)
|
7
|
+
@marker_data = marker_data
|
8
|
+
@config = Appsignal::Config.new(root_path, rails_env, logger).load
|
9
|
+
@logger = logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def transmit
|
13
|
+
begin
|
14
|
+
transmitter = Transmitter.new(
|
15
|
+
@config[:endpoint], ACTION, @config[:api_key]
|
16
|
+
)
|
17
|
+
@logger.info "Notifying Appsignal of deploy..."
|
18
|
+
result = transmitter.transmit(marker_data)
|
19
|
+
if result == '200'
|
20
|
+
@logger.info "Appsignal has been notified of this deploy!"
|
21
|
+
else
|
22
|
+
raise "#{result} at #{transmitter.uri}"
|
23
|
+
end
|
24
|
+
rescue Exception => e
|
25
|
+
message = "Something went wrong while trying to notify Appsignal: #{e}"
|
26
|
+
if @logger.respond_to?(:important)
|
27
|
+
# This is a Capistrano logger
|
28
|
+
@logger.important message
|
29
|
+
else
|
30
|
+
@logger.error message
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'action_dispatch'
|
2
|
+
|
3
|
+
module Appsignal
|
4
|
+
class Middleware
|
5
|
+
def initialize(app, options = {})
|
6
|
+
@app, @options = app, options
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
Appsignal::Transaction.create(env['action_dispatch.request_id'], env)
|
11
|
+
@app.call(env)
|
12
|
+
rescue Exception => exception
|
13
|
+
unless in_ignored_exceptions?(exception)
|
14
|
+
Appsignal::Transaction.current.add_exception(
|
15
|
+
Appsignal::ExceptionNotification.new(env, exception)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
raise exception
|
19
|
+
ensure
|
20
|
+
Appsignal::Transaction.current.complete!
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def in_ignored_exceptions?(exception)
|
26
|
+
Array.wrap(Appsignal.config[:ignore_exceptions]).
|
27
|
+
include?(exception.class.name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Appsignal
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer "appsignal.configure_rails_initialization" do |app|
|
4
|
+
if Appsignal.active?
|
5
|
+
app.middleware.insert_before ActionDispatch::RemoteIp, Appsignal::Middleware
|
6
|
+
|
7
|
+
Appsignal.subscriber = ActiveSupport::Notifications.subscribe(/^[^!]/) do |*args|
|
8
|
+
if Appsignal::Transaction.current
|
9
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
10
|
+
if event.name == 'process_action.action_controller'
|
11
|
+
Appsignal::Transaction.current.set_process_action_event(event)
|
12
|
+
end
|
13
|
+
Appsignal::Transaction.current.add_event(event)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'appsignal/transaction/transaction_formatter'
|
3
|
+
|
4
|
+
module Appsignal
|
5
|
+
class Transaction
|
6
|
+
def self.create(key, env)
|
7
|
+
Thread.current[:appsignal_transaction_id] = key
|
8
|
+
Appsignal.transactions[key] = Appsignal::Transaction.new(key, env)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.current
|
12
|
+
Appsignal.transactions[Thread.current[:appsignal_transaction_id]]
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :id, :events, :process_action_event, :action, :exception, :env
|
16
|
+
|
17
|
+
def initialize(id, env)
|
18
|
+
@id = id
|
19
|
+
@events = []
|
20
|
+
@process_action_event = nil
|
21
|
+
@exception = nil
|
22
|
+
@env = env
|
23
|
+
end
|
24
|
+
|
25
|
+
def request
|
26
|
+
ActionDispatch::Request.new(@env)
|
27
|
+
end
|
28
|
+
|
29
|
+
def set_process_action_event(event)
|
30
|
+
@process_action_event = event
|
31
|
+
if @process_action_event && @process_action_event.payload
|
32
|
+
@action = "#{process_action_event.payload[:controller]}#"\
|
33
|
+
"#{process_action_event.payload[:action]}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_event(event)
|
38
|
+
@events << event
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_exception(ex)
|
42
|
+
@exception = ex
|
43
|
+
end
|
44
|
+
|
45
|
+
def exception?
|
46
|
+
!!exception
|
47
|
+
end
|
48
|
+
|
49
|
+
def slow_request?
|
50
|
+
return false unless process_action_event && process_action_event.payload
|
51
|
+
Appsignal.config[:slow_request_threshold] <= process_action_event.duration
|
52
|
+
end
|
53
|
+
|
54
|
+
def clear_payload_and_events!
|
55
|
+
@process_action_event.payload.clear
|
56
|
+
@events.clear
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_hash
|
60
|
+
if exception?
|
61
|
+
TransactionFormatter.faulty(self)
|
62
|
+
elsif slow_request?
|
63
|
+
TransactionFormatter.slow(self)
|
64
|
+
else
|
65
|
+
TransactionFormatter.regular(self)
|
66
|
+
end.to_hash
|
67
|
+
end
|
68
|
+
|
69
|
+
def complete!
|
70
|
+
Thread.current[:appsignal_transaction_id] = nil
|
71
|
+
current_transaction = Appsignal.transactions.delete(@id)
|
72
|
+
if process_action_event || exception?
|
73
|
+
Appsignal.agent.add_to_queue(current_transaction)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Appsignal
|
2
|
+
class TransactionFormatter
|
3
|
+
class FaultyRequestFormatter < Appsignal::TransactionFormatter
|
4
|
+
|
5
|
+
def to_hash
|
6
|
+
super.merge :exception => formatted_exception
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def_delegators :exception, :backtrace, :name, :message
|
12
|
+
|
13
|
+
def formatted_exception
|
14
|
+
{
|
15
|
+
:backtrace => backtrace,
|
16
|
+
:exception => name,
|
17
|
+
:message => message
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def basic_process_action_event
|
22
|
+
super.merge(
|
23
|
+
:environment => filtered_environment,
|
24
|
+
:session_data => request.session
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Appsignal
|
2
|
+
class ParamsSanitizer
|
3
|
+
class << self
|
4
|
+
def sanitize(params)
|
5
|
+
sanitize_hash(params)
|
6
|
+
end
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def sanitize_hash(hash)
|
11
|
+
out = {}
|
12
|
+
hash.each_pair do |key, value|
|
13
|
+
out[key] = sanitize_value(value)
|
14
|
+
end
|
15
|
+
out
|
16
|
+
end
|
17
|
+
|
18
|
+
def sanitize_array(array)
|
19
|
+
array.map { |value| sanitize_value(value) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def sanitize_value(value)
|
23
|
+
case value
|
24
|
+
when Hash
|
25
|
+
sanitize_hash(value)
|
26
|
+
when Array
|
27
|
+
sanitize_array(value)
|
28
|
+
when String
|
29
|
+
value
|
30
|
+
else
|
31
|
+
value.inspect
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|