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