appsignal 0.4.0
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.
- 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
|