projectlocker_pulse 0.2.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.
- data/CHANGELOG +26 -0
- data/Gemfile +3 -0
- data/Guardfile +6 -0
- data/INSTALL +20 -0
- data/MIT-LICENSE +23 -0
- data/README.md +439 -0
- data/README_FOR_HEROKU_ADDON.md +89 -0
- data/Rakefile +223 -0
- data/SUPPORTED_RAILS_VERSIONS +38 -0
- data/TESTING.md +41 -0
- data/features/metal.feature +18 -0
- data/features/rack.feature +60 -0
- data/features/rails.feature +272 -0
- data/features/rails_with_js_notifier.feature +97 -0
- data/features/rake.feature +27 -0
- data/features/sinatra.feature +29 -0
- data/features/step_definitions/file_steps.rb +10 -0
- data/features/step_definitions/metal_steps.rb +23 -0
- data/features/step_definitions/rack_steps.rb +23 -0
- data/features/step_definitions/rails_application_steps.rb +478 -0
- data/features/step_definitions/rake_steps.rb +17 -0
- data/features/support/env.rb +18 -0
- data/features/support/matchers.rb +35 -0
- data/features/support/projectlocker_pulse_shim.rb.template +16 -0
- data/features/support/rails.rb +201 -0
- data/features/support/rake/Rakefile +68 -0
- data/features/support/terminal.rb +107 -0
- data/features/user_informer.feature +63 -0
- data/generators/pulse/lib/insert_commands.rb +34 -0
- data/generators/pulse/lib/rake_commands.rb +24 -0
- data/generators/pulse/pulse_generator.rb +94 -0
- data/generators/pulse/templates/capistrano_hook.rb +6 -0
- data/generators/pulse/templates/initializer.rb +6 -0
- data/generators/pulse/templates/pulse_tasks.rake +25 -0
- data/install.rb +1 -0
- data/lib/projectlocker_pulse.rb +159 -0
- data/lib/pulse/backtrace.rb +108 -0
- data/lib/pulse/capistrano.rb +43 -0
- data/lib/pulse/configuration.rb +305 -0
- data/lib/pulse/notice.rb +390 -0
- data/lib/pulse/rack.rb +54 -0
- data/lib/pulse/rails/action_controller_catcher.rb +30 -0
- data/lib/pulse/rails/controller_methods.rb +85 -0
- data/lib/pulse/rails/error_lookup.rb +33 -0
- data/lib/pulse/rails/javascript_notifier.rb +47 -0
- data/lib/pulse/rails/middleware/exceptions_catcher.rb +33 -0
- data/lib/pulse/rails.rb +40 -0
- data/lib/pulse/rails3_tasks.rb +99 -0
- data/lib/pulse/railtie.rb +49 -0
- data/lib/pulse/rake_handler.rb +65 -0
- data/lib/pulse/sender.rb +128 -0
- data/lib/pulse/shared_tasks.rb +47 -0
- data/lib/pulse/tasks.rb +83 -0
- data/lib/pulse/user_informer.rb +27 -0
- data/lib/pulse/utils/blank.rb +53 -0
- data/lib/pulse/version.rb +3 -0
- data/lib/pulse_tasks.rb +64 -0
- data/lib/rails/generators/pulse/pulse_generator.rb +100 -0
- data/lib/templates/javascript_notifier.erb +15 -0
- data/lib/templates/rescue.erb +91 -0
- data/pulse.gemspec +39 -0
- data/rails/init.rb +1 -0
- data/resources/README.md +34 -0
- data/resources/ca-bundle.crt +3376 -0
- data/script/integration_test.rb +38 -0
- data/test/backtrace_test.rb +162 -0
- data/test/capistrano_test.rb +34 -0
- data/test/catcher_test.rb +333 -0
- data/test/configuration_test.rb +236 -0
- data/test/helper.rb +263 -0
- data/test/javascript_notifier_test.rb +51 -0
- data/test/logger_test.rb +79 -0
- data/test/notice_test.rb +490 -0
- data/test/notifier_test.rb +276 -0
- data/test/projectlocker_pulse_tasks_test.rb +170 -0
- data/test/pulse.xsd +88 -0
- data/test/rack_test.rb +58 -0
- data/test/rails_initializer_test.rb +36 -0
- data/test/recursion_test.rb +10 -0
- data/test/sender_test.rb +288 -0
- data/test/user_informer_test.rb +29 -0
- metadata +432 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
# Don't load anything when running the gems:* tasks.
|
2
|
+
# Otherwise, projectlocker-pulse will be considered a framework gem.
|
3
|
+
# https://thoughtbot.lighthouseapp.com/projects/14221/tickets/629
|
4
|
+
unless ARGV.any? {|a| a =~ /^gems/}
|
5
|
+
|
6
|
+
Dir[File.join(Rails.root, 'vendor', 'gems', 'projectlocker-pulse-*')].each do |vendored_notifier|
|
7
|
+
$: << File.join(vendored_notifier, 'lib')
|
8
|
+
end
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'pulse/tasks'
|
12
|
+
rescue LoadError => exception
|
13
|
+
namespace :pulse do
|
14
|
+
%w(deploy test log_stdout).each do |task_name|
|
15
|
+
desc "Missing dependency for pulse:#{task_name}"
|
16
|
+
task task_name do
|
17
|
+
$stderr.puts "Failed to run pulse:#{task_name} because of missing dependency."
|
18
|
+
$stderr.puts "You probably need to run `rake gems:install` to install the projectlocker-pulse gem"
|
19
|
+
abort exception.inspect
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
puts IO.read(File.join(File.dirname(__FILE__), 'INSTALL'))
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require "girl_friday"
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'rubygems'
|
5
|
+
require 'pulse/utils/blank'
|
6
|
+
require 'pulse/version'
|
7
|
+
require 'pulse/configuration'
|
8
|
+
require 'pulse/notice'
|
9
|
+
require 'pulse/sender'
|
10
|
+
require 'pulse/backtrace'
|
11
|
+
require 'pulse/rack'
|
12
|
+
require 'pulse/user_informer'
|
13
|
+
|
14
|
+
require 'pulse/railtie' if defined?(Rails::Railtie)
|
15
|
+
|
16
|
+
module Pulse
|
17
|
+
API_VERSION = "2.3"
|
18
|
+
LOG_PREFIX = "** [Pulse] "
|
19
|
+
|
20
|
+
HEADERS = {
|
21
|
+
'Content-type' => 'text/xml',
|
22
|
+
'Accept' => 'text/xml, application/xml'
|
23
|
+
}
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# The sender object is responsible for delivering formatted data to the Pulse server.
|
27
|
+
# Must respond to #send_to_pulse. See Pulse::Sender.
|
28
|
+
attr_accessor :sender
|
29
|
+
|
30
|
+
# A Pulse configuration object. Must act like a hash and return sensible
|
31
|
+
# values for all Pulse configuration options. See Pulse::Configuration.
|
32
|
+
attr_writer :configuration
|
33
|
+
|
34
|
+
# Tell the log that the Notifier is good to go
|
35
|
+
def report_ready
|
36
|
+
write_verbose_log("Notifier #{VERSION} ready to catch errors")
|
37
|
+
end
|
38
|
+
|
39
|
+
# Prints out the environment info to the log for debugging help
|
40
|
+
def report_environment_info
|
41
|
+
write_verbose_log("Environment Info: #{environment_info}")
|
42
|
+
end
|
43
|
+
|
44
|
+
# Prints out the response body from Pulse for debugging help
|
45
|
+
def report_response_body(response)
|
46
|
+
write_verbose_log("Response from Pulse: \n#{response}")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Prints out the details about the notice that wasn't sent to server
|
50
|
+
def report_notice(notice)
|
51
|
+
write_verbose_log("Notice details: \n#{notice}")
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the Ruby version, Rails version, and current Rails environment
|
55
|
+
def environment_info
|
56
|
+
info = "[Ruby: #{RUBY_VERSION}]"
|
57
|
+
info << " [#{configuration.framework}]" if configuration.framework
|
58
|
+
info << " [Env: #{configuration.environment_name}]" if configuration.environment_name
|
59
|
+
end
|
60
|
+
|
61
|
+
# Writes out the given message to the #logger
|
62
|
+
def write_verbose_log(message)
|
63
|
+
logger.debug LOG_PREFIX + message if logger
|
64
|
+
end
|
65
|
+
|
66
|
+
# Look for the Rails logger currently defined
|
67
|
+
def logger
|
68
|
+
self.configuration.logger
|
69
|
+
end
|
70
|
+
|
71
|
+
# Call this method to modify defaults in your initializers.
|
72
|
+
#
|
73
|
+
# @example
|
74
|
+
# Pulse.configure do |config|
|
75
|
+
# config.api_key = '1234567890abcdef'
|
76
|
+
# config.secure = false
|
77
|
+
# end
|
78
|
+
def configure(silent = false)
|
79
|
+
yield(configuration)
|
80
|
+
self.sender = Sender.new(configuration)
|
81
|
+
report_ready unless silent
|
82
|
+
self.sender
|
83
|
+
end
|
84
|
+
|
85
|
+
# The configuration object.
|
86
|
+
# @see Pulse.configure
|
87
|
+
def configuration
|
88
|
+
@configuration ||= Configuration.new
|
89
|
+
end
|
90
|
+
|
91
|
+
# Sends an exception manually using this method, even when you are not in a controller.
|
92
|
+
#
|
93
|
+
# @param [Exception] exception The exception you want to notify Pulse about.
|
94
|
+
# @param [Hash] opts Data that will be sent to Pulse.
|
95
|
+
#
|
96
|
+
# @option opts [String] :api_key The API key for this project. The API key is a unique identifier that Pulse uses for identification.
|
97
|
+
# @option opts [String] :error_message The error returned by the exception (or the message you want to log).
|
98
|
+
# @option opts [String] :backtrace A backtrace, usually obtained with +caller+.
|
99
|
+
# @option opts [String] :rack_env The Rack environment.
|
100
|
+
# @option opts [String] :session The contents of the user's session.
|
101
|
+
# @option opts [String] :environment_name The application environment name.
|
102
|
+
def notify(exception, opts = {})
|
103
|
+
send_notice(build_notice_for(exception, opts))
|
104
|
+
end
|
105
|
+
|
106
|
+
# Sends the notice unless it is one of the default ignored exceptions
|
107
|
+
# @see Pulse.notify
|
108
|
+
def notify_or_ignore(exception, opts = {})
|
109
|
+
notice = build_notice_for(exception, opts)
|
110
|
+
send_notice(notice) unless notice.ignore?
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_lookup_hash_for(exception, options = {})
|
114
|
+
notice = build_notice_for(exception, options)
|
115
|
+
|
116
|
+
result = {}
|
117
|
+
result[:action] = notice.action rescue nil
|
118
|
+
result[:component] = notice.component rescue nil
|
119
|
+
result[:error_class] = notice.error_class if notice.error_class
|
120
|
+
result[:environment_name] = 'production'
|
121
|
+
|
122
|
+
unless notice.backtrace.lines.empty?
|
123
|
+
result[:file] = notice.backtrace.lines.first.file
|
124
|
+
result[:line_number] = notice.backtrace.lines.first.number
|
125
|
+
end
|
126
|
+
|
127
|
+
result
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def send_notice(notice)
|
133
|
+
if configuration.public?
|
134
|
+
if configuration.async?
|
135
|
+
configuration.async.call(notice)
|
136
|
+
else
|
137
|
+
sender.send_to_pulse(notice)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def build_notice_for(exception, opts = {})
|
143
|
+
exception = unwrap_exception(exception)
|
144
|
+
opts = opts.merge(:exception => exception) if exception.is_a?(Exception)
|
145
|
+
opts = opts.merge(exception.to_hash) if exception.respond_to?(:to_hash)
|
146
|
+
Notice.new(configuration.merge(opts))
|
147
|
+
end
|
148
|
+
|
149
|
+
def unwrap_exception(exception)
|
150
|
+
if exception.respond_to?(:original_exception)
|
151
|
+
exception.original_exception
|
152
|
+
elsif exception.respond_to?(:continued_exception)
|
153
|
+
exception.continued_exception
|
154
|
+
else
|
155
|
+
exception
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Pulse
|
2
|
+
# Front end to parsing the backtrace for each notice
|
3
|
+
class Backtrace
|
4
|
+
|
5
|
+
# Handles backtrace parsing line by line
|
6
|
+
class Line
|
7
|
+
|
8
|
+
# regexp (optionnally allowing leading X: for windows support)
|
9
|
+
INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
|
10
|
+
|
11
|
+
# The file portion of the line (such as app/models/user.rb)
|
12
|
+
attr_reader :file
|
13
|
+
|
14
|
+
# The line number portion of the line
|
15
|
+
attr_reader :number
|
16
|
+
|
17
|
+
# The method of the line (such as index)
|
18
|
+
attr_reader :method
|
19
|
+
|
20
|
+
# Parses a single line of a given backtrace
|
21
|
+
# @param [String] unparsed_line The raw line from +caller+ or some backtrace
|
22
|
+
# @return [Line] The parsed backtrace line
|
23
|
+
def self.parse(unparsed_line)
|
24
|
+
_, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
|
25
|
+
new(file, number, method)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(file, number, method)
|
29
|
+
self.file = file
|
30
|
+
self.number = number
|
31
|
+
self.method = method
|
32
|
+
end
|
33
|
+
|
34
|
+
# Reconstructs the line in a readable fashion
|
35
|
+
def to_s
|
36
|
+
"#{file}:#{number}:in `#{method}'"
|
37
|
+
end
|
38
|
+
|
39
|
+
def ==(other)
|
40
|
+
to_s == other.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
"<Line:#{to_s}>"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_writer :file, :number, :method
|
50
|
+
end
|
51
|
+
|
52
|
+
# holder for an Array of Backtrace::Line instances
|
53
|
+
attr_reader :lines
|
54
|
+
|
55
|
+
def self.parse(ruby_backtrace, opts = {})
|
56
|
+
ruby_lines = split_multiline_backtrace(ruby_backtrace)
|
57
|
+
|
58
|
+
filters = opts[:filters] || []
|
59
|
+
filtered_lines = ruby_lines.to_a.map do |line|
|
60
|
+
filters.inject(line) do |line, proc|
|
61
|
+
proc.call(line)
|
62
|
+
end
|
63
|
+
end.compact
|
64
|
+
|
65
|
+
lines = filtered_lines.collect do |unparsed_line|
|
66
|
+
Line.parse(unparsed_line)
|
67
|
+
end
|
68
|
+
|
69
|
+
instance = new(lines)
|
70
|
+
end
|
71
|
+
|
72
|
+
def initialize(lines)
|
73
|
+
self.lines = lines
|
74
|
+
end
|
75
|
+
|
76
|
+
def inspect
|
77
|
+
"<Backtrace: " + lines.collect { |line| line.inspect }.join(", ") + ">"
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_s
|
81
|
+
content = []
|
82
|
+
lines.each do |line|
|
83
|
+
content << line
|
84
|
+
end
|
85
|
+
content.join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
def ==(other)
|
89
|
+
if other.respond_to?(:lines)
|
90
|
+
lines == other.lines
|
91
|
+
else
|
92
|
+
false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
attr_writer :lines
|
99
|
+
|
100
|
+
def self.split_multiline_backtrace(backtrace)
|
101
|
+
if backtrace.to_a.size == 1
|
102
|
+
backtrace.to_a.first.split(/\n\s*/)
|
103
|
+
else
|
104
|
+
backtrace
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# Defines deploy:notify_pulse which will send information about the deploy to Pulse.
|
2
|
+
require 'capistrano'
|
3
|
+
|
4
|
+
module Pulse
|
5
|
+
module Capistrano
|
6
|
+
def self.load_into(configuration)
|
7
|
+
configuration.load do
|
8
|
+
after "deploy", "pulse:deploy"
|
9
|
+
after "deploy:migrations", "pulse:deploy"
|
10
|
+
|
11
|
+
namespace :pulse do
|
12
|
+
desc <<-DESC
|
13
|
+
Notify Pulse of the deployment by running the notification on the REMOTE machine.
|
14
|
+
- Run remotely so we use remote API keys, environment, etc.
|
15
|
+
DESC
|
16
|
+
task :deploy, :except => { :no_release => true } do
|
17
|
+
rails_env = fetch(:rails_env, "production")
|
18
|
+
pulse_env = fetch(:pulse_env, fetch(:rails_env, "production"))
|
19
|
+
local_user = ENV['USER'] || ENV['USERNAME']
|
20
|
+
executable = RUBY_PLATFORM.downcase.include?('mswin') ? fetch(:rake, 'rake.bat') : fetch(:rake, 'rake')
|
21
|
+
directory = configuration.current_release
|
22
|
+
notify_command = "cd #{directory}; #{executable} RAILS_ENV=#{rails_env} pulse:deploy TO=#{pulse_env} REVISION=#{current_revision} REPO=#{repository} USER=#{local_user}"
|
23
|
+
notify_command << " DRY_RUN=true" if dry_run
|
24
|
+
notify_command << " API_KEY=#{ENV['API_KEY']}" if ENV['API_KEY']
|
25
|
+
logger.info "Notifying Pulse of Deploy (#{notify_command})"
|
26
|
+
if configuration.dry_run
|
27
|
+
logger.info "DRY RUN: Notification not actually run."
|
28
|
+
else
|
29
|
+
result = ""
|
30
|
+
run(notify_command, :once => true) { |ch, stream, data| result << data }
|
31
|
+
# TODO: Check if SSL is active on account via result content.
|
32
|
+
end
|
33
|
+
logger.info "Pulse Notification Complete."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
if Capistrano::Configuration.instance
|
42
|
+
Pulse::Capistrano.load_into(Capistrano::Configuration.instance)
|
43
|
+
end
|
@@ -0,0 +1,305 @@
|
|
1
|
+
module Pulse
|
2
|
+
# Used to set up and modify settings for the notifier.
|
3
|
+
class Configuration
|
4
|
+
|
5
|
+
OPTIONS = [:api_key, :js_api_key, :backtrace_filters, :development_environments,
|
6
|
+
:development_lookup, :environment_name, :host,
|
7
|
+
:http_open_timeout, :http_read_timeout, :ignore, :ignore_by_filters,
|
8
|
+
:ignore_user_agent, :notifier_name, :notifier_url, :notifier_version,
|
9
|
+
:params_filters, :project_root, :port, :protocol, :proxy_host,
|
10
|
+
:proxy_pass, :proxy_port, :proxy_user, :secure, :use_system_ssl_cert_chain,
|
11
|
+
:framework, :user_information, :rescue_rake_exceptions].freeze
|
12
|
+
|
13
|
+
# The API key for your project, found on the project edit form.
|
14
|
+
attr_accessor :api_key
|
15
|
+
|
16
|
+
# If you're using the Javascript notifier and would want to separate
|
17
|
+
# Javascript notifications into another Pulse project, specify
|
18
|
+
# its APi key here.
|
19
|
+
# Defaults to #api_key (of the base project)
|
20
|
+
attr_writer :js_api_key
|
21
|
+
|
22
|
+
# The host to connect to (defaults to errors.projectlocker.com).
|
23
|
+
attr_accessor :host
|
24
|
+
|
25
|
+
# The port on which your Pulse server runs (defaults to 443 for secure
|
26
|
+
# connections, 80 for insecure connections).
|
27
|
+
attr_accessor :port
|
28
|
+
|
29
|
+
# +true+ for https connections, +false+ for http connections.
|
30
|
+
attr_accessor :secure
|
31
|
+
|
32
|
+
# +true+ to use whatever CAs OpenSSL has installed on your system. +false+ to use the ca-bundle.crt file included in Pulse itself (reccomended and default)
|
33
|
+
attr_accessor :use_system_ssl_cert_chain
|
34
|
+
|
35
|
+
# The HTTP open timeout in seconds (defaults to 2).
|
36
|
+
attr_accessor :http_open_timeout
|
37
|
+
|
38
|
+
# The HTTP read timeout in seconds (defaults to 5).
|
39
|
+
attr_accessor :http_read_timeout
|
40
|
+
|
41
|
+
# The hostname of your proxy server (if using a proxy)
|
42
|
+
attr_accessor :proxy_host
|
43
|
+
|
44
|
+
# The port of your proxy server (if using a proxy)
|
45
|
+
attr_accessor :proxy_port
|
46
|
+
|
47
|
+
# The username to use when logging into your proxy server (if using a proxy)
|
48
|
+
attr_accessor :proxy_user
|
49
|
+
|
50
|
+
# The password to use when logging into your proxy server (if using a proxy)
|
51
|
+
attr_accessor :proxy_pass
|
52
|
+
|
53
|
+
# A list of parameters that should be filtered out of what is sent to Pulse.
|
54
|
+
# By default, all "password" attributes will have their contents replaced.
|
55
|
+
attr_reader :params_filters
|
56
|
+
|
57
|
+
# A list of filters for cleaning and pruning the backtrace. See #filter_backtrace.
|
58
|
+
attr_reader :backtrace_filters
|
59
|
+
|
60
|
+
# A list of filters for ignoring exceptions. See #ignore_by_filter.
|
61
|
+
attr_reader :ignore_by_filters
|
62
|
+
|
63
|
+
# A list of exception classes to ignore. The array can be appended to.
|
64
|
+
attr_reader :ignore
|
65
|
+
|
66
|
+
# A list of user agents that are being ignored. The array can be appended to.
|
67
|
+
attr_reader :ignore_user_agent
|
68
|
+
|
69
|
+
# A list of environments in which notifications should not be sent.
|
70
|
+
attr_accessor :development_environments
|
71
|
+
|
72
|
+
# +true+ if you want to check for production errors matching development errors, +false+ otherwise.
|
73
|
+
attr_accessor :development_lookup
|
74
|
+
|
75
|
+
# The name of the environment the application is running in
|
76
|
+
attr_accessor :environment_name
|
77
|
+
|
78
|
+
# The path to the project in which the error occurred, such as the Rails.root
|
79
|
+
attr_accessor :project_root
|
80
|
+
|
81
|
+
# The name of the notifier library being used to send notifications (such as "Pulse Notifier")
|
82
|
+
attr_accessor :notifier_name
|
83
|
+
|
84
|
+
# The version of the notifier library being used to send notifications (such as "1.0.2")
|
85
|
+
attr_accessor :notifier_version
|
86
|
+
|
87
|
+
# The url of the notifier library being used to send notifications
|
88
|
+
attr_accessor :notifier_url
|
89
|
+
|
90
|
+
# The logger used by Pulse
|
91
|
+
attr_accessor :logger
|
92
|
+
|
93
|
+
# The text that the placeholder is replaced with. {{error_id}} is the actual error number.
|
94
|
+
attr_accessor :user_information
|
95
|
+
|
96
|
+
# The framework Pulse is configured to use
|
97
|
+
attr_accessor :framework
|
98
|
+
|
99
|
+
# Should Pulse catch exceptions from Rake tasks?
|
100
|
+
# (boolean or nil; set to nil to catch exceptions when rake isn't running from a terminal; default is nil)
|
101
|
+
attr_accessor :rescue_rake_exceptions
|
102
|
+
|
103
|
+
|
104
|
+
DEFAULT_PARAMS_FILTERS = %w(password password_confirmation).freeze
|
105
|
+
|
106
|
+
DEFAULT_BACKTRACE_FILTERS = [
|
107
|
+
lambda { |line|
|
108
|
+
if defined?(Pulse.configuration.project_root) && Pulse.configuration.project_root.to_s != ''
|
109
|
+
line.sub(/#{Pulse.configuration.project_root}/, "[PROJECT_ROOT]")
|
110
|
+
else
|
111
|
+
line
|
112
|
+
end
|
113
|
+
},
|
114
|
+
lambda { |line| line.gsub(/^\.\//, "") },
|
115
|
+
lambda { |line|
|
116
|
+
if defined?(Gem)
|
117
|
+
Gem.path.inject(line) do |line, path|
|
118
|
+
line.gsub(/#{path}/, "[GEM_ROOT]")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
},
|
122
|
+
lambda { |line| line if line !~ %r{lib/pulse} }
|
123
|
+
].freeze
|
124
|
+
|
125
|
+
IGNORE_DEFAULT = ['ActiveRecord::RecordNotFound',
|
126
|
+
'ActionController::RoutingError',
|
127
|
+
'ActionController::InvalidAuthenticityToken',
|
128
|
+
'CGI::Session::CookieStore::TamperedWithCookie',
|
129
|
+
'ActionController::UnknownAction',
|
130
|
+
'AbstractController::ActionNotFound',
|
131
|
+
'Mongoid::Errors::DocumentNotFound']
|
132
|
+
|
133
|
+
alias_method :secure?, :secure
|
134
|
+
alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
|
135
|
+
|
136
|
+
def initialize
|
137
|
+
@secure = false
|
138
|
+
@use_system_ssl_cert_chain= false
|
139
|
+
@host = 'errors.projectlocker.com'
|
140
|
+
@http_open_timeout = 2
|
141
|
+
@http_read_timeout = 5
|
142
|
+
@params_filters = DEFAULT_PARAMS_FILTERS.dup
|
143
|
+
@backtrace_filters = DEFAULT_BACKTRACE_FILTERS.dup
|
144
|
+
@ignore_by_filters = []
|
145
|
+
@ignore = IGNORE_DEFAULT.dup
|
146
|
+
@ignore_user_agent = []
|
147
|
+
@development_environments = %w(development test cucumber)
|
148
|
+
@development_lookup = true
|
149
|
+
@notifier_name = 'Pulse Notifier'
|
150
|
+
@notifier_version = VERSION
|
151
|
+
@notifier_url = 'http://www.projectlocker.com'
|
152
|
+
@framework = 'Standalone'
|
153
|
+
@user_information = 'Pulse Error {{error_id}}'
|
154
|
+
@rescue_rake_exceptions = nil
|
155
|
+
end
|
156
|
+
|
157
|
+
# Takes a block and adds it to the list of backtrace filters. When the filters
|
158
|
+
# run, the block will be handed each line of the backtrace and can modify
|
159
|
+
# it as necessary.
|
160
|
+
#
|
161
|
+
# @example
|
162
|
+
# config.filter_bracktrace do |line|
|
163
|
+
# line.gsub(/^#{Rails.root}/, "[Rails.root]")
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# @param [Proc] block The new backtrace filter.
|
167
|
+
# @yieldparam [String] line A line in the backtrace.
|
168
|
+
def filter_backtrace(&block)
|
169
|
+
self.backtrace_filters << block
|
170
|
+
end
|
171
|
+
|
172
|
+
# Takes a block and adds it to the list of ignore filters.
|
173
|
+
# When the filters run, the block will be handed the exception.
|
174
|
+
# @example
|
175
|
+
# config.ignore_by_filter do |exception_data|
|
176
|
+
# true if exception_data[:error_class] == "RuntimeError"
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# @param [Proc] block The new ignore filter
|
180
|
+
# @yieldparam [Hash] data The exception data given to +Pulse.notify+
|
181
|
+
# @yieldreturn [Boolean] If the block returns true the exception will be ignored, otherwise it will be processed by Pulse.
|
182
|
+
def ignore_by_filter(&block)
|
183
|
+
self.ignore_by_filters << block
|
184
|
+
end
|
185
|
+
|
186
|
+
# Overrides the list of default ignored errors.
|
187
|
+
#
|
188
|
+
# @param [Array<Exception>] names A list of exceptions to ignore.
|
189
|
+
def ignore_only=(names)
|
190
|
+
@ignore = [names].flatten
|
191
|
+
end
|
192
|
+
|
193
|
+
# Overrides the list of default ignored user agents
|
194
|
+
#
|
195
|
+
# @param [Array<String>] A list of user agents to ignore
|
196
|
+
def ignore_user_agent_only=(names)
|
197
|
+
@ignore_user_agent = [names].flatten
|
198
|
+
end
|
199
|
+
|
200
|
+
# Allows config options to be read like a hash
|
201
|
+
#
|
202
|
+
# @param [Symbol] option Key for a given attribute
|
203
|
+
def [](option)
|
204
|
+
send(option)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Returns a hash of all configurable options
|
208
|
+
def to_hash
|
209
|
+
OPTIONS.inject({}) do |hash, option|
|
210
|
+
hash[option.to_sym] = self.send(option)
|
211
|
+
hash
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Returns a hash of all configurable options merged with +hash+
|
216
|
+
#
|
217
|
+
# @param [Hash] hash A set of configuration options that will take precedence over the defaults
|
218
|
+
def merge(hash)
|
219
|
+
to_hash.merge(hash)
|
220
|
+
end
|
221
|
+
|
222
|
+
# Determines if the notifier will send notices.
|
223
|
+
# @return [Boolean] Returns +false+ if in a development environment, +true+ otherwise.
|
224
|
+
def public?
|
225
|
+
!development_environments.include?(environment_name)
|
226
|
+
end
|
227
|
+
|
228
|
+
def port
|
229
|
+
@port || default_port
|
230
|
+
end
|
231
|
+
|
232
|
+
# Determines whether protocol should be "http" or "https".
|
233
|
+
# @return [String] Returns +"http"+ if you've set secure to +false+ in
|
234
|
+
# configuration, and +"https"+ otherwise.
|
235
|
+
def protocol
|
236
|
+
if secure?
|
237
|
+
'https'
|
238
|
+
else
|
239
|
+
'http'
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Should Pulse send notifications asynchronously
|
244
|
+
# (boolean, nil or callable; default is nil).
|
245
|
+
# Can be used as callable-setter when block provided.
|
246
|
+
def async(&block)
|
247
|
+
if block_given?
|
248
|
+
@async = block
|
249
|
+
end
|
250
|
+
@async
|
251
|
+
end
|
252
|
+
alias_method :async?, :async
|
253
|
+
|
254
|
+
def async=(use_default_or_this)
|
255
|
+
@async = use_default_or_this == true ?
|
256
|
+
default_async_processor :
|
257
|
+
use_default_or_this
|
258
|
+
end
|
259
|
+
|
260
|
+
def js_api_key
|
261
|
+
@js_api_key || self.api_key
|
262
|
+
end
|
263
|
+
|
264
|
+
def js_notifier=(*args)
|
265
|
+
warn '[PULSE] config.js_notifier has been deprecated and has no effect. You should use <%= pulse_javascript_notifier %> directly at the top of your layouts. Be sure to place it before all other javascript.'
|
266
|
+
end
|
267
|
+
|
268
|
+
def environment_filters
|
269
|
+
warn 'config.environment_filters has been deprecated and has no effect.'
|
270
|
+
[]
|
271
|
+
end
|
272
|
+
|
273
|
+
def ca_bundle_path
|
274
|
+
if use_system_ssl_cert_chain? && File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
|
275
|
+
OpenSSL::X509::DEFAULT_CERT_FILE
|
276
|
+
else
|
277
|
+
local_cert_path # ca-bundle.crt built from source, see resources/README.md
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def local_cert_path
|
282
|
+
File.expand_path(File.join("..", "..", "..", "resources", "ca-bundle.crt"), __FILE__)
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
# Determines what port should we use for sending notices.
|
287
|
+
# @return [Fixnum] Returns 443 if you've set secure to true in your
|
288
|
+
# configuration, and 80 otherwise.
|
289
|
+
def default_port
|
290
|
+
if secure?
|
291
|
+
443
|
292
|
+
else
|
293
|
+
80
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
# Async notice delivery defaults to girl friday
|
298
|
+
def default_async_processor
|
299
|
+
queue = GirlFriday::WorkQueue.new(nil, :size => 3) do |notice|
|
300
|
+
Pulse.sender.send_to_pulse(notice)
|
301
|
+
end
|
302
|
+
lambda {|notice| queue << notice}
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|