projectlocker_pulse 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data/CHANGELOG +26 -0
  2. data/Gemfile +3 -0
  3. data/Guardfile +6 -0
  4. data/INSTALL +20 -0
  5. data/MIT-LICENSE +23 -0
  6. data/README.md +439 -0
  7. data/README_FOR_HEROKU_ADDON.md +89 -0
  8. data/Rakefile +223 -0
  9. data/SUPPORTED_RAILS_VERSIONS +38 -0
  10. data/TESTING.md +41 -0
  11. data/features/metal.feature +18 -0
  12. data/features/rack.feature +60 -0
  13. data/features/rails.feature +272 -0
  14. data/features/rails_with_js_notifier.feature +97 -0
  15. data/features/rake.feature +27 -0
  16. data/features/sinatra.feature +29 -0
  17. data/features/step_definitions/file_steps.rb +10 -0
  18. data/features/step_definitions/metal_steps.rb +23 -0
  19. data/features/step_definitions/rack_steps.rb +23 -0
  20. data/features/step_definitions/rails_application_steps.rb +478 -0
  21. data/features/step_definitions/rake_steps.rb +17 -0
  22. data/features/support/env.rb +18 -0
  23. data/features/support/matchers.rb +35 -0
  24. data/features/support/projectlocker_pulse_shim.rb.template +16 -0
  25. data/features/support/rails.rb +201 -0
  26. data/features/support/rake/Rakefile +68 -0
  27. data/features/support/terminal.rb +107 -0
  28. data/features/user_informer.feature +63 -0
  29. data/generators/pulse/lib/insert_commands.rb +34 -0
  30. data/generators/pulse/lib/rake_commands.rb +24 -0
  31. data/generators/pulse/pulse_generator.rb +94 -0
  32. data/generators/pulse/templates/capistrano_hook.rb +6 -0
  33. data/generators/pulse/templates/initializer.rb +6 -0
  34. data/generators/pulse/templates/pulse_tasks.rake +25 -0
  35. data/install.rb +1 -0
  36. data/lib/projectlocker_pulse.rb +159 -0
  37. data/lib/pulse/backtrace.rb +108 -0
  38. data/lib/pulse/capistrano.rb +43 -0
  39. data/lib/pulse/configuration.rb +305 -0
  40. data/lib/pulse/notice.rb +390 -0
  41. data/lib/pulse/rack.rb +54 -0
  42. data/lib/pulse/rails/action_controller_catcher.rb +30 -0
  43. data/lib/pulse/rails/controller_methods.rb +85 -0
  44. data/lib/pulse/rails/error_lookup.rb +33 -0
  45. data/lib/pulse/rails/javascript_notifier.rb +47 -0
  46. data/lib/pulse/rails/middleware/exceptions_catcher.rb +33 -0
  47. data/lib/pulse/rails.rb +40 -0
  48. data/lib/pulse/rails3_tasks.rb +99 -0
  49. data/lib/pulse/railtie.rb +49 -0
  50. data/lib/pulse/rake_handler.rb +65 -0
  51. data/lib/pulse/sender.rb +128 -0
  52. data/lib/pulse/shared_tasks.rb +47 -0
  53. data/lib/pulse/tasks.rb +83 -0
  54. data/lib/pulse/user_informer.rb +27 -0
  55. data/lib/pulse/utils/blank.rb +53 -0
  56. data/lib/pulse/version.rb +3 -0
  57. data/lib/pulse_tasks.rb +64 -0
  58. data/lib/rails/generators/pulse/pulse_generator.rb +100 -0
  59. data/lib/templates/javascript_notifier.erb +15 -0
  60. data/lib/templates/rescue.erb +91 -0
  61. data/pulse.gemspec +39 -0
  62. data/rails/init.rb +1 -0
  63. data/resources/README.md +34 -0
  64. data/resources/ca-bundle.crt +3376 -0
  65. data/script/integration_test.rb +38 -0
  66. data/test/backtrace_test.rb +162 -0
  67. data/test/capistrano_test.rb +34 -0
  68. data/test/catcher_test.rb +333 -0
  69. data/test/configuration_test.rb +236 -0
  70. data/test/helper.rb +263 -0
  71. data/test/javascript_notifier_test.rb +51 -0
  72. data/test/logger_test.rb +79 -0
  73. data/test/notice_test.rb +490 -0
  74. data/test/notifier_test.rb +276 -0
  75. data/test/projectlocker_pulse_tasks_test.rb +170 -0
  76. data/test/pulse.xsd +88 -0
  77. data/test/rack_test.rb +58 -0
  78. data/test/rails_initializer_test.rb +36 -0
  79. data/test/recursion_test.rb +10 -0
  80. data/test/sender_test.rb +288 -0
  81. data/test/user_informer_test.rb +29 -0
  82. metadata +432 -0
@@ -0,0 +1,6 @@
1
+ <% if Rails::VERSION::MAJOR < 3 && Rails::VERSION::MINOR < 2 -%>
2
+ require 'pulse/rails'
3
+ <% end -%>
4
+ Pulse.configure do |config|
5
+ config.api_key = <%= api_key_expression %>
6
+ end
@@ -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