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