errordite 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +23 -0
- data/Rakefile +20 -0
- data/errordite-rack.gemspec +21 -0
- data/errordite-rails.gemspec +21 -0
- data/errordite.gemspec +28 -0
- data/examples/rack/Gemfile +4 -0
- data/examples/rack/config.ru +7 -0
- data/examples/rails/.gitignore +16 -0
- data/examples/rails/Gemfile +36 -0
- data/examples/rails/README.rdoc +28 -0
- data/examples/rails/Rakefile +6 -0
- data/examples/rails/app/assets/images/.keep +0 -0
- data/examples/rails/app/assets/javascripts/application.js +16 -0
- data/examples/rails/app/assets/stylesheets/application.css +13 -0
- data/examples/rails/app/controllers/application_controller.rb +5 -0
- data/examples/rails/app/controllers/concerns/.keep +0 -0
- data/examples/rails/app/helpers/application_helper.rb +2 -0
- data/examples/rails/app/mailers/.keep +0 -0
- data/examples/rails/app/models/.keep +0 -0
- data/examples/rails/app/models/concerns/.keep +0 -0
- data/examples/rails/app/views/layouts/application.html.erb +14 -0
- data/examples/rails/bin/bundle +3 -0
- data/examples/rails/bin/rails +4 -0
- data/examples/rails/bin/rake +4 -0
- data/examples/rails/config.ru +4 -0
- data/examples/rails/config/application.rb +23 -0
- data/examples/rails/config/boot.rb +4 -0
- data/examples/rails/config/database.yml +25 -0
- data/examples/rails/config/environment.rb +5 -0
- data/examples/rails/config/environments/development.rb +29 -0
- data/examples/rails/config/environments/production.rb +80 -0
- data/examples/rails/config/environments/test.rb +36 -0
- data/examples/rails/config/initializers/backtrace_silencers.rb +7 -0
- data/examples/rails/config/initializers/filter_parameter_logging.rb +4 -0
- data/examples/rails/config/initializers/inflections.rb +16 -0
- data/examples/rails/config/initializers/mime_types.rb +5 -0
- data/examples/rails/config/initializers/secret_token.rb +12 -0
- data/examples/rails/config/initializers/session_store.rb +3 -0
- data/examples/rails/config/initializers/wrap_parameters.rb +14 -0
- data/examples/rails/config/locales/en.yml +23 -0
- data/examples/rails/config/routes.rb +56 -0
- data/examples/rails/db/seeds.rb +7 -0
- data/examples/rails/lib/assets/.keep +0 -0
- data/examples/rails/lib/tasks/.keep +0 -0
- data/examples/rails/log/.keep +0 -0
- data/examples/rails/public/404.html +58 -0
- data/examples/rails/public/422.html +58 -0
- data/examples/rails/public/500.html +57 -0
- data/examples/rails/public/favicon.ico +0 -0
- data/examples/rails/public/robots.txt +5 -0
- data/lib/errordite-rails.rb +7 -0
- data/lib/errordite.rb +33 -0
- data/lib/errordite/client.rb +44 -0
- data/lib/errordite/config.rb +44 -0
- data/lib/errordite/rack.rb +22 -0
- data/lib/errordite/serializer.rb +62 -0
- data/lib/errordite/version.rb +3 -0
- data/spec/lib/errordite/client_spec.rb +64 -0
- data/spec/lib/errordite/config_spec.rb +61 -0
- data/spec/lib/errordite/serializer_spec.rb +66 -0
- data/spec/lib/errordite_spec.rb +34 -0
- data/spec/spec_helper.rb +1 -0
- metadata +214 -0
File without changes
|
data/lib/errordite.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'errordite/version'
|
2
|
+
|
3
|
+
module Errordite
|
4
|
+
autoload :Client, 'errordite/client'
|
5
|
+
autoload :Config, 'errordite/config'
|
6
|
+
autoload :Rack, 'errordite/rack'
|
7
|
+
autoload :Serializer, 'errordite/serializer'
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def config
|
11
|
+
@config ||= Config.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def config=(config)
|
15
|
+
@config = config
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method, *args)
|
19
|
+
if config.respond_to?(method)
|
20
|
+
config.__send__ method, *args
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def monitor(context = {})
|
27
|
+
yield
|
28
|
+
rescue Exception => e
|
29
|
+
client.record e, context
|
30
|
+
raise e
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
class Errordite::Client
|
4
|
+
attr_reader :server, :port, :logger
|
5
|
+
|
6
|
+
def initialize(server = 'www.errordite.com', port = 443, logger = Errordite.logger)
|
7
|
+
@server = server
|
8
|
+
@port = port
|
9
|
+
@logger = logger
|
10
|
+
end
|
11
|
+
|
12
|
+
def record(error, context)
|
13
|
+
post_json '/receiveerror', Errordite::Serializer.new(error, context).to_json
|
14
|
+
end
|
15
|
+
|
16
|
+
def post_json(path, body)
|
17
|
+
headers = {'Content-Type' => 'application/json; charset=utf-8', 'Content-Length' => body.size.to_s}
|
18
|
+
response = Connection.new(server, port).post path, body, headers
|
19
|
+
log_response response
|
20
|
+
response
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def log_response(response)
|
26
|
+
if response.code != "201"
|
27
|
+
logger.warn "Failed to log error: #{response.code} #{response.message}"
|
28
|
+
elsif logger.debug?
|
29
|
+
logger.debug "Error logged: #{response.code} #{response.message}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Connection
|
34
|
+
def initialize(server, port)
|
35
|
+
@http = Net::HTTP.new server, port
|
36
|
+
@http.use_ssl = true
|
37
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
38
|
+
end
|
39
|
+
|
40
|
+
def post(*args)
|
41
|
+
@http.post(*args)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'errordite'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
class Errordite::Config
|
5
|
+
def api_token
|
6
|
+
@api_token ||= ENV['ERRORDITE_TOKEN']
|
7
|
+
end
|
8
|
+
|
9
|
+
def api_token=(token)
|
10
|
+
@api_token = token
|
11
|
+
end
|
12
|
+
|
13
|
+
def server
|
14
|
+
@server ||= 'www.errordite.com'
|
15
|
+
end
|
16
|
+
|
17
|
+
def server=(s)
|
18
|
+
@server = s
|
19
|
+
end
|
20
|
+
|
21
|
+
def port
|
22
|
+
@port ||= 443
|
23
|
+
end
|
24
|
+
|
25
|
+
def port=(p)
|
26
|
+
@port = p
|
27
|
+
end
|
28
|
+
|
29
|
+
def logger
|
30
|
+
@logger ||= begin
|
31
|
+
l = Logger.new(STDOUT)
|
32
|
+
l.level = Logger::WARN
|
33
|
+
l
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def client
|
38
|
+
@client ||= Errordite::Client.new(server, port)
|
39
|
+
end
|
40
|
+
|
41
|
+
def client=(client)
|
42
|
+
@client = client
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'errordite'
|
2
|
+
require 'rack/request'
|
3
|
+
|
4
|
+
class Errordite::Rack
|
5
|
+
def initialize(app, options = {})
|
6
|
+
@app = app
|
7
|
+
@context = options[:context] || {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
Errordite.monitor rack_context(env).merge(@context) do
|
12
|
+
@app.call(env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def rack_context(env)
|
19
|
+
request = Rack::Request.new(env)
|
20
|
+
{'Url' => request.url, 'UserAgent' => env['HTTP_USER_AGENT']}
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'errordite'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class Errordite::Serializer
|
5
|
+
SPECIAL_CONTEXT_ATTRIBUTES = ['MachineName', 'Url', 'UserAgent', 'Version']
|
6
|
+
|
7
|
+
attr_reader :exception, :context
|
8
|
+
|
9
|
+
def initialize(exception, context = {})
|
10
|
+
@exception = exception
|
11
|
+
@context = context
|
12
|
+
end
|
13
|
+
|
14
|
+
def as_json
|
15
|
+
special_attributes.merge(
|
16
|
+
"Token" => Errordite.api_token,
|
17
|
+
"TimestampUtc" => Time.now.strftime("%Y-%m-%d %H:%M:%S"),
|
18
|
+
"ExceptionInfo" => {
|
19
|
+
"Source" => source,
|
20
|
+
"Message" => exception.message,
|
21
|
+
"MethodName" => method_name,
|
22
|
+
"ExceptionType" => exception.class.name,
|
23
|
+
"StackTrace" => stack_trace,
|
24
|
+
"Data" => exception_data
|
25
|
+
}
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_json
|
30
|
+
JSON.dump(as_json)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def special_attributes
|
36
|
+
Hash[context.select {|k, v| SPECIAL_CONTEXT_ATTRIBUTES.include?(k) }]
|
37
|
+
end
|
38
|
+
|
39
|
+
def exception_data
|
40
|
+
Hash[context.reject {|k, v| SPECIAL_CONTEXT_ATTRIBUTES.include?(k) }]
|
41
|
+
end
|
42
|
+
|
43
|
+
def timestamp
|
44
|
+
Time.now.strftime("%Y-%m-%d %H:%M:%S")
|
45
|
+
end
|
46
|
+
|
47
|
+
def stack_trace
|
48
|
+
exception.backtrace && exception.backtrace.join("\n")
|
49
|
+
end
|
50
|
+
|
51
|
+
def source
|
52
|
+
split_first_line && split_first_line[1]
|
53
|
+
end
|
54
|
+
|
55
|
+
def method_name
|
56
|
+
split_first_line && split_first_line[3]
|
57
|
+
end
|
58
|
+
|
59
|
+
def split_first_line
|
60
|
+
@split_first_line ||= exception.backtrace && exception.backtrace[0] && exception.backtrace[0].match(/^(.*?)(?:\:(\d+))(?:\:in `(.*)')?$/)
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Errordite::Client do
|
4
|
+
let(:logger) { double(:logger) }
|
5
|
+
let(:connection) { double(:connection) }
|
6
|
+
let(:response) { double(:response, code: '201', message: 'Created') }
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
Errordite::Client::Connection.stub(:new).and_return(connection)
|
10
|
+
connection.stub(:post).and_return(response)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'records errors by posting them to /receiveerror' do
|
14
|
+
error = double(:error)
|
15
|
+
context = double(:context)
|
16
|
+
serializer = double(:serializer, to_json: 'json-representation')
|
17
|
+
Errordite::Serializer.stub(:new).with(error, context).and_return(serializer)
|
18
|
+
subject.should_receive(:post_json).with('/receiveerror', 'json-representation')
|
19
|
+
subject.record(error, context)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'sends json to the server by posting through a new connection' do
|
23
|
+
path = '/receiveerror'
|
24
|
+
body = '{}'
|
25
|
+
connection.should_receive(:post).with(
|
26
|
+
'/receiveerror', '{}', 'Content-Type' => 'application/json; charset=utf-8', 'Content-Length' => '2'
|
27
|
+
).and_return(response)
|
28
|
+
Errordite::Client.new.post_json(path, body)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'logs response if code != 201' do
|
32
|
+
client = Errordite::Client.new('server.example.com', 443, logger)
|
33
|
+
response.stub(:code).and_return('500')
|
34
|
+
response.stub(:message).and_return('Server Error')
|
35
|
+
logger.should_receive(:warn)
|
36
|
+
client.post_json '/receiveerror', ''
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'logs response if log level is debug' do
|
40
|
+
client = Errordite::Client.new('server.example.com', 443, logger)
|
41
|
+
response.stub(:code).and_return('500')
|
42
|
+
response.stub(:message).and_return('Server Error')
|
43
|
+
logger.should_receive(:warn)
|
44
|
+
client.post_json '/receiveerror', ''
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe Errordite::Client::Connection do
|
49
|
+
let(:http) { double(:http).as_null_object }
|
50
|
+
|
51
|
+
it 'creates a new verified https connection to the given server and port' do
|
52
|
+
Net::HTTP.stub(:new).with('one.example.com', 443).and_return(http)
|
53
|
+
http.should_receive(:use_ssl=).with(true)
|
54
|
+
http.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
|
55
|
+
Errordite::Client::Connection.new('one.example.com', 443)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'posts data through the https connection' do
|
59
|
+
Net::HTTP.stub(:new).and_return(http)
|
60
|
+
arguments = double('arguments')
|
61
|
+
http.should_receive(:post).with(arguments)
|
62
|
+
Errordite::Client::Connection.new('one.example.com', 443).post(arguments)
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Errordite::Config do
|
4
|
+
subject { Errordite::Config.new }
|
5
|
+
let(:client) { double('client') }
|
6
|
+
|
7
|
+
it 'uses www.errordite.com as default server' do
|
8
|
+
expect(subject.server).to eql('www.errordite.com')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'allows server to be set' do
|
12
|
+
subject.server = 'errordite.example.com'
|
13
|
+
expect(subject.server).to eql('errordite.example.com')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'uses 443 as default port' do
|
17
|
+
expect(subject.port).to eql(443)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'allows port to be set' do
|
21
|
+
subject.port = 80
|
22
|
+
expect(subject.port).to eql(80)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'uses ERRORDITE_TOKEN environment variable as default api token' do
|
26
|
+
ENV['ERRORDITE_TOKEN'] = 'token-from-environment'
|
27
|
+
expect(subject.api_token).to eql('token-from-environment')
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'allows api token to be set' do
|
31
|
+
ENV['ERRORDITE_TOKEN'] = 'token-from-environment'
|
32
|
+
subject.api_token = 'overidden-token'
|
33
|
+
expect(subject.api_token).to eql('overidden-token')
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'logs to STDOUT with log level of WARN by default' do
|
37
|
+
logger = double('logger')
|
38
|
+
Logger.should_receive(:new).with(STDOUT).and_return(logger)
|
39
|
+
logger.should_receive(:level=).with(Logger::WARN)
|
40
|
+
expect(subject.logger).to eql(logger)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'builds client with server and port' do
|
44
|
+
subject.server = 'errordite.example.com'
|
45
|
+
subject.port = 123
|
46
|
+
Errordite::Client.should_receive(:new).with('errordite.example.com', 123).and_return(client)
|
47
|
+
expect(subject.client).to eql(client)
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'memoizes client' do
|
51
|
+
Errordite::Client.stub(:new).and_return(client)
|
52
|
+
expect(subject.client).to eql(client)
|
53
|
+
expect(subject.client).to eql(client)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'allows custom client to be set' do
|
57
|
+
custom_client = double('custom-client')
|
58
|
+
subject.client = custom_client
|
59
|
+
expect(subject.client).to eql(custom_client)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Errordite::Serializer do
|
4
|
+
describe '(in general)' do
|
5
|
+
let(:context) { Hash.new }
|
6
|
+
let(:exception) { Exception.new }
|
7
|
+
let(:serializer) { Errordite::Serializer.new(exception, context) }
|
8
|
+
|
9
|
+
it 'includes Token taken from Errordite.api_token' do
|
10
|
+
Errordite.stub(:api_token).and_return("AnApiToken")
|
11
|
+
expect(serializer.as_json['Token']).to eql('AnApiToken')
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'takes TimestampUtc from the current time' do
|
15
|
+
Time.stub(:now).and_return(Time.utc(2013, 3, 4, 12, 34, 56))
|
16
|
+
expect(serializer.as_json['TimestampUtc']).to eql('2013-03-04 12:34:56')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'takes Source from backtrace file' do
|
20
|
+
exception.stub(:backtrace).and_return(["/path/to/file/example.rb:80:in `hello'"])
|
21
|
+
expect(serializer.as_json['ExceptionInfo']['Source']).to eql('/path/to/file/example.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'takes MethodName from backtrace method' do
|
25
|
+
exception.stub(:backtrace).and_return(["/path/to/file/example.rb:80:in `hello'"])
|
26
|
+
expect(serializer.as_json['ExceptionInfo']['MethodName']).to eql('hello')
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'takes ExceptionType from exception class' do
|
30
|
+
exception.stub(:class).and_return(FloatDomainError)
|
31
|
+
expect(serializer.as_json['ExceptionInfo']['ExceptionType']).to eql('FloatDomainError')
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'takes Message from exception message' do
|
35
|
+
exception.stub(:message).and_return("My message is this")
|
36
|
+
expect(serializer.as_json['ExceptionInfo']['Message']).to eql('My message is this')
|
37
|
+
end
|
38
|
+
|
39
|
+
['MachineName', 'Url', 'UserAgent', 'Version'].each do |special_attribute|
|
40
|
+
it "adds #{special_attribute} to top level if included in context" do
|
41
|
+
context[special_attribute] = "special-attribute-value"
|
42
|
+
expect(serializer.as_json[special_attribute]).to eql("special-attribute-value")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "adds other unknown attributes to exception data when included in context" do
|
47
|
+
context["Anything"] = "anything-value"
|
48
|
+
context["Something"] = "something-value"
|
49
|
+
expect(serializer.as_json["ExceptionInfo"]["Data"]).to eql({
|
50
|
+
"Anything" => "anything-value",
|
51
|
+
"Something" => "something-value"
|
52
|
+
})
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'forms StackTrace using backtrace joined with newlines' do
|
56
|
+
exception.stub(:backtrace).and_return(["first-line", "second-line"])
|
57
|
+
expect(serializer.as_json['ExceptionInfo']['StackTrace']).to eql("first-line\nsecond-line")
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'is serializable as JSON' do
|
61
|
+
serializer.stub(:as_json).and_return(:as_json_result)
|
62
|
+
JSON.should_receive(:dump).with(:as_json_result).and_return(:serialized_json)
|
63
|
+
expect(serializer.to_json).to eql(:serialized_json)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Errordite do
|
4
|
+
let(:config) { double('config') }
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
Errordite.config = nil
|
8
|
+
Errordite::Config.stub(:new).and_return(config)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'builds a new Config instance if none set' do
|
12
|
+
Errordite::Config.should_receive(:new).and_return(config)
|
13
|
+
expect(Errordite.config).to eql(config)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'returns same Config instance if asked multiple times' do
|
17
|
+
expect(Errordite.config).to equal(Errordite.config)
|
18
|
+
end
|
19
|
+
|
20
|
+
[:api_token].each do |method|
|
21
|
+
it "delegates getting #{method} to config instance" do
|
22
|
+
result = double('result')
|
23
|
+
config.should_receive(method).and_return(result)
|
24
|
+
expect(Errordite.__send__(method)).to equal(result)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "delegates setting #{method} to config instance" do
|
28
|
+
argument = double('argument')
|
29
|
+
result = double('result')
|
30
|
+
config.should_receive("#{method}=").with(argument).and_return(result)
|
31
|
+
expect(Errordite.__send__("#{method}=", argument)).to equal(result)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|