airbrake-api 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ *.gem
5
+ pkg/*
6
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format=nested
3
+ --backtrace
@@ -0,0 +1,7 @@
1
+ rvm:
2
+ - 1.8.7
3
+ - 1.9.2
4
+ - jruby
5
+ - rbx
6
+ - ree
7
+ - ruby-head
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ platforms :jruby do
4
+ gem 'jruby-openssl', '~> 0.7'
5
+ end
6
+
7
+ gemspec
@@ -0,0 +1,80 @@
1
+ Airbrake API [![Build Status](https://secure.travis-ci.org/spagalloco/airbrake-api.png)](http://travis-ci.org/spagalloco/airbrake-api)
2
+ ======================================================================================================================================
3
+
4
+ A ruby wrapper for the [Airbrake API](http://airbrakeapp.com/pages/api)
5
+
6
+ Usage
7
+ -----
8
+
9
+ The first thing you need to set is the account name. This is the same as the web address for your account.
10
+
11
+ AirbrakeAPI.account = 'myaccount'
12
+
13
+ Then, you should set the authentication token.
14
+
15
+ AirbrakeAPI.auth_token = 'abcdefg'
16
+
17
+ If your account uses ssl then turn it on:
18
+
19
+ AirbrakeAPI.secure = true
20
+
21
+ Optionally, you can configure through a single method:
22
+
23
+ AirbrakeAPI.configure(:account => 'anapp', :auth_token => 'abcdefg', :secure => true)
24
+
25
+ Once you've configured authentication, you can make calls against the API. If no token or authentication is given, an AirbrakeError exception will be raised.
26
+
27
+ Finding Errors
28
+ --------------
29
+
30
+ Errors are paginated, the API responds with 25 at a time, pass an optional params hash for additional pages:
31
+
32
+ AirbrakeAPI::Error.find(:all)
33
+ AirbrakeAPI::Error.find(:all, :page => 2)
34
+
35
+ To find an individual error, you can find by ID:
36
+
37
+ AirbrakeAPI::Error.find(error_id)
38
+
39
+ Find *all* notices of an error:
40
+
41
+ AirbrakeAPI::Notice.find_all_by_error_id(error_id)
42
+
43
+ Find an individual notice:
44
+
45
+ AirbrakeAPI::Notice.find(notice_id, error_id)
46
+
47
+ To resolve an error via the API:
48
+
49
+ AirbrakeAPI::Error.update(1696170, :group => { :resolved => true})
50
+
51
+ Recreate an error:
52
+
53
+ STDOUT.sync = true
54
+ AirbrakeAPI::Notice.find_all_by_error_id(error_id) do |batch|
55
+ batch.each do |notice|
56
+ result = system "curl --silent '#{notice.request.url}' > /dev/null"
57
+ print (result ? '.' : 'F')
58
+ end
59
+ end
60
+
61
+ Projects
62
+ --------
63
+
64
+ To retrieve a list of projects:
65
+
66
+ AirbrakeAPI::Project.find(:all)
67
+
68
+ Responses
69
+ ---------
70
+
71
+ If an error is returned from the API, an AirbrakeError will be raised. Successful responses will return a Hashie::Mash object based on the data from the response.
72
+
73
+
74
+ Contributors
75
+ ------------
76
+
77
+ * [Matias Käkelä](https://github.com/massive) - SSL Support
78
+ * [Jordan Brough](https://github.com/jordan-brough) - Notices
79
+ * [Michael Grosser](https://github.com/grosser) - Numerous performance improvements and bug fixes
80
+ * [Brad Greenlee](https://github.com/bgreenlee) - Switch from Hoptoad to Airbrake
@@ -0,0 +1,19 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
9
+ namespace :doc do
10
+ require 'yard'
11
+ YARD::Rake::YardocTask.new do |task|
12
+ task.files = ['lib/**/*.rb']
13
+ task.options = [
14
+ '--protected',
15
+ '--output-dir', 'doc/yard',
16
+ '--markup', 'markdown',
17
+ ]
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "airbrake-api/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'airbrake-api'
7
+ s.version = AirbrakeAPI::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+
10
+ s.summary = "A ruby wrapper for the Airbrake API"
11
+ s.description = "A ruby wrapper for the Airbrake API"
12
+
13
+ s.authors = ['Steve Agalloco']
14
+ s.email = ['steve.agalloco@gmail.com']
15
+ s.homepage = 'https://github.com/spagalloco/airbrake-api'
16
+
17
+ s.add_dependency 'httparty', '~> 0.8.0'
18
+ s.add_dependency 'hashie', '~> 1.1.0'
19
+ s.add_dependency 'parallel', '~> 0.5.0'
20
+
21
+ s.add_development_dependency 'rake', '~> 0.9.2'
22
+ s.add_development_dependency 'rspec', '~> 2.6.0'
23
+ s.add_development_dependency 'yard', '~> 0.7.2'
24
+ s.add_development_dependency 'maruku', '~> 0.6'
25
+ s.add_development_dependency 'simplecov', '~> 0.4.2'
26
+ s.add_development_dependency 'fakeweb', '~> 1.3.0'
27
+ s.add_development_dependency 'nokogiri', '~> 1.4'
28
+ s.add_development_dependency 'airbrake', '~> 3.0'
29
+ s.add_development_dependency 'i18n', '~> 0.6.0'
30
+
31
+ # ensure the gem is built out of versioned files
32
+ s.files = `git ls-files`.split("\n")
33
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
34
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
35
+ s.require_paths = ["lib"]
36
+ end
@@ -0,0 +1,30 @@
1
+ require 'hashie'
2
+ require 'httparty'
3
+
4
+ module AirbrakeAPI
5
+ extend self
6
+ attr_accessor :account, :auth_token, :secure
7
+
8
+ class AirbrakeError < StandardError; end
9
+
10
+ def configure(options={})
11
+ @account = options[:account] if options.has_key?(:account)
12
+ @auth_token = options[:auth_token] if options.has_key?(:auth_token)
13
+ @secure = options[:secure] if options.has_key?(:secure)
14
+ end
15
+
16
+ def account_path
17
+ "#{protocol}://#{@account}.airbrakeapp.com"
18
+ end
19
+
20
+ def protocol
21
+ secure ? "https" : "http"
22
+ end
23
+
24
+ end
25
+
26
+ require 'airbrake-api/core_extensions'
27
+ require 'airbrake-api/client'
28
+ require 'airbrake-api/error'
29
+ require 'airbrake-api/notice'
30
+ require 'airbrake-api/project'
@@ -0,0 +1,41 @@
1
+ module AirbrakeAPI
2
+ class Base
3
+ include HTTParty
4
+ format :xml
5
+
6
+ private
7
+
8
+ def self.setup
9
+ base_uri AirbrakeAPI.account_path
10
+ default_params :auth_token => AirbrakeAPI.auth_token
11
+
12
+ check_configuration
13
+ end
14
+
15
+ def self.check_configuration
16
+ raise AirbrakeError.new('API Token cannot be nil') if default_options.nil? || default_options[:default_params].nil? || !default_options[:default_params].has_key?(:auth_token)
17
+ raise AirbrakeError.new('Account cannot be nil') unless default_options.has_key?(:base_uri)
18
+ end
19
+
20
+ def self.fetch(path, options)
21
+ response = get(path, { :query => options })
22
+ if response.code == 403
23
+ raise AirbrakeError.new('SSL should be enabled - use AirbrakeAPI.secure = true in configuration')
24
+ end
25
+
26
+ Hashie::Mash.new(response)
27
+ end
28
+
29
+ end
30
+ end
31
+
32
+ # airbrake sometimes returns broken xml with invalid xml tag names
33
+ # so we remove them
34
+ require 'httparty/parser'
35
+ class HTTParty::Parser
36
+ def xml
37
+ body.gsub!(/<__utmz>.*?<\/__utmz>/m,'')
38
+ body.gsub!(/<[0-9]+.*?>.*?<\/[0-9]+.*?>/m,'')
39
+ MultiXml.parse(body)
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ class Array
2
+ def extract_options!
3
+ last.is_a?(::Hash) ? pop : {}
4
+ end
5
+ end
@@ -0,0 +1,59 @@
1
+ module AirbrakeAPI
2
+ class Error < AirbrakeAPI::Base
3
+
4
+ def self.find(*args)
5
+ setup
6
+
7
+ results = case args.first
8
+ when Fixnum
9
+ find_individual(args)
10
+ when :all
11
+ find_all(args)
12
+ else
13
+ raise AirbrakeError.new('Invalid argument')
14
+ end
15
+
16
+ raise AirbrakeError.new('No results found.') if results.nil?
17
+ raise AirbrakeError.new(results.errors.error) if results.errors
18
+
19
+ results.group || results.groups
20
+ end
21
+
22
+ def self.update(error, options)
23
+ setup
24
+
25
+ response = put(error_path(error), { :query => options })
26
+ if response.code == 403
27
+ raise AirbrakeError.new('SSL should be enabled - use Airbrake.secure = true in configuration')
28
+ end
29
+ results = Hashie::Mash.new(response)
30
+
31
+ raise AirbrakeError.new(results.errors.error) if results.errors
32
+ results.group
33
+ end
34
+
35
+ private
36
+
37
+ def self.find_all(args)
38
+ options = args.extract_options!
39
+
40
+ fetch(collection_path, options)
41
+ end
42
+
43
+ def self.find_individual(args)
44
+ id = args.shift
45
+ options = args.extract_options!
46
+
47
+ fetch(error_path(id), options)
48
+ end
49
+
50
+ def self.collection_path
51
+ '/errors.xml'
52
+ end
53
+
54
+ def self.error_path(error_id)
55
+ "/errors/#{error_id}.xml"
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,66 @@
1
+ require 'parallel'
2
+
3
+ module AirbrakeAPI
4
+ class Notice < AirbrakeAPI::Base
5
+ PER_PAGE = 30
6
+ PARALLEL_WORKERS = 10
7
+
8
+ def self.find(id, error_id, options={})
9
+ setup
10
+
11
+ hash = fetch(find_path(id, error_id), options)
12
+
13
+ if hash.errors
14
+ raise AirbrakeError.new(results.errors.error)
15
+ end
16
+
17
+ hash.notice
18
+ end
19
+
20
+ def self.find_all_by_error_id(error_id, notice_options = {})
21
+ setup
22
+
23
+ options = {}
24
+ notices = []
25
+ page = 1
26
+ while !notice_options[:pages] || page <= notice_options[:pages]
27
+ options[:page] = page
28
+ hash = fetch(all_path(error_id), options)
29
+ if hash.errors
30
+ raise AirbrakeError.new(results.errors.error)
31
+ end
32
+
33
+ batch = Parallel.map(hash.notices, :in_threads => PARALLEL_WORKERS) do |notice_stub|
34
+ find(notice_stub.id, error_id)
35
+ end
36
+ yield batch if block_given?
37
+ batch.each{|n| notices << n }
38
+
39
+ break if batch.size < PER_PAGE
40
+ page += 1
41
+ end
42
+ notices
43
+ end
44
+
45
+ def self.find_by_error_id(error_id, options={ 'page' => 1})
46
+ setup
47
+
48
+ hash = fetch(all_path(error_id), options)
49
+ if hash.errors
50
+ raise AirbrakeError.new(results.errors.error)
51
+ end
52
+
53
+ hash.notices
54
+ end
55
+
56
+ private
57
+
58
+ def self.find_path(id, error_id)
59
+ "/errors/#{error_id}/notices/#{id}.xml"
60
+ end
61
+
62
+ def self.all_path(error_id)
63
+ "/errors/#{error_id}/notices.xml"
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,19 @@
1
+ module AirbrakeAPI
2
+ class Project < AirbrakeAPI::Base
3
+
4
+ def self.find(*args)
5
+ setup
6
+ options = args.extract_options!
7
+
8
+ results = fetch(collection_path, options)
9
+
10
+ raise AirbrakeError.new(results.errors.error) if results.errors
11
+ results.projects.project
12
+ end
13
+
14
+ def self.collection_path
15
+ '/data_api/v1/projects.xml'
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module AirbrakeAPI
2
+ VERSION = '3.2.0'
3
+ end
@@ -0,0 +1 @@
1
+ require 'airbrake-api'
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe AirbrakeAPI::Error do
4
+ before(:all) do
5
+ AirbrakeAPI.account = 'myapp'
6
+ AirbrakeAPI.auth_token = 'abcdefg123456'
7
+ AirbrakeAPI.secure = false
8
+ end
9
+
10
+ it "should have correct collection path" do
11
+ AirbrakeAPI::Error.collection_path.should == "/errors.xml"
12
+ end
13
+
14
+ it "should generate correct error path given an id" do
15
+ AirbrakeAPI::Error.error_path(1234).should == "/errors/1234.xml"
16
+ end
17
+
18
+ describe '.find' do
19
+ it "should find a page of the 30 most recent errors" do
20
+ errors = AirbrakeAPI::Error.find(:all)
21
+ ordered = errors.sort_by(&:most_recent_notice_at).reverse
22
+ ordered.should == errors
23
+ errors.size.should == 30
24
+ end
25
+
26
+ it "should paginate errors" do
27
+ errors = AirbrakeAPI::Error.find(:all, :page => 2)
28
+ ordered = errors.sort_by(&:most_recent_notice_at).reverse
29
+ ordered.should == errors
30
+ errors.size.should == 2
31
+ end
32
+
33
+ it "should find an individual error" do
34
+ error = AirbrakeAPI::Error.find(1696170)
35
+ error.action.should == 'index'
36
+ error.id.should == 1696170
37
+ end
38
+
39
+ it "should raise an error when not passed an id" do
40
+ lambda do
41
+ AirbrakeAPI::Error.find
42
+ end.should raise_error(AirbrakeAPI::AirbrakeError)
43
+ end
44
+ end
45
+
46
+ describe '.update' do
47
+ it 'should update the status of an error' do
48
+ error = AirbrakeAPI::Error.update(1696170, :group => { :resolved => true})
49
+ error.resolved.should be_true
50
+ end
51
+ end
52
+
53
+ end