captivus 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/captivus.gemspec +23 -0
- data/lib/captivus/backtrace/line.rb +21 -0
- data/lib/captivus/backtrace.rb +14 -0
- data/lib/captivus/configuration.rb +23 -0
- data/lib/captivus/payload.rb +20 -0
- data/lib/captivus/rack_capturer.rb +23 -0
- data/lib/captivus/version.rb +3 -0
- data/lib/captivus.rb +33 -0
- data/lib/faraday/request/hmac_authentication.rb +69 -0
- data/spec/captivus/backtrace/line_spec.rb +53 -0
- data/spec/captivus/backtrace_spec.rb +41 -0
- data/spec/captivus/configuration_spec.rb +67 -0
- data/spec/captivus/payload_spec.rb +47 -0
- data/spec/captivus/rack_capturer_spec.rb +39 -0
- data/spec/captivus_spec.rb +62 -0
- data/spec/integration/events_from_rack_spec.rb +93 -0
- data/spec/spec_helper.rb +37 -0
- metadata +123 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Austin Schneider
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Captivus
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'captivus'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install captivus
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/captivus.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'captivus/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "captivus"
|
8
|
+
gem.version = Captivus::VERSION
|
9
|
+
gem.authors = ["Austin Schneider"]
|
10
|
+
gem.email = ["austinthecoder@gmail.com"]
|
11
|
+
gem.description = "Captivus client gem"
|
12
|
+
gem.summary = "Captivus client gem"
|
13
|
+
gem.homepage = "http://captiv.us"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "faraday", '~> 0.8.4'
|
21
|
+
gem.add_dependency "multi_json", "~> 1.3.6"
|
22
|
+
gem.add_dependency "captivus-auth_hmac", "0.0.1"
|
23
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Captivus
|
2
|
+
class Backtrace
|
3
|
+
class Line
|
4
|
+
PATTERN = /^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$/
|
5
|
+
|
6
|
+
def initialize(raw_line)
|
7
|
+
if match_data = PATTERN.match(raw_line)
|
8
|
+
@as_json = {
|
9
|
+
:file => match_data[1],
|
10
|
+
:number => match_data[2].to_i,
|
11
|
+
:method => match_data[3]
|
12
|
+
}
|
13
|
+
else
|
14
|
+
raise ArgumentError, "Unrecognized format"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :as_json
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'captivus/backtrace/line'
|
2
|
+
|
3
|
+
module Captivus
|
4
|
+
class Backtrace
|
5
|
+
def initialize(exception)
|
6
|
+
unless exception.respond_to?(:backtrace)
|
7
|
+
raise ArgumentError, "#{exception} must respond to `backtrace`"
|
8
|
+
end
|
9
|
+
@as_json = Array(exception.backtrace).map { |line| Line.new(line).as_json }
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :as_json
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Captivus
|
2
|
+
class Configuration
|
3
|
+
def initialize(attrs = {}, &block)
|
4
|
+
defaults = {
|
5
|
+
:scheme => 'http',
|
6
|
+
:host => 'api.captiv.us'
|
7
|
+
}
|
8
|
+
configure defaults.merge(attrs), &block
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :host, :scheme, :api_key, :api_secret_key
|
12
|
+
|
13
|
+
def configure(attrs = {})
|
14
|
+
attrs.each { |attr, value| send "#{attr}=", value }
|
15
|
+
yield self if block_given?
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def ==(other)
|
20
|
+
other.is_a?(Captivus::Configuration) && host == other.host
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Captivus
|
2
|
+
class Payload
|
3
|
+
def initialize(exception)
|
4
|
+
if exception.respond_to?(:class) && exception.class.respond_to?(:name) && exception.respond_to?(:message)
|
5
|
+
@as_json = {
|
6
|
+
'event' => {
|
7
|
+
'type' => exception.class.name,
|
8
|
+
'message' => exception.message,
|
9
|
+
'timestamp' => Time.now.utc.to_s
|
10
|
+
},
|
11
|
+
'backtrace' => Backtrace.new(exception).as_json
|
12
|
+
}
|
13
|
+
else
|
14
|
+
raise ArgumentError, "Unexpected exception: #{exception.inspect}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :as_json
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module Captivus
|
5
|
+
class RackCapturer
|
6
|
+
def initialize(app)
|
7
|
+
@app = app
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
begin
|
12
|
+
@app.call env
|
13
|
+
rescue => exception
|
14
|
+
Captivus.notify exception
|
15
|
+
raise
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :app
|
22
|
+
end
|
23
|
+
end
|
data/lib/captivus.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "captivus/version"
|
2
|
+
require 'captivus/configuration'
|
3
|
+
require 'faraday/request/hmac_authentication'
|
4
|
+
|
5
|
+
module Captivus
|
6
|
+
class << self
|
7
|
+
def config
|
8
|
+
@config ||= Configuration.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def configure(*args, &block)
|
12
|
+
config.configure *args, &block
|
13
|
+
end
|
14
|
+
|
15
|
+
def notify(exception)
|
16
|
+
connection = Faraday.new(:url => "#{config.scheme}://#{config.host}") do |faraday|
|
17
|
+
faraday.request :hmac_authentication, config.api_key, config.api_secret_key, {:service_id => 'Captivus'}
|
18
|
+
faraday.adapter :net_http
|
19
|
+
end
|
20
|
+
|
21
|
+
connection.post do |request|
|
22
|
+
request.headers['Content-Type'] = 'application/json; charset=UTF-8'
|
23
|
+
request.url '/events'
|
24
|
+
request.body = MultiJson.dump(Payload.new(exception).as_json)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
require 'captivus/auth_hmac'
|
31
|
+
require 'captivus/backtrace'
|
32
|
+
require 'captivus/payload'
|
33
|
+
require 'captivus/rack_capturer'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'captivus/auth_hmac'
|
2
|
+
require 'uri'
|
3
|
+
require 'faraday'
|
4
|
+
|
5
|
+
module Faraday
|
6
|
+
class Request
|
7
|
+
|
8
|
+
attr_reader :access_id, :secret
|
9
|
+
|
10
|
+
# # Sign the request with the specified `access_id` and `secret`.
|
11
|
+
# def sign!(access_id, secret)
|
12
|
+
# @access_id, @secret = access_id, secret
|
13
|
+
|
14
|
+
# #self.sign_with = access_id
|
15
|
+
# #Captivus::AuthHMAC.keys[access_id] = secret
|
16
|
+
# end
|
17
|
+
|
18
|
+
class HMACAuthentication < Faraday::Middleware
|
19
|
+
# Modified CanonicalString to know how to pull from the Faraday-specific
|
20
|
+
# env hash.
|
21
|
+
class CanonicalString < Captivus::AuthHMAC::CanonicalString
|
22
|
+
def request_method(request)
|
23
|
+
request[:method].to_s.upcase
|
24
|
+
end
|
25
|
+
|
26
|
+
def request_body(request)
|
27
|
+
request[:body]
|
28
|
+
end
|
29
|
+
|
30
|
+
def request_path(request)
|
31
|
+
URI.parse(request[:url].to_s).path
|
32
|
+
end
|
33
|
+
|
34
|
+
def headers(request)
|
35
|
+
request[:request_headers]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
KEY = "Authorization".freeze
|
40
|
+
|
41
|
+
attr_reader :auth, :token, :secret
|
42
|
+
|
43
|
+
def initialize(app, token, secret, options = {})
|
44
|
+
options.merge!(:signature => HMACAuthentication::CanonicalString)
|
45
|
+
keys = {token => secret}
|
46
|
+
@token, @secret = token, secret
|
47
|
+
@auth = Captivus::AuthHMAC.new(keys, options)
|
48
|
+
super(app)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Public
|
52
|
+
def call(env)
|
53
|
+
env[:request_headers][KEY] ||= hmac_auth_header(env).to_s if sign_request?
|
54
|
+
@app.call(env)
|
55
|
+
end
|
56
|
+
|
57
|
+
def hmac_auth_header(env)
|
58
|
+
auth.authorization(env, token, secret)
|
59
|
+
end
|
60
|
+
|
61
|
+
def sign_request?
|
62
|
+
!!@token && !!@secret
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
Faraday.register_middleware :request, :hmac_authentication => Faraday::Request::HMACAuthentication
|
68
|
+
end
|
69
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Captivus::Backtrace::Line do
|
4
|
+
def new_line(*args)
|
5
|
+
Captivus::Backtrace::Line.new *args
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ".new" do
|
9
|
+
it "raises an argument error when the line is not a correctly formatted backtrace line" do
|
10
|
+
[
|
11
|
+
nil,
|
12
|
+
' ',
|
13
|
+
'foo',
|
14
|
+
'/Users/austin/something.rb:10:',
|
15
|
+
"/Users/austin/something.rb:10:in 'something'",
|
16
|
+
].each do |line|
|
17
|
+
expect { new_line line }.to raise_error(ArgumentError, "Unrecognized format")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "as_json" do
|
23
|
+
context "when the line has the format <file>:<number>" do
|
24
|
+
it "returns the parts, minus the method, in a hash" do
|
25
|
+
new_line("/Users/austin/dir/run-file.rb:1").as_json.should == {
|
26
|
+
:file => '/Users/austin/dir/run-file.rb',
|
27
|
+
:number => 1,
|
28
|
+
:method => nil
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "when the line has the format <file>:<number>:in `<method>'" do
|
34
|
+
it "returns the parts in a hash" do
|
35
|
+
new_line("/Users/austin/go.rb:10:in `perform'").as_json.should == {
|
36
|
+
:file => '/Users/austin/go.rb',
|
37
|
+
:number => 10,
|
38
|
+
:method => 'perform'
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "when the line has the format <drive>:<file>:<number>:in `<method>'" do
|
44
|
+
it "returns the parts in a hash" do
|
45
|
+
new_line("C:/Users/austin/go.rb:10:in `perform'").as_json.should == {
|
46
|
+
:file => 'C:/Users/austin/go.rb',
|
47
|
+
:number => 10,
|
48
|
+
:method => 'perform'
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Captivus::Backtrace do
|
4
|
+
def new_backtrace(*args)
|
5
|
+
Captivus::Backtrace.new *args
|
6
|
+
end
|
7
|
+
|
8
|
+
describe ".new" do
|
9
|
+
context "when the given object doesn't respond to `backtrace`" do
|
10
|
+
it "raises an argument error" do
|
11
|
+
object = double 'object'
|
12
|
+
expect {
|
13
|
+
new_backtrace object
|
14
|
+
}.to raise_error(ArgumentError, "#{object} must respond to `backtrace`")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'as_json' do
|
20
|
+
before do
|
21
|
+
@object = double 'object', :backtrace => ['/Users/x.rb:1', '/Users/x.rb:2', '/Users/x.rb:3']
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when the object has a backtrace" do
|
25
|
+
it 'is an array of the backtrace lines as json' do
|
26
|
+
new_backtrace(@object).as_json.should == [
|
27
|
+
Captivus::Backtrace::Line.new('/Users/x.rb:1').as_json,
|
28
|
+
Captivus::Backtrace::Line.new('/Users/x.rb:2').as_json,
|
29
|
+
Captivus::Backtrace::Line.new('/Users/x.rb:3').as_json
|
30
|
+
]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when the object doesn't have a backtrace" do
|
35
|
+
it "is an empty array" do
|
36
|
+
@object.stub :backtrace
|
37
|
+
new_backtrace(@object).as_json.should == []
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Captivus::Configuration do
|
4
|
+
[
|
5
|
+
['scheme', 'http'],
|
6
|
+
['host', 'api.captiv.us'],
|
7
|
+
['api_key', nil],
|
8
|
+
['api_secret_key', nil]
|
9
|
+
].each do |attribute, default_value|
|
10
|
+
describe attribute do
|
11
|
+
it "defaults to #{default_value.inspect}" do
|
12
|
+
expect(eval("Captivus::Configuration.new.#{attribute}")).to eq default_value
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is the given #{attribute.to_sym} option" do
|
16
|
+
expect(eval("Captivus::Configuration.new(:#{attribute} => 'x').#{attribute}")).to eq 'x'
|
17
|
+
expect(eval("Captivus::Configuration.new(:#{attribute} => ' ').#{attribute}")).to eq ' '
|
18
|
+
expect(eval("Captivus::Configuration.new(:#{attribute} => nil).#{attribute}")).to be_nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "host" do
|
24
|
+
it "defaults to api.captiv.us" do
|
25
|
+
expect(Captivus::Configuration.new.host).to eq 'api.captiv.us'
|
26
|
+
end
|
27
|
+
|
28
|
+
it "is the given host option" do
|
29
|
+
expect(Captivus::Configuration.new(:host => 'x').host).to eq 'x'
|
30
|
+
expect(Captivus::Configuration.new(:host => ' ').host).to eq ' '
|
31
|
+
expect(Captivus::Configuration.new(:host => nil).host).to be_nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "configure" do
|
36
|
+
before { @config = Captivus::Configuration.new }
|
37
|
+
|
38
|
+
it "sets its attributes from the given hash" do
|
39
|
+
@config.configure :host => 'newho.st'
|
40
|
+
expect(@config.host).to eq 'newho.st'
|
41
|
+
end
|
42
|
+
|
43
|
+
it "yields itself if given a block" do
|
44
|
+
@config.configure do |config|
|
45
|
+
expect(config).to eq @config
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "returns itself" do
|
50
|
+
expect(@config.configure).to equal @config
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "==" do
|
55
|
+
it "is true given another configuration with the same attributes" do
|
56
|
+
expect(Captivus::Configuration.new(:host => 'a')).to eq Captivus::Configuration.new(:host => 'a')
|
57
|
+
end
|
58
|
+
|
59
|
+
it "is false given another configuration with different attributes" do
|
60
|
+
expect(Captivus::Configuration.new).to_not eq Captivus::Configuration.new(:host => 'a')
|
61
|
+
end
|
62
|
+
|
63
|
+
it "is false given a non configuration" do
|
64
|
+
expect(Captivus::Configuration.new).to_not eq Object.new
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Captivus::Payload do
|
4
|
+
describe ".new" do
|
5
|
+
context "when argument doesn't behave like an exception" do
|
6
|
+
it "raises an argument error" do
|
7
|
+
[
|
8
|
+
double('exception'),
|
9
|
+
double('exception', :class => double('class')),
|
10
|
+
double('exception', :class => double('class', :name => 'X')),
|
11
|
+
double('exception', :class => double('class'), :message => 'x')
|
12
|
+
].each do |exception|
|
13
|
+
expect {
|
14
|
+
Captivus::Payload.new exception
|
15
|
+
}.to raise_error(ArgumentError, "Unexpected exception: #{exception.inspect}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "as_json" do
|
22
|
+
it "returns a hash of event and backtrace info" do
|
23
|
+
Timecop.freeze Time.utc(2012, 8, 23, 16, 34, 19)
|
24
|
+
|
25
|
+
exception = double 'exception',
|
26
|
+
:class => double('class', :name => 'ExceptionType'),
|
27
|
+
:message => 'The exception message.',
|
28
|
+
:backtrace => ['/Users/x.rb:1', '/Users/x.rb:2', '/Users/x.rb:3']
|
29
|
+
|
30
|
+
Captivus::Payload.new(exception).as_json.tap do |hash|
|
31
|
+
expect(hash.keys).to match_array ['event', 'backtrace']
|
32
|
+
|
33
|
+
hash['event'].tap do |event|
|
34
|
+
expect(event.keys).to match_array ['type', 'message', 'timestamp']
|
35
|
+
|
36
|
+
expect(event['type']).to eq 'ExceptionType'
|
37
|
+
expect(event['message']).to eq 'The exception message.'
|
38
|
+
expect(event['timestamp']).to eq '2012-08-23 16:34:19 UTC'
|
39
|
+
end
|
40
|
+
|
41
|
+
hash['backtrace'].should == Captivus::Backtrace.new(exception).as_json
|
42
|
+
end
|
43
|
+
|
44
|
+
Timecop.return
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Captivus::RackCapturer do
|
4
|
+
describe "call" do
|
5
|
+
before do
|
6
|
+
@app = double 'app'
|
7
|
+
@rack_capturer = Captivus::RackCapturer.new @app
|
8
|
+
end
|
9
|
+
|
10
|
+
it "is delegated to the given app, passing the env along" do
|
11
|
+
env = double 'env'
|
12
|
+
result = double 'result'
|
13
|
+
@app.stub(:call).with(env) { result }
|
14
|
+
expect(@rack_capturer.call(env)).to eq result
|
15
|
+
end
|
16
|
+
|
17
|
+
context "when calling the app raises an error" do
|
18
|
+
before { @app.stub(:call) { raise StandardError, 'Error raised' } }
|
19
|
+
|
20
|
+
it "raises the error" do
|
21
|
+
expect { @rack_capturer.call double('env') }.to raise_error StandardError, "Error raised"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "notifies the Captivus API" do
|
25
|
+
Captivus.should_receive(:notify).with kind_of(StandardError)
|
26
|
+
@rack_capturer.call double('env') rescue nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context "when calling the app does not raise an error" do
|
31
|
+
before { @app.stub :call }
|
32
|
+
|
33
|
+
it "does not notify the Captivus API" do
|
34
|
+
@rack_capturer.call double('env')
|
35
|
+
expect(captivus_api.requests.size).to eq 0
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Captivus do
|
4
|
+
describe ".configuration" do
|
5
|
+
it "is a configuration" do
|
6
|
+
expect(Captivus.config).to eq Captivus::Configuration.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "always returns the same configuration" do
|
10
|
+
expect(Captivus.config).to equal Captivus.config
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe ".configure" do
|
15
|
+
it "is delegated to the config" do
|
16
|
+
attrs = {:a => 1, :b => 2}
|
17
|
+
block = Proc.new {}
|
18
|
+
Captivus.config.should_receive(:configure).with(attrs, &block)
|
19
|
+
Captivus.configure attrs, &block
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe ".notify" do
|
24
|
+
it "sends a notice to the Captivus API" do
|
25
|
+
Timecop.freeze Time.utc(2012, 11, 28, 2, 46, 4)
|
26
|
+
|
27
|
+
exception = StandardError.new('Error raised')
|
28
|
+
|
29
|
+
Captivus.configure do |config|
|
30
|
+
config.api_key = 'api-key'
|
31
|
+
config.api_secret_key = 'secret'
|
32
|
+
end
|
33
|
+
|
34
|
+
Captivus.notify exception
|
35
|
+
|
36
|
+
expect(captivus_api.requests.size).to eq 1
|
37
|
+
|
38
|
+
headers = {
|
39
|
+
"CONTENT_TYPE"=>"application/json; charset=UTF-8",
|
40
|
+
"HTTP_ACCEPT" => "*/*",
|
41
|
+
"HTTP_USER_AGENT" => "Ruby",
|
42
|
+
"HTTP_DATE" => "Wed, 28 Nov 2012 02:46:04 GMT",
|
43
|
+
"HTTP_AUTHORIZATION" => "Captivus api-key:IgBW675pSKkCuD4McYnX9QQMI5g=",
|
44
|
+
"PATH_INFO" => "/events",
|
45
|
+
"QUERY_STRING" => "",
|
46
|
+
"REQUEST_METHOD"=>"POST",
|
47
|
+
"SCRIPT_NAME" => "",
|
48
|
+
"SERVER_NAME" => "api.captiv.us",
|
49
|
+
"SERVER_PORT" => "80",
|
50
|
+
"rack.url_scheme" => "http"
|
51
|
+
}
|
52
|
+
|
53
|
+
last_request = captivus_api.requests[0]
|
54
|
+
|
55
|
+
expect(last_request.values_at(*headers.keys)).to eq headers.values
|
56
|
+
|
57
|
+
expect(last_request['rack.input'].read).to eq MultiJson.dump(Captivus::Payload.new(exception).as_json)
|
58
|
+
|
59
|
+
Timecop.return
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Reporting from a rack app' do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
before do
|
7
|
+
Captivus.configure do |config|
|
8
|
+
config.api_key = 'api-key'
|
9
|
+
config.api_secret_key = 'secret'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "when installed as middleware in a Rack app" do
|
14
|
+
def app
|
15
|
+
Rack::Builder.app do
|
16
|
+
use Captivus::RackCapturer
|
17
|
+
run lambda { |env| raise StandardError, "Error from Rack app" }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
it "errors are raised as expected" do
|
22
|
+
expect { get '/' }.to raise_error StandardError, "Error from Rack app"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "reports errors to the Captivus API" do
|
26
|
+
Timecop.freeze Time.utc(2012, 8, 23, 16, 34, 19)
|
27
|
+
|
28
|
+
get '/' rescue nil
|
29
|
+
|
30
|
+
expect(captivus_api.requests.size).to eq 1
|
31
|
+
|
32
|
+
last_request = captivus_api.requests[0]
|
33
|
+
|
34
|
+
headers = {
|
35
|
+
"CONTENT_TYPE"=>"application/json; charset=UTF-8",
|
36
|
+
"HTTP_ACCEPT" => "*/*",
|
37
|
+
"HTTP_USER_AGENT" => "Ruby",
|
38
|
+
"HTTP_DATE" => "Thu, 23 Aug 2012 16:34:19 GMT",
|
39
|
+
"HTTP_AUTHORIZATION" => "Captivus api-key:syLWMLH8mJ4jTFGO2lVe+9PfgVo=",
|
40
|
+
"PATH_INFO" => "/events",
|
41
|
+
"QUERY_STRING" => "",
|
42
|
+
"REQUEST_METHOD"=>"POST",
|
43
|
+
"SCRIPT_NAME" => "",
|
44
|
+
"SERVER_NAME" => "api.captiv.us",
|
45
|
+
"SERVER_PORT" => "80",
|
46
|
+
"rack.url_scheme" => "http"
|
47
|
+
}
|
48
|
+
|
49
|
+
expect(last_request.values_at(*headers.keys)).to eq headers.values
|
50
|
+
|
51
|
+
MultiJson.load(last_request['rack.input'].read).tap do |payload|
|
52
|
+
expect(payload.keys).to match_array ['event', 'backtrace']
|
53
|
+
|
54
|
+
payload['event'].tap do |event|
|
55
|
+
expect(event.keys).to match_array ['type', 'message', 'timestamp']
|
56
|
+
|
57
|
+
expect(event['type']).to eq "StandardError"
|
58
|
+
expect(event['message']).to eq "Error from Rack app"
|
59
|
+
expect(event['timestamp']).to eq "2012-08-23 16:34:19 UTC"
|
60
|
+
end
|
61
|
+
|
62
|
+
expect(payload['backtrace'].size > 10).to be_true
|
63
|
+
expect(payload['backtrace'][0..2]).to eq [{
|
64
|
+
"file" => "/Users/austin/projects/captivus/gem/spec/integration/events_from_rack_spec.rb",
|
65
|
+
"number"=> 17,
|
66
|
+
"method" => "block (2 levels) in app"
|
67
|
+
}, {
|
68
|
+
"file" => "/Users/austin/projects/captivus/gem/lib/captivus/rack_capturer.rb",
|
69
|
+
"number"=> 12,
|
70
|
+
"method" => "call"
|
71
|
+
}, {
|
72
|
+
"file" => "/Users/austin/projects/captivus/gem/lib/captivus/rack_capturer.rb",
|
73
|
+
"number"=> 12,
|
74
|
+
"method" => "call"
|
75
|
+
}]
|
76
|
+
end
|
77
|
+
|
78
|
+
Timecop.return
|
79
|
+
|
80
|
+
# possible things to care about
|
81
|
+
# "GATEWAY_INTERFACE"=>"CGI/1.1",
|
82
|
+
# "REMOTE_ADDR"=>"127.0.0.1",
|
83
|
+
# "REMOTE_HOST"=>"localhost",
|
84
|
+
# "REQUEST_URI"=>"http://localhost:9293/events",
|
85
|
+
# "SERVER_PROTOCOL"=>"HTTP/1.1",
|
86
|
+
# "SERVER_SOFTWARE"=>"WEBrick/1.3.1 (Ruby/1.9.3/2012-04-20)",
|
87
|
+
# "HTTP_CONNECTION"=>"close",
|
88
|
+
# "HTTP_HOST"=>"localhost:9293",
|
89
|
+
# "HTTP_VERSION"=>"HTTP/1.1",
|
90
|
+
# "REQUEST_PATH"=>"/events"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'sham_rack'
|
3
|
+
require 'rack/test'
|
4
|
+
require 'timecop'
|
5
|
+
require "delorean"
|
6
|
+
require_relative '../lib/captivus'
|
7
|
+
|
8
|
+
class CaptivusAPI
|
9
|
+
def call(env)
|
10
|
+
requests << env
|
11
|
+
[200, {}, ['']]
|
12
|
+
end
|
13
|
+
|
14
|
+
def requests
|
15
|
+
@requests ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def clear_requests!
|
19
|
+
@requests = []
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Helpers
|
24
|
+
def captivus_api
|
25
|
+
@captivus_api ||= CaptivusAPI.new
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
RSpec.configure do |config|
|
30
|
+
config.include Helpers
|
31
|
+
config.include Delorean
|
32
|
+
|
33
|
+
config.before do
|
34
|
+
ShamRack.mount captivus_api, "api.captiv.us"
|
35
|
+
captivus_api.clear_requests!
|
36
|
+
end
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: captivus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Austin Schneider
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-22 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: faraday
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.8.4
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.8.4
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: multi_json
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.3.6
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.3.6
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: captivus-auth_hmac
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - '='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.0.1
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.0.1
|
62
|
+
description: Captivus client gem
|
63
|
+
email:
|
64
|
+
- austinthecoder@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- LICENSE.txt
|
72
|
+
- README.md
|
73
|
+
- Rakefile
|
74
|
+
- captivus.gemspec
|
75
|
+
- lib/captivus.rb
|
76
|
+
- lib/captivus/backtrace.rb
|
77
|
+
- lib/captivus/backtrace/line.rb
|
78
|
+
- lib/captivus/configuration.rb
|
79
|
+
- lib/captivus/payload.rb
|
80
|
+
- lib/captivus/rack_capturer.rb
|
81
|
+
- lib/captivus/version.rb
|
82
|
+
- lib/faraday/request/hmac_authentication.rb
|
83
|
+
- spec/captivus/backtrace/line_spec.rb
|
84
|
+
- spec/captivus/backtrace_spec.rb
|
85
|
+
- spec/captivus/configuration_spec.rb
|
86
|
+
- spec/captivus/payload_spec.rb
|
87
|
+
- spec/captivus/rack_capturer_spec.rb
|
88
|
+
- spec/captivus_spec.rb
|
89
|
+
- spec/integration/events_from_rack_spec.rb
|
90
|
+
- spec/spec_helper.rb
|
91
|
+
homepage: http://captiv.us
|
92
|
+
licenses: []
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
none: false
|
105
|
+
requirements:
|
106
|
+
- - ! '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
requirements: []
|
110
|
+
rubyforge_project:
|
111
|
+
rubygems_version: 1.8.24
|
112
|
+
signing_key:
|
113
|
+
specification_version: 3
|
114
|
+
summary: Captivus client gem
|
115
|
+
test_files:
|
116
|
+
- spec/captivus/backtrace/line_spec.rb
|
117
|
+
- spec/captivus/backtrace_spec.rb
|
118
|
+
- spec/captivus/configuration_spec.rb
|
119
|
+
- spec/captivus/payload_spec.rb
|
120
|
+
- spec/captivus/rack_capturer_spec.rb
|
121
|
+
- spec/captivus_spec.rb
|
122
|
+
- spec/integration/events_from_rack_spec.rb
|
123
|
+
- spec/spec_helper.rb
|