airbrake-api 3.3.0 → 4.0.0

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/.travis.yml CHANGED
@@ -1,7 +1,13 @@
1
+ language: ruby
2
+ matrix:
3
+ allow_failures:
4
+ - rvm:
5
+ - rbx-18mode
6
+ - rbx-19mode
7
+ - ruby-head
1
8
  rvm:
2
9
  - 1.8.7
3
10
  - 1.9.2
11
+ - 1.9.3
4
12
  - jruby
5
- - rbx
6
13
  - ree
7
- - ruby-head
data/README.md CHANGED
@@ -1,78 +1,102 @@
1
- Airbrake API [![Build Status](https://secure.travis-ci.org/spagalloco/airbrake-api.png)](http://travis-ci.org/spagalloco/airbrake-api)
2
- ======================================================================================================================================
1
+ # Airbrake API [![Build Status](https://secure.travis-ci.org/spagalloco/airbrake-api.png)](http://travis-ci.org/spagalloco/airbrake-api)
3
2
 
4
- A ruby wrapper for the [Airbrake API](http://airbrakeapp.com/pages/api)
3
+ A ruby client for the [Airbrake API](http://help.airbrake.io/kb/api-2/api-overview)
5
4
 
6
- Usage
7
- -----
5
+ ## Changes in 4.0
8
6
 
9
- The first thing you need to set is the account name. This is the same as the web address for your account.
7
+ AirbrakeAPI has been completely rewritten in 4.0. Why the high version number?
8
+ This was the first gem I ever wrote and it's wandered a path that started with
9
+ ActiveResource, followed by HTTParty, and now Faraday. Along the way, AirbrakeAPI
10
+ has lost it's ActiveRecord-like syntax for a more concise and simple API. Instead
11
+ of using classes such as `AirbrakeAPI::Error`, the entire API is contained within
12
+ `AirbrakeAPI::Client`.
10
13
 
11
- AirbrakeAPI.account = 'myaccount'
14
+ The following classes are now deprecated:
12
15
 
13
- Then, you should set the authentication token.
16
+ * `AirbrakeAPI::Error`
17
+ * `AirbrakeAPI::Notice`
18
+ * `AirbrakeAPI::Project`
14
19
 
15
- AirbrakeAPI.auth_token = 'abcdefg'
20
+ While your code will continue to work using the old API, they will ultimately be removed in favor of `AirbrakeAPI::Client`.
16
21
 
17
- If your account uses ssl then turn it on:
22
+ ## Configuration
18
23
 
19
- AirbrakeAPI.secure = true
20
-
21
- Optionally, you can configure through a single method:
24
+ AirbrakeAPI can be configured by passing a hash to the configure method:
22
25
 
23
26
  AirbrakeAPI.configure(:account => 'anapp', :auth_token => 'abcdefg', :secure => true)
24
27
 
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.
28
+ or via a block:
29
+
30
+ AirbrakeAPI.configure do |config|
31
+ config.account = 'anapp'
32
+ config.auth_token = 'abcdefg'
33
+ config.secure = true
34
+ end
26
35
 
27
- Finding Errors
28
- --------------
36
+ ## Finding Errors
29
37
 
30
38
  Errors are paginated, the API responds with 25 at a time, pass an optional params hash for additional pages:
31
39
 
32
- AirbrakeAPI::Error.find(:all)
33
- AirbrakeAPI::Error.find(:all, :page => 2)
40
+ AirbrakeAPI.errors
41
+ AirbrakeAPI.errors(:page => 2)
34
42
 
35
43
  To find an individual error, you can find by ID:
36
44
 
37
- AirbrakeAPI::Error.find(error_id)
45
+ AirbrakeAPI.error(error_id)
46
+
47
+
48
+ ## Finding Notices
38
49
 
39
50
  Find *all* notices of an error:
40
51
 
41
- AirbrakeAPI::Notice.find_all_by_error_id(error_id)
52
+ AirbrakeAPI.notices(error_id)
42
53
 
43
54
  Find an individual notice:
44
55
 
45
- AirbrakeAPI::Notice.find(notice_id, error_id)
56
+ AirbrakeAPI.notice(notice_id, error_id)
46
57
 
47
58
  To resolve an error via the API:
48
59
 
49
- AirbrakeAPI::Error.update(1696170, :group => { :resolved => true})
60
+ AirbrakeAPI.update(1696170, :group => { :resolved => true})
50
61
 
51
62
  Recreate an error:
52
63
 
53
64
  STDOUT.sync = true
54
- AirbrakeAPI::Notice.find_all_by_error_id(error_id) do |batch|
65
+ AirbrakeAPI.notices(error_id) do |batch|
55
66
  batch.each do |notice|
56
67
  result = system "curl --silent '#{notice.request.url}' > /dev/null"
57
68
  print (result ? '.' : 'F')
58
69
  end
59
70
  end
60
71
 
61
- Projects
62
- --------
72
+ ## Projects
63
73
 
64
74
  To retrieve a list of projects:
65
75
 
66
- AirbrakeAPI::Project.find(:all)
76
+ AirbrakeAPI.projects
77
+
78
+ ## Deployments
79
+
80
+ To retrieve a list of deployments:
81
+
82
+ AirbrakeAPI.deployments
83
+
84
+ ## Connecting to more than one account
85
+
86
+ While module-based configuration will work in most cases, if you'd like to simultaneously connect to more than one account at once, you can create instances of `AirbrakeAPI::Client` to do so:
67
87
 
68
- Responses
69
- ---------
88
+ client = AirbrakeAPI::Client.new(:account => 'myaccount', :auth_token => 'abcdefg', :secure => true)
89
+ altclient = AirbrakeAPI::Client.new(:account => 'anotheraccount', :auth_token => '123456789', :secure => false)
90
+
91
+ client.errors
92
+
93
+ altclient.projects
70
94
 
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.
95
+ ## Responses
72
96
 
97
+ If an error is returned from the API, an AirbrakeError exception will be raised. Successful responses will return a Hashie::Mash object based on the data from the response.
73
98
 
74
- Contributors
75
- ------------
99
+ ## Contributors
76
100
 
77
101
  * [Matias Käkelä](https://github.com/massive) - SSL Support
78
102
  * [Jordan Brough](https://github.com/jordan-brough) - Notices
data/airbrake-api.gemspec CHANGED
@@ -14,9 +14,11 @@ Gem::Specification.new do |s|
14
14
  s.email = ['steve.agalloco@gmail.com']
15
15
  s.homepage = 'https://github.com/spagalloco/airbrake-api'
16
16
 
17
- s.add_dependency 'httparty', '~> 0.8.0'
18
17
  s.add_dependency 'hashie', '~> 1.2'
19
18
  s.add_dependency 'parallel', '~> 0.5.0'
19
+ s.add_dependency 'faraday_middleware', '~> 0.8'
20
+ s.add_dependency 'multi_xml', '~> 0.4'
21
+ s.add_dependency 'mash'
20
22
 
21
23
  s.add_development_dependency 'rake', '~> 0.9.2'
22
24
  s.add_development_dependency 'rspec', '~> 2.6.0'
data/lib/airbrake-api.rb CHANGED
@@ -1,29 +1,11 @@
1
- require 'hashie'
2
- require 'httparty'
1
+ require 'airbrake-api/configuration'
3
2
 
4
3
  module AirbrakeAPI
5
- extend self
6
- attr_accessor :account, :auth_token, :secure
4
+ extend Configuration
7
5
 
8
6
  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}.airbrake.io"
18
- end
19
-
20
- def protocol
21
- secure ? "https" : "http"
22
- end
23
-
24
7
  end
25
8
 
26
- require 'airbrake-api/core_extensions'
27
9
  require 'airbrake-api/client'
28
10
  require 'airbrake-api/error'
29
11
  require 'airbrake-api/notice'
@@ -0,0 +1,17 @@
1
+ # placeholder since pre-refactor classes used AirbrakeAPI::Base
2
+ module AirbrakeAPI
3
+ class Base
4
+ class << self
5
+ def setup
6
+ end
7
+
8
+ def fetch(path, options)
9
+ Client.new.get(path, options)
10
+ end
11
+
12
+ def deprecate(msg)
13
+ Kernel.warn("[Deprecation] - #{msg}")
14
+ end
15
+ end
16
+ end
17
+ end
@@ -1,41 +1,164 @@
1
+ require 'faraday_middleware'
2
+ require 'airbrake-api/middleware/scrub_response'
3
+ require 'airbrake-api/middleware/raise_server_error'
4
+ require 'airbrake-api/middleware/raise_response_error'
5
+
1
6
  module AirbrakeAPI
2
- class Base
3
- include HTTParty
4
- format :xml
7
+ class Client
5
8
 
6
- private
9
+ PER_PAGE = 30
10
+ PARALLEL_WORKERS = 10
7
11
 
8
- def self.setup
9
- base_uri AirbrakeAPI.account_path
10
- default_params :auth_token => AirbrakeAPI.auth_token
12
+ attr_accessor *AirbrakeAPI::Configuration::VALID_OPTIONS_KEYS
11
13
 
12
- check_configuration
14
+ def initialize(options={})
15
+ attrs = AirbrakeAPI.options.merge(options)
16
+ AirbrakeAPI::Configuration::VALID_OPTIONS_KEYS.each do |key|
17
+ send("#{key}=", attrs[key])
18
+ end
13
19
  end
14
20
 
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)
21
+ # deploys
22
+
23
+ def deploys(project_id, options = {})
24
+ results = request(:get, deploys_path(project_id), options)
25
+ results.projects.respond_to?(:deploy) ? results.projects.try(:deploy) : []
26
+ end
27
+
28
+ def deploys_path(project_id)
29
+ "/projects/#{project_id}/deploys.xml"
30
+ end
31
+
32
+ # projects
33
+ def projects_path
34
+ '/data_api/v1/projects.xml'
35
+ end
36
+
37
+ def projects(options = {})
38
+ results = request(:get, projects_path, options)
39
+ results.projects.project
40
+ end
41
+
42
+ # errors
43
+
44
+ def error_path(error_id)
45
+ "/errors/#{error_id}.xml"
18
46
  end
19
47
 
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')
48
+ def errors_path
49
+ '/errors.xml'
50
+ end
51
+
52
+ def update(error, options = {})
53
+ results = request(:put, error_path(error), :body => options)
54
+ results.group
55
+ end
56
+
57
+ def error(error_id, options = {})
58
+ results = request(:get, error_path(error_id), options)
59
+ results.group || results.groups
60
+ end
61
+
62
+ def errors(options = {})
63
+ results = request(:get, errors_path, options)
64
+ results.group || results.groups
65
+ end
66
+
67
+ # notices
68
+
69
+ def notice_path(notice_id, error_id)
70
+ "/errors/#{error_id}/notices/#{notice_id}.xml"
71
+ end
72
+
73
+ def notices_path(error_id)
74
+ "/errors/#{error_id}/notices.xml"
75
+ end
76
+
77
+ def notice(notice_id, error_id, options = {})
78
+ hash = request(:get, notice_path(notice_id, error_id), options)
79
+ hash.notice
80
+ end
81
+
82
+ def notices(error_id, notice_options = {}, &block)
83
+ options = {}
84
+ notices = []
85
+ page = notice_options[:page]
86
+ page_count = 0
87
+
88
+ # a specific page is requested, only return that page
89
+ # if no page is specified, start on page 1
90
+ if page
91
+ notice_options[:pages] = 1
92
+ else
93
+ page = 1
24
94
  end
25
95
 
26
- Hashie::Mash.new(response)
96
+ while !notice_options[:pages] || (page_count + 1) <= notice_options[:pages]
97
+ options[:page] = page + page_count
98
+ hash = request(:get, notices_path(error_id), options)
99
+
100
+ batch = Parallel.map(hash.notices, :in_threads => PARALLEL_WORKERS) do |notice_stub|
101
+ notice(notice_stub.id, error_id)
102
+ end
103
+ yield batch if block_given?
104
+ batch.each{|n| notices << n }
105
+
106
+ break if batch.size < PER_PAGE
107
+ page_count += 1
108
+ end
109
+ notices
27
110
  end
28
111
 
29
- end
30
- end
112
+ private
113
+
114
+ def account_path
115
+ "#{protocol}://#{@account}.airbrake.io"
116
+ end
117
+
118
+ def protocol
119
+ @secure ? "https" : "http"
120
+ end
121
+
122
+ # Perform an HTTP request
123
+ def request(method, path, params = {}, options = {})
124
+
125
+ raise AirbrakeError.new('API Token cannot be nil') if @auth_token.nil?
126
+ raise AirbrakeError.new('Account cannot be nil') if @account.nil?
127
+
128
+ params.merge!(:auth_token => @auth_token)
129
+
130
+ response = connection(options).run_request(method, nil, nil, nil) do |request|
131
+ case method.to_sym
132
+ when :delete, :get
133
+ request.url(path, params)
134
+ when :post, :put
135
+ request.path = path
136
+ request.body = params unless params.empty?
137
+ end
138
+ end
139
+ response.body
140
+ end
141
+
142
+ def connection(options={})
143
+ default_options = {
144
+ :headers => {
145
+ :accept => 'application/xml',
146
+ :user_agent => user_agent,
147
+ },
148
+ :ssl => {:verify => false},
149
+ :url => account_path,
150
+ }
151
+ @connection ||= Faraday.new(default_options.deep_merge(connection_options)) do |builder|
152
+ builder.use Faraday::Request::UrlEncoded
153
+ builder.use AirbrakeAPI::Middleware::RaiseResponseError
154
+ builder.use FaradayMiddleware::Mashify
155
+ builder.use FaradayMiddleware::ParseXml
156
+ builder.use AirbrakeAPI::Middleware::ScrubResponse
157
+ builder.use AirbrakeAPI::Middleware::RaiseServerError
158
+
159
+ builder.adapter adapter
160
+ end
161
+ end
31
162
 
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
163
  end
41
164
  end
@@ -0,0 +1,55 @@
1
+ require 'airbrake-api/version'
2
+
3
+ module AirbrakeAPI
4
+ module Configuration
5
+ VALID_OPTIONS_KEYS = [
6
+ :account,
7
+ :auth_token,
8
+ :secure,
9
+ :connection_options,
10
+ :adapter,
11
+ :user_agent]
12
+
13
+ attr_accessor *VALID_OPTIONS_KEYS
14
+
15
+ DEFAULT_ADAPTER = :net_http
16
+ DEFAULT_USER_AGENT = "AirbrakeAPI Ruby Gem #{AirbrakeAPI::VERSION}"
17
+ DEFAULT_CONNECTION_OPTIONS = {}
18
+
19
+ def self.extended(base)
20
+ base.reset
21
+ end
22
+
23
+ def configure(options={})
24
+ @account = options[:account] if options.has_key?(:account)
25
+ @auth_token = options[:auth_token] if options.has_key?(:auth_token)
26
+ @secure = options[:secure] if options.has_key?(:secure)
27
+ yield self if block_given?
28
+ self
29
+ end
30
+
31
+ def options
32
+ options = {}
33
+ VALID_OPTIONS_KEYS.each{|k| options[k] = send(k)}
34
+ options
35
+ end
36
+
37
+ def account_path
38
+ "#{protocol}://#{@account}.airbrake.io"
39
+ end
40
+
41
+ def protocol
42
+ @secure ? "https" : "http"
43
+ end
44
+
45
+ def reset
46
+ @account = nil
47
+ @auth_token = nil
48
+ @secure = false
49
+ @adapter = DEFAULT_ADAPTER
50
+ @user_agent = DEFAULT_USER_AGENT
51
+ @connection_options = DEFAULT_CONNECTION_OPTIONS
52
+ end
53
+
54
+ end
55
+ end