coalmine 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ .DS_Store
4
+ .rvmrc
5
+ Gemfile.lock
6
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in coalmine.gemspec
4
+ gemspec
5
+
6
+ group :test, :development do
7
+ # Pretty printed test output
8
+ gem 'turn', :require => false
9
+ gem 'rspec-rails'
10
+ gem 'fakeweb'
11
+ end
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Ruby Connector for Coalmine
2
+
3
+ This connector allows you to easily send errors and log messages to the Coalmine API.
4
+
5
+ ## Setup
6
+
7
+ Rails 3.x:
8
+
9
+ gem "coalmine"
10
+
11
+ ## Configuration
12
+
13
+ In a Rails app create an initializer and configure as such:
14
+
15
+ Coalmine.configure do |config|
16
+ config.signature = "my_secret_signature"
17
+ config.logger = Rails.logger
18
+ end
19
+
20
+ All uncaught exceptions are automatically logged. To manually log an exception to coalmine:
21
+
22
+ begin
23
+ call_dangerous_method
24
+ rescue Exception => e
25
+ notify_coalmine(e)
26
+ end
27
+
28
+ ## Usage
29
+
30
+ To notify Coalmine of a deployment
31
+
32
+ rake coalmine:deployment[your_version,username]
33
+
34
+ # For example
35
+ rake coalmine:deployment[1.0.0,brad]
36
+
37
+ ## Filtering sensitive information
38
+
39
+ Coalmine will automatically string-replace values that you deem to be sensitive and do not want to be sent out.
40
+ Coalmine automatically honors `Rails.application.config.filter_parameters`. If you wish to include additional filter properties, you can via the config:
41
+
42
+ Coalmine.configure do |config|
43
+ config.filters += ["credit-card"]
44
+ end
45
+
46
+ The above would replace all properties named `credit-card` with the value [FILTERED].
47
+
48
+ ## Setting custom variables to included with notifications
49
+
50
+ You can include extra information by defining custom variables. These are automatically appended to the notification sent to Coalmine and appear in the 'Application' tab. Custom variables are added like so:
51
+
52
+ Coalmine.custom_variables[:username] = current_user.username
53
+
54
+ You will most likely initialize all your custom application variables at the beginning of the request. If you are using Rails, it might look something like:
55
+
56
+ class ApplicationController < ApplicationController::Base
57
+ before_filter :set_custom_variables
58
+
59
+ protected
60
+
61
+ def set_custom_variables
62
+ Coalmine.custom_variables[:user_name] = current_user.username
63
+ Coalmine.custom_variables[:something] = "another custom value"
64
+ end
65
+ end
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ Rspec::Core::RakeTask.new
data/coalmine.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "coalmine/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "coalmine"
7
+ s.version = Coalmine::VERSION
8
+ s.authors = ["Brad Seefeld", "Matt Ratzloff"]
9
+ s.email = ["admin@coalmineapp.com"]
10
+ s.homepage = "https://github.com/coalmine/coalmine_ruby"
11
+ s.summary = "Coalmine Connector Ruby implementation"
12
+ s.description = "Send errors to the Coalmine API for logging and analytics."
13
+
14
+ s.rubyforge_project = "coalmine"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "fakeweb"
24
+ s.add_runtime_dependency "jsonbuilder"
25
+ end
@@ -0,0 +1,33 @@
1
+ require "capistrano"
2
+
3
+ module Coalmine
4
+ module Capistrano
5
+ def self.setup(config)
6
+ config.load do
7
+ after "deploy", "coalmine:deploy"
8
+
9
+ namespace :coalmine do
10
+ desc "Notify Coalmine of the deployment"
11
+ task :deploy, :except => { :no_release => true } do
12
+ environment = fetch(:rails_env, :production)
13
+ author = ENV["USER"] || ENV["USERNAME"]
14
+ rake = fetch(:rake, :rake)
15
+ cmd = "cd #{config.current_release} && RAILS_ENV=#{environment} #{rake} coalmine:deployment[#{current_revision},#{author}]"
16
+ logger.info "Notifying Coalmine of Deploy"
17
+ if config.dry_run
18
+ logger.info "Dry Run... Coalmine will not actually be notified"
19
+ else
20
+ run(cmd, :once => true) { |ch, stream, data| result << data }
21
+ end
22
+
23
+ logger.info "Coalmine notification completed."
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ if Capistrano::Configuration.instance
32
+ Coalmine::Capistrano.setup(Capistrano::Configuration.instance)
33
+ end
@@ -0,0 +1,37 @@
1
+ class Coalmine::Configuration
2
+
3
+ attr_accessor :url, :environment, :signature, :logger, :host, :port, :proxy_host,
4
+ :proxy_port, :proxy_user, :proxy_password, :protocol, :secure, :http_open_timeout,
5
+ :http_read_timeout, :project_root, :framework, :filters,
6
+
7
+ # For HTTP basic auth
8
+ :http_user, :http_password,
9
+
10
+ # The application version
11
+ :version,
12
+
13
+ # The environments for which notifications can be posted
14
+ :enabled_environments
15
+
16
+ def initialize
17
+ self.protocol = "https"
18
+ self.host = "coalmineapp.com"
19
+ self.port = 443
20
+ self.secure = true
21
+ self.enabled_environments = ["production", "staging"]
22
+
23
+ self.http_open_timeout = 3
24
+ self.http_read_timeout = 6
25
+ self.logger = Coalmine::Logger.new
26
+ self.filters = []
27
+ end
28
+
29
+ def protocol=(proto)
30
+ proto = "http" unless ["http", "https"].include?(proto)
31
+ @protocol = proto
32
+ end
33
+
34
+ def secure?
35
+ self.secure
36
+ end
37
+ end
@@ -0,0 +1,16 @@
1
+ module Coalmine
2
+ class Logger
3
+
4
+ def error(*msg)
5
+ end
6
+
7
+ def warn(*msg)
8
+ end
9
+
10
+ def info(*msg)
11
+ end
12
+
13
+ def debug(*msg)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,186 @@
1
+ require "jsonbuilder"
2
+ require "socket"
3
+
4
+ ##
5
+ # Contains all data sent to the API. Responsible for serializing data into the
6
+ # correct format.
7
+ #
8
+ # @link http://rack.rubyforge.org/doc/files/SPEC.html
9
+ module Coalmine
10
+ class Notification
11
+
12
+ attr_accessor :stack_trace, :message, :line_number, :url,
13
+ :error_class, :controller, :action, :method, :parameters,
14
+ :ip_address, :user_agent, :cookies, :environment, :server,
15
+ :severity, :hostname, :process_id, :file, :referrer, :thread_id
16
+
17
+ def initialize(args = {})
18
+
19
+ exception = args[:exception]
20
+
21
+ if exception
22
+ self.stack_trace = exception.backtrace * "\n" if exception.backtrace
23
+ self.message = exception.message
24
+ self.error_class = exception.class.name
25
+ self.line_number = extract_line_number(exception.backtrace)
26
+ self.file = extract_file_name(exception.backtrace)
27
+ end
28
+
29
+ if args[:rack_env]
30
+ set_from_rack_env(args[:rack_env])
31
+ end
32
+
33
+ args.keys.each do |key|
34
+ setter = :"#{key}="
35
+ send(setter, args[key]) if respond_to?(setter)
36
+ end
37
+
38
+ self.severity = "ERROR" unless self.severity
39
+ self.hostname = Socket.gethostname
40
+
41
+ begin
42
+ self.process_id = Process::pid
43
+ rescue
44
+ # Ignore
45
+ end
46
+
47
+ begin
48
+ self.thread_id = Thread.current.object_id
49
+ rescue
50
+ # Ignore
51
+ end
52
+ end
53
+
54
+ def post_data
55
+ {:signature => Coalmine.config.signature, :json => to_json}
56
+ end
57
+
58
+ def to_json(options = {})
59
+ ActiveSupport::JSON.encode(serialize(options))
60
+ end
61
+
62
+ def serialize(options)
63
+ config = Coalmine.config
64
+ results = {
65
+ :version => config.version,
66
+ :app_environment => config.environment,
67
+ :url => url,
68
+ :file => file,
69
+ :line_number => line_number,
70
+ :message => message,
71
+ :stack_trace => stack_trace,
72
+ :class => error_class,
73
+ :framework => config.framework,
74
+ :parameters => parameters,
75
+ :ip_address => ip_address,
76
+ :user_agent => user_agent,
77
+ :cookies => cookies,
78
+ :method => method,
79
+ :environment => environment,
80
+ :server => server,
81
+ :severity => severity,
82
+ :hostname => hostname,
83
+ :process_id => process_id,
84
+ :thread_id => thread_id.to_s, # Because it is a long
85
+ :referrer => referrer,
86
+ :application => Coalmine.custom_variables
87
+ }
88
+
89
+ Coalmine.filter(results)
90
+ end
91
+
92
+ ##
93
+ # Remote resource path
94
+ #
95
+ # @return [String] The path of the remote resource. Will be appended to the remote base URL.
96
+ def resource_path
97
+ "/notify/"
98
+ end
99
+
100
+ protected
101
+
102
+ def set_from_rack_env(env)
103
+ return unless env
104
+
105
+ self.url = assemble_url(env)
106
+ self.ip_address = env["REMOTE_ADDR"]
107
+ self.user_agent = env["HTTP_USER_AGENT"]
108
+ self.method = env["REQUEST_METHOD"]
109
+ self.cookies = env["HTTP_COOKIE"]
110
+ self.parameters = env["QUERY_STRING"]
111
+
112
+ begin
113
+ require "rack/request"
114
+ request = ::Rack::Request.new(env) # Always returns the same request object
115
+ self.referrer = request.referrer if request.respond_to? :referrer
116
+ rescue
117
+ # Ignore
118
+ end
119
+
120
+ environment_keys = ["GATEWAY_INTERFACE", "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR",
121
+ "REMOTE_HOST", "REQUEST_METHOD", "REQUEST_URI", "SCRIPT_NAME", "SERVER_NAME",
122
+ "SERVER_PORT", "SERVER_PROTOCOL", "SERVER_SOFTWARE", "HTTP_HOST", "HTTP_CONNECTION",
123
+ "HTTP_USER_AGENT", "HTTP_ACCEPT", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE",
124
+ "HTTP_ACCEPT_CHARSET", "HTTP_COOKIE"]
125
+ self.environment = {}
126
+ environment_keys.each do |key|
127
+ self.environment[key] = env[key].to_s
128
+ end
129
+
130
+ server_keys = ["HTTP_CACHE_CONTROL", "rack.version", "rack.multithread", "rack.multiprocess",
131
+ "rack.run_once", "rack.url_scheme", "HTTP_VERSION", "REQUEST_PATH", "action_dispatch.secret_token",
132
+ "action_dispatch.show_exceptions", "action_dispatch.remote_ip", "rack.session",
133
+ "rack.session_options", "rack.request.cookie_hash", "action_dispatch.request.unsigned_session_cookie",
134
+ "action_dispatch.request.path_parameters"]
135
+ self.server = {}
136
+ server_keys.each do |key|
137
+ self.server[key] = env[key].to_s
138
+ end
139
+ end
140
+
141
+ def assemble_url(env)
142
+ protocol = env["rack.url_scheme"]
143
+ protocol ||= "http"
144
+ if env["HTTP_HOST"]
145
+ host = env["HTTP_HOST"]
146
+ else
147
+ host = env["SERVER_NAME"]
148
+ unless [80, 443].include?(env["SERVER_PORT"])
149
+ host << ":#{env["SERVER_PORT"]}"
150
+ end
151
+ end
152
+ path = env["SCRIPT_NAME"] + env["PATH_INFO"]
153
+ "#{protocol}://#{host}#{path}"
154
+ end
155
+
156
+ ##
157
+ # Extract the file name from a backtrace. Format for the first line is something like:
158
+ # "/Users/user/workspace/coalmine_ruby_test/app/controllers/index_controller.rb:4:in `index'"
159
+ #
160
+ # @param <String> backtrace The exceptions backtrace
161
+ # @return <String|Nil> The file name that generated the exception. This is based from the project root.
162
+ def extract_file_name(backtrace)
163
+ return unless backtrace
164
+ backtrace = backtrace.to_a
165
+ return unless backtrace.length >= 1
166
+
167
+ m = backtrace.first.match(/^(.+?):/)
168
+ return unless m and m.length > 1
169
+ m[1].gsub(Dir.getwd, "")
170
+ end
171
+
172
+ ##
173
+ # Extract the line number which generated the exception.
174
+ #
175
+ # @param <String> backtrace Backtrace of the exception
176
+ # @return <Integer|Nil> The line number of the error. Nil if cannot be found.
177
+ def extract_line_number(backtrace)
178
+ return unless backtrace
179
+ backtrace = backtrace.to_a
180
+ return unless backtrace.length > 1
181
+ m = backtrace.first.match(/^.+:(\d+):/)
182
+ return unless m and m.length > 1
183
+ m[1]
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,22 @@
1
+ module Coalmine
2
+ class Rack
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ begin
9
+ response = @app.call(env)
10
+ rescue Exception => e
11
+ Coalmine.notify(e, :rack_env => env)
12
+ raise
13
+ end
14
+
15
+ if env["rack.exception"]
16
+ Coalmine.notify(env["rack.exception"], :rack_env => env)
17
+ end
18
+
19
+ response
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,15 @@
1
+ module Coalmine
2
+ module Rails
3
+ module ActionController
4
+
5
+ ##
6
+ # Setup some rails magic so that when an exception is raised, it is
7
+ # first sent to our handler.
8
+ def self.included(base)
9
+ base.send(:before_filter, :clear_coalmine_custom_variables)
10
+ base.send(:alias_method, :rescue_action_in_public_without_coalmine, :rescue_action_in_public)
11
+ base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_coalmine)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,57 @@
1
+ module Coalmine
2
+ module Rails
3
+ module ControllerMethods
4
+
5
+ protected
6
+
7
+ ##
8
+ # Convenience method for other developers to send information to coalmine.
9
+ #
10
+ # @param [Hash|Exception] The exception or hash to send to Coalmine API.
11
+ def notify_coalmine(exception_or_hash)
12
+ return unless coalmine_notification_allowed?
13
+ Coalmine.notify(exception_or_hash, coalmine_request_data)
14
+ end
15
+
16
+ def clear_coalmine_custom_variables
17
+ Coalmine.custom_variables.clear
18
+ end
19
+
20
+ private
21
+
22
+ ##
23
+ # Automatically called by the rails stack when an uncaught exception
24
+ # is encountered.
25
+ #
26
+ # @param [Exception] The uncaught exception
27
+ def rescue_action_in_public_with_coalmine(exception)
28
+ Coalmine.logger.debug "Coalmine is handling uncaught exception: #{exception.message}"
29
+ notify_coalmine(exception)
30
+ rescue_action_in_public_without_coalmine(exception)
31
+ end
32
+
33
+ ##
34
+ # Gather environment information about the current request.
35
+ #
36
+ # @return [Hash] Information about the current request.
37
+ def coalmine_request_data
38
+ {
39
+ :controller => params[:controller],
40
+ :action => params[:action],
41
+ :url => request.url,
42
+ :method => request.method,
43
+ :parameters => params,
44
+ :ip_address => request.remote_ip
45
+ }
46
+ end
47
+
48
+ ##
49
+ # Determine if the we can notify coalmine.
50
+ #
51
+ # @return <Boolean> True if we can send notifications in the current env.
52
+ def coalmine_notification_allowed?
53
+ Coalmine.config.enabled_environments.include?(::Rails.env)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,13 @@
1
+ namespace :coalmine do
2
+ desc "Notify Coalmine of a recent deployment"
3
+ task :deployment, [:version, :author] => [:environment] do |t, args|
4
+ version = Coalmine::VersionNotification.new
5
+ version.version = args[:version]
6
+ version.author = args[:author]
7
+ if Coalmine::Sender.send(version)
8
+ puts "Successfully notified Coalmine of version #{args[:version]}"
9
+ else
10
+ puts "There was an error while notifying Coalmine. Please check the logs for details."
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,26 @@
1
+ require "coalmine"
2
+ require "coalmine/rails/action_controller"
3
+
4
+ ##
5
+ # Enhance rails 2 apps with coalmine error catching. This injects some methods into
6
+ # ActionController::Base which catches exceptions.
7
+ module Coalmine
8
+ module Rails
9
+ def self.init
10
+
11
+ if defined? ::ActionController::Base
12
+ Rails.logger.debug "Enhancing ActionController::Base with Coalmine goodies."
13
+ ::ActionController::Base.send(:include, Coalmine::Rails::ActionController)
14
+ end
15
+
16
+ # Add to configuration
17
+ Coalmine.configure do |config|
18
+ config.logger = Rails.logger
19
+ config.environment = Rails.env
20
+ config.project_root = Rails.root
21
+ config.framework = "Rails: #{::Rails::VERSION::STRING}" if defined? ::RAILS::VERSION
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,31 @@
1
+ require "coalmine"
2
+ require "rails"
3
+
4
+ module Coalmine
5
+ class Railtie < Rails::Railtie
6
+
7
+ initializer "coalmine.use_rack_middleware" do |app|
8
+ app.config.middleware.use "Coalmine::Rack"
9
+ end
10
+
11
+ rake_tasks do
12
+ load "coalmine/rails/tasks.rb"
13
+ end
14
+
15
+ config.after_initialize do
16
+ # Add to configuration
17
+ Coalmine.configure do |config|
18
+ config.logger = Rails.logger
19
+ config.environment ||= Rails.env
20
+ config.project_root ||= Rails.root
21
+ config.framework ||= "Rails: #{::Rails::VERSION::STRING}" if defined? ::RAILS::VERSION
22
+ config.filters += Rails.application.config.filter_parameters
23
+ end
24
+
25
+ if defined? ::ActionController::Base
26
+ require "coalmine/rails/controller_methods"
27
+ ::ActionController::Base.send(:include, Coalmine::Rails::ControllerMethods)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,78 @@
1
+ require "net/http"
2
+ require "net/https"
3
+ require "cgi"
4
+
5
+ module Coalmine
6
+ class Sender
7
+
8
+ HEADERS = {
9
+ "Content-type" => "application/x-www-form-urlencoded",
10
+ "Accept" => "text/json, application/json"
11
+ }
12
+
13
+ HTTP_ERRORS = [Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
14
+ Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError,
15
+ Errno::ECONNREFUSED, Errno::ETIMEDOUT].freeze
16
+
17
+ def self.send(notification)
18
+ unless config.enabled_environments.include?(config.environment)
19
+ Coalmine.logger.debug "Attempted to send a notification to Coalmine, " +
20
+ "but notifications are not enabled for #{config.environment}. To " +
21
+ "send requests for this environment, add it to config.enabled_environments."
22
+ return
23
+ end
24
+
25
+ proxy = Net::HTTP::Proxy(config.proxy_host, config.proxy_port, config.proxy_user, config.proxy_password)
26
+ url = self.url(notification)
27
+ http = proxy.new(url.host, url.port)
28
+
29
+ http.read_timeout = config.http_read_timeout
30
+ http.open_timeout = config.http_open_timeout
31
+
32
+ if config.secure?
33
+ http.use_ssl = true
34
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
35
+ http.ca_file = OpenSSL::X509::DEFAULT_CERT_FILE if File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
36
+ else
37
+ http.use_ssl = false
38
+ end
39
+
40
+ response = nil
41
+ begin
42
+ req = Net::HTTP::Post.new(url.path)
43
+ req.set_form_data(notification.post_data)
44
+ HEADERS.each_pair do |key, value|
45
+ req[key] = value
46
+ end
47
+
48
+ if config.http_user && config.http_password
49
+ req.basic_auth config.http_user, config.http_password
50
+ end
51
+
52
+ response = http.request(req)
53
+
54
+ unless response.code == "200"
55
+ Coalmine.logger.error "Unable to notify Coalmine (HTTP #{response.code})"
56
+ Coalmine.logger.error "Coalmine response: #{response.body}" if response.body
57
+ return false
58
+ end
59
+ rescue *HTTP_ERRORS => e
60
+ Coalmine.logger.error "Timeout while attempting to notify Coalmine at #{url}"
61
+ return false
62
+ end
63
+
64
+ Coalmine.logger.debug "Sent notification to Coalmine at #{url}"
65
+ true
66
+ end
67
+
68
+ protected
69
+
70
+ def self.url(data)
71
+ URI.parse("#{config.protocol}://#{config.host}:#{config.port}#{data.resource_path}")
72
+ end
73
+
74
+ def self.config
75
+ Coalmine.config
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,3 @@
1
+ module Coalmine
2
+ VERSION = "0.5.0"
3
+ end
@@ -0,0 +1,19 @@
1
+ module Coalmine
2
+ class VersionNotification
3
+
4
+ attr_accessor :environment, :version, :author
5
+
6
+ def initialize
7
+ self.environment = Coalmine.config.environment
8
+ end
9
+
10
+ def resource_path
11
+ "/versions/"
12
+ end
13
+
14
+ def post_data
15
+ {:signature => Coalmine.config.signature, :environment => self.environment,
16
+ :version => self.version, :author => self.author}
17
+ end
18
+ end
19
+ end
data/lib/coalmine.rb ADDED
@@ -0,0 +1,101 @@
1
+ require "coalmine/configuration"
2
+ require "coalmine/sender"
3
+ require "coalmine/notification"
4
+ require "coalmine/rack"
5
+ require "coalmine/logger"
6
+ require "coalmine/version_notification"
7
+ require "coalmine/railtie" if defined? Rails
8
+
9
+ module Coalmine
10
+
11
+ ##
12
+ # Configure the coalmine client.
13
+ #
14
+ # @example
15
+ # Coalmine.configure do |config|
16
+ # config.signature = "abc"
17
+ # config.logger = Rails.logger
18
+ # end
19
+ def self.configure
20
+ yield(config)
21
+ end
22
+
23
+ ##
24
+ # Fetch the config.
25
+ #
26
+ # @return [Coalmine::Configuration] The configuration
27
+ def self.config
28
+ @configuration ||= Configuration.new
29
+ @configuration
30
+ end
31
+
32
+ def self.logger
33
+ config.logger
34
+ end
35
+
36
+ ##
37
+ # Send an exception manually to the API. This method packages up the exception and
38
+ # then sends it.
39
+ #
40
+ # @param [Exception] exception The exception to log
41
+ # @return [Boolean] True if notification is sent OK
42
+ def self.notify(exception, additional_data = {})
43
+
44
+ # We also log the exception locally.
45
+ logger.error exception
46
+
47
+ # Be paranoid about causing exceptions.
48
+ begin
49
+ notification = build_from_exception(exception, additional_data)
50
+
51
+ # Send the exception to the remote.
52
+ return send(notification)
53
+ rescue Exception => e
54
+ logger.error e
55
+ return false
56
+ end
57
+ end
58
+
59
+ def self.err(message)
60
+ notify(nil, :message => message, :severity => "ERROR")
61
+ end
62
+
63
+ def self.error(message)
64
+ err(message)
65
+ end
66
+
67
+ def self.warn(message)
68
+ notify(nil, :message => message, :severity => "WARN")
69
+ end
70
+
71
+ def self.info(message)
72
+ notify(nil, :message => message, :severity => "INFO")
73
+ end
74
+
75
+ def self.debug(message)
76
+ notify(nil, :message => message, :severity => "DEBUG")
77
+ end
78
+
79
+ def self.filter(hash)
80
+ @filter ||= ActionDispatch::Http::ParameterFilter.new(config.filters)
81
+ @filter.filter(hash)
82
+ end
83
+
84
+ ##
85
+ # Variables that may be set by the application.
86
+ def self.custom_variables
87
+ @custom_variables ||= {}
88
+ end
89
+
90
+ protected
91
+
92
+ def self.build_from_exception(exception, additional_data = {})
93
+ additional_data ||= {}
94
+ additional_data[:exception] = exception
95
+ Notification.new(additional_data)
96
+ end
97
+
98
+ def self.send(data)
99
+ Coalmine::Sender.send(data)
100
+ end
101
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "coalmine/rails"
2
+ Coalmine::Rails.init
@@ -0,0 +1,28 @@
1
+ require "spec_helper"
2
+
3
+ describe Coalmine::Configuration do
4
+
5
+ it "defaults to http protocol" do
6
+ Coalmine::Configuration.new.protocol.should == "http"
7
+ end
8
+
9
+ it "falls back to http when an unsupported protocol is given" do
10
+ config = Coalmine::Configuration.new
11
+ config.protocol = "something"
12
+ config.protocol.should == "http"
13
+ end
14
+
15
+ it "accepts https as the protocol" do
16
+ config = Coalmine::Configuration.new
17
+ config.protocol = "https"
18
+ config.protocol.should == "https"
19
+ end
20
+
21
+ it "sets appropriate timeout defaults" do
22
+ config = Coalmine::Configuration.new
23
+ config.http_read_timeout.should_not be_nil
24
+ config.http_read_timeout.should > 0
25
+ config.http_open_timeout.should_not be_nil
26
+ config.http_open_timeout.should > 0
27
+ end
28
+ end
@@ -0,0 +1,115 @@
1
+ require "spec_helper"
2
+
3
+ describe Coalmine::Notification do
4
+
5
+ it "grabs info from the exception" do
6
+ exception = Exception.new("a test exception")
7
+ notification = Coalmine::Notification.new(:exception => exception)
8
+ notification.message.should == exception.message
9
+ notification.stack_trace.should == exception.backtrace
10
+ notification.error_class.should == exception.class.name
11
+ end
12
+
13
+ it "serializes to json" do
14
+ exception = Exception.new("just a test")
15
+ notification = Coalmine::Notification.new(:exception => exception, :url => "http://www.test.com", :user_agent => %Q[something "with" quote's])
16
+ json = ActiveSupport::JSON.decode(notification.to_json)
17
+
18
+ required_fields = ["signature", "version", "app_environment", "url"]
19
+ required_fields.each do |field|
20
+ json.keys.include?(field)
21
+ end
22
+ end
23
+
24
+ it "sets the file name" do
25
+ begin
26
+ raise Exception.new("test")
27
+ rescue Exception => e
28
+ notification = Coalmine::Notification.new(:exception => e)
29
+ notification.file.should_not be_nil
30
+ end
31
+ end
32
+
33
+ it "sets the line number" do
34
+ begin
35
+ raise Exception.new("test")
36
+ rescue Exception => e
37
+ notification = Coalmine::Notification.new(:exception => e)
38
+ notification.line_number.should_not be_nil
39
+ end
40
+ end
41
+
42
+ it "populates from a rack env" do
43
+ url = "http://localhost:3000/"
44
+ ip = "123.3.32.234"
45
+ method = "GET"
46
+ params = "key=value&some_other_key=*another(val)"
47
+ user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_1) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.220 Safari/535.1"
48
+ cookies = "_coalmine_session=BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJTZiZTE3NWZiNDMzNWE4NTZiNGUyOTllODkzYzQwMDk5BjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMUJaaE1pWWdnWTkwalg3ZmZTWVZKbE5GU3d4UzhJcEtJWTF2cGxmanViWEk9BjsARg%3D%3D--7ef48133c020462203c902444fe11fa8241f9c02"
49
+
50
+ rack_env = {"GATEWAY_INTERFACE" => "CGI/1.1", "PATH_INFO" => "/", "QUERY_STRING" => params,
51
+ "REMOTE_ADDR" => ip, "REMOTE_HOST" => "localhost", "REQUEST_METHOD" => method,
52
+ "REQUEST_URI" => url, "SCRIPT_NAME"=>"", "SERVER_NAME"=>"localhost", "SERVER_PORT"=>"3000",
53
+ "SERVER_PROTOCOL"=>"HTTP/1.1", "SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.2/2011-07-09)",
54
+ "HTTP_HOST"=>"localhost:3000", "HTTP_CONNECTION"=>"keep-alive",
55
+ "HTTP_USER_AGENT"=> user_agent, "HTTP_ACCEPT"=>"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
56
+ "HTTP_ACCEPT_ENCODING"=>"gzip,deflate,sdch", "HTTP_ACCEPT_LANGUAGE"=>"en-US,en;q=0.8",
57
+ "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.3",
58
+ "HTTP_COOKIE"=> cookies, "rack.version"=>[1, 1]}
59
+
60
+ notification = Coalmine::Notification.new(:rack_env => rack_env)
61
+ notification.url.should == url
62
+ notification.ip_address.should == ip
63
+ notification.method.should == method
64
+ notification.user_agent.should == user_agent
65
+ notification.cookies.should == cookies
66
+ notification.parameters.should == params
67
+ notification.environment.length.should >= 20
68
+ end
69
+
70
+ it "defaults to error severity" do
71
+ notification = Coalmine::Notification.new
72
+ notification.severity.should == "ERROR"
73
+ end
74
+
75
+ it "allows severity to be set explicitly" do
76
+ severity = "INFO"
77
+ notification = Coalmine::Notification.new(:severity => severity)
78
+ notification.severity.should == severity
79
+ end
80
+
81
+ it "sets the hostname" do
82
+ notification = Coalmine::Notification.new
83
+ notification.hostname.should_not be_nil
84
+ end
85
+
86
+ it "does not allow hostname to be set explicitly" do
87
+ hostname = "not a real hostname"
88
+ notification = Coalmine::Notification.new(:hostname => hostname)
89
+ notification.hostname.should_not == hostname
90
+ end
91
+
92
+ it "sets the process ID" do
93
+ notification = Coalmine::Notification.new
94
+ notification.process_id.should_not be_nil
95
+ end
96
+
97
+ it "does not allow the process ID to be set explicitly" do
98
+ pid = 1
99
+ notification = Coalmine::Notification.new(:process_id => pid)
100
+ notification.process_id.should_not == pid
101
+ end
102
+
103
+ it "sets the thread ID" do
104
+ notification = Coalmine::Notification.new
105
+ notification.thread_id.should_not be_nil
106
+ notification.thread_id.should > 0
107
+ end
108
+
109
+ it "does not allow the thread ID to be set explicitly" do
110
+ thread_id = 1
111
+ notification = Coalmine::Notification.new(:thread_id => thread_id)
112
+ notification.thread_id.should_not == thread_id
113
+ notification.thread_id.should_not be_nil
114
+ end
115
+ end
@@ -0,0 +1,40 @@
1
+ require "spec_helper"
2
+
3
+ describe Coalmine::Sender do
4
+
5
+ before :each do
6
+ FakeWeb.clean_registry
7
+ Coalmine.configure do |config|
8
+ config.signature = "test"
9
+ end
10
+ end
11
+
12
+ context "sending data" do
13
+ it "successfully sends data given the happy path" do
14
+ FakeWeb.register_uri(:post, Coalmine::Sender.url, :status => "200")
15
+ Coalmine::Sender.send("some test data").should be_true
16
+ end
17
+
18
+ it "successfully sends data through a proxy" do
19
+ Coalmine.configure do |config|
20
+ config.proxy_host = "coalmine-proxy.net"
21
+ config.proxy_port = 999
22
+ config.proxy_user = "some_user"
23
+ config.proxy_password = "Sp3c!@lCh4r#"
24
+ end
25
+
26
+ FakeWeb.register_uri(:post, Coalmine::Sender.url, :status => "200")
27
+ Coalmine::Sender.send("something").should be_true
28
+ end
29
+
30
+ it "fails when response code is bad" do
31
+ FakeWeb.register_uri(:post, Coalmine::Sender.url, :status => ["500", "Internal Server Error"])
32
+ Coalmine::Sender.send("test data").should be_false
33
+ end
34
+
35
+ it "catches network errors" do
36
+ Net::HTTP.any_instance.stub(:post).and_raise(Timeout::Error)
37
+ Coalmine::Sender.send("test").should be_false
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,18 @@
1
+ require "spec_helper"
2
+
3
+ describe Coalmine do
4
+
5
+ context "configuring" do
6
+ it "allows block style configuration" do
7
+ host = "http://www.test.com"
8
+ port = 3000
9
+ Coalmine.configure do |config|
10
+ config.host = host
11
+ config.port = port
12
+ end
13
+
14
+ Coalmine.config.host.should == host
15
+ Coalmine.config.port.should == port
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,4 @@
1
+ require "coalmine"
2
+ require "fakeweb"
3
+
4
+ FakeWeb.allow_net_connect = false
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: coalmine
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 5
8
+ - 0
9
+ version: 0.5.0
10
+ platform: ruby
11
+ authors:
12
+ - Brad Seefeld
13
+ - Matt Ratzloff
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-04-09 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ - !ruby/object:Gem::Dependency
34
+ name: fakeweb
35
+ prerelease: false
36
+ requirement: &id002 !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 0
42
+ version: "0"
43
+ type: :development
44
+ version_requirements: *id002
45
+ - !ruby/object:Gem::Dependency
46
+ name: jsonbuilder
47
+ prerelease: false
48
+ requirement: &id003 !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ segments:
53
+ - 0
54
+ version: "0"
55
+ type: :runtime
56
+ version_requirements: *id003
57
+ description: Send errors to the Coalmine API for logging and analytics.
58
+ email:
59
+ - admin@coalmineapp.com
60
+ executables: []
61
+
62
+ extensions: []
63
+
64
+ extra_rdoc_files: []
65
+
66
+ files:
67
+ - .gitignore
68
+ - Gemfile
69
+ - README.md
70
+ - Rakefile
71
+ - coalmine.gemspec
72
+ - lib/coalmine.rb
73
+ - lib/coalmine/capistrano.rb
74
+ - lib/coalmine/configuration.rb
75
+ - lib/coalmine/logger.rb
76
+ - lib/coalmine/notification.rb
77
+ - lib/coalmine/rack.rb
78
+ - lib/coalmine/rails.rb
79
+ - lib/coalmine/rails/action_controller.rb
80
+ - lib/coalmine/rails/controller_methods.rb
81
+ - lib/coalmine/rails/tasks.rb
82
+ - lib/coalmine/railtie.rb
83
+ - lib/coalmine/sender.rb
84
+ - lib/coalmine/version.rb
85
+ - lib/coalmine/version_notification.rb
86
+ - rails/init.rb
87
+ - spec/coalmine/configuration_spec.rb
88
+ - spec/coalmine/notification_spec.rb
89
+ - spec/coalmine/sender_spec.rb
90
+ - spec/coalmine_spec.rb
91
+ - spec/spec_helper.rb
92
+ has_rdoc: true
93
+ homepage: https://github.com/coalmine/coalmine_ruby
94
+ licenses: []
95
+
96
+ post_install_message:
97
+ rdoc_options: []
98
+
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ segments:
113
+ - 0
114
+ version: "0"
115
+ requirements: []
116
+
117
+ rubyforge_project: coalmine
118
+ rubygems_version: 1.3.6
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: Coalmine Connector Ruby implementation
122
+ test_files:
123
+ - spec/coalmine/configuration_spec.rb
124
+ - spec/coalmine/notification_spec.rb
125
+ - spec/coalmine/sender_spec.rb
126
+ - spec/coalmine_spec.rb
127
+ - spec/spec_helper.rb