airbrake_tools 1.1.6 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f6e685b460c2432e490b9a535f28aba359672c81
4
- data.tar.gz: a1e69905a9b4534d24bf595cbada9ac18cc1a103
3
+ metadata.gz: 36fac1e64067233ec999c77a709664e713fb3d1e
4
+ data.tar.gz: a4ab31edbfca13e3c5bda4a7f5e4bce73662db05
5
5
  SHA512:
6
- metadata.gz: 661d9e8ab2d8fcd3fa17896c31a32ea1af4e852f2551f2748fa0ca2e381569f9002dad7133f3b41bc5ddfa04aea07d5f504a8145df1b3efeb6b8d22bcb485555
7
- data.tar.gz: 71a7e83db45ae534f9e034821b4ded8d3e689e458112488adca1d4c113eb7b3fcef3042ec9674d6487438323e18793e3945a67a54266e3ef6601500c81043c02
6
+ metadata.gz: 91ca4a6fd975468cce389a972ce4823d0e1011d6165e49abc14edbd9adcd418051e98d17df9f8fb5c44dc40528d371948a801e033a72ef3e1083ca2c9e8d4e84
7
+ data.tar.gz: 0ebcf50c02b4652fb2da1e4f834ff00fc7c9ec5ac0ea72f9b4238ad7ba81b15a6069af1436fe8b50adfaadb64928c73262d9a76c30fbed24d6b4c53ac6d5a7c4
@@ -1,18 +1,14 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- airbrake_tools (1.1.6)
5
- airbrake-api (>= 4.5.1)
4
+ airbrake_tools (1.2.0)
5
+ json
6
+ parallel
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
11
  addressable (2.3.6)
11
- airbrake-api (4.6.0)
12
- faraday_middleware
13
- hashie
14
- multi_xml
15
- parallel
16
12
  bump (0.3.8)
17
13
  byebug (3.5.1)
18
14
  columnize (~> 0.8)
@@ -21,16 +17,10 @@ GEM
21
17
  columnize (0.8.9)
22
18
  debugger-linecache (1.2.0)
23
19
  diff-lcs (1.1.3)
24
- faraday (0.9.1)
25
- multipart-post (>= 1.2, < 3)
26
- faraday_middleware (0.9.1)
27
- faraday (>= 0.7.4, < 0.10)
28
- hashie (3.4.0)
20
+ json (1.8.2)
29
21
  launchy (2.4.2)
30
22
  addressable (~> 2.3)
31
- multi_xml (0.5.5)
32
- multipart-post (2.0.0)
33
- parallel (1.4.1)
23
+ parallel (1.6.0)
34
24
  rake (0.9.2.2)
35
25
  rspec (2.11.0)
36
26
  rspec-core (~> 2.11.0)
@@ -11,5 +11,6 @@ Gem::Specification.new name, AirbrakeTools::VERSION do |s|
11
11
  s.bindir = 'bin'
12
12
  s.license = "MIT"
13
13
  s.executables = ["airbrake-tools"]
14
- s.add_runtime_dependency "airbrake-api", ">= 4.5.1"
14
+ s.add_runtime_dependency "json"
15
+ s.add_runtime_dependency "parallel"
15
16
  end
@@ -1,13 +1,19 @@
1
1
  require "airbrake_tools/version"
2
- require "airbrake-api"
2
+ require "json"
3
+ require "ostruct"
4
+ require "net/http"
5
+ require "net/https"
6
+ require "time"
7
+ require "parallel"
3
8
 
4
9
  module AirbrakeTools
5
10
  DEFAULT_HOT_PAGES = 1
6
11
  DEFAULT_NEW_PAGES = 1
7
- DEFAULT_LIST_PAGES = 10
8
- DEFAULT_SUMMARY_PAGES = 10
12
+ DEFAULT_LIST_PAGES = 1 # TODO 10 once pagination is not broken :/
13
+ DEFAULT_SUMMARY_PAGES = 1 # TODO 10 once pagination is not broken :/
9
14
  DEFAULT_COMPARE_DEPTH_ADDITION = 3 # first line in project is 6 -> compare at 6 + x depth
10
15
  DEFAULT_ENVIRONMENT = "production"
16
+ PER_PAGE = 20
11
17
  COLORS = {
12
18
  :gray => "\e[0;37m",
13
19
  :green => "\e[0;32m",
@@ -20,17 +26,15 @@ module AirbrakeTools
20
26
  def cli(argv)
21
27
  options = extract_options(argv)
22
28
 
23
- AirbrakeAPI.account = ARGV[0]
24
- AirbrakeAPI.auth_token = ARGV[1]
25
- AirbrakeAPI.secure = true
26
-
27
- options[:project_id] = project_id(options.delete(:project_name)) if options[:project_name]
28
-
29
- if AirbrakeAPI.account.to_s.empty? || AirbrakeAPI.auth_token.to_s.empty?
29
+ # TODO get rid of argument 0
30
+ @token = ARGV[1]
31
+ if @token.to_s.empty?
30
32
  puts "Usage instructions: airbrake-tools --help"
31
33
  return 1
32
34
  end
33
35
 
36
+ options[:project_id] = project_id(options.delete(:project_name)) if options[:project_name]
37
+
34
38
  case ARGV[2]
35
39
  when "hot"
36
40
  print_errors(hot(options))
@@ -52,7 +56,7 @@ module AirbrakeTools
52
56
  errors = Array(options[:project_id] || projects.map(&:id)).flat_map do |project_id|
53
57
  errors_with_notices({pages: DEFAULT_HOT_PAGES, project_id: project_id}.merge(options))
54
58
  end
55
- errors.sort_by{|_,_,f| f }.reverse[0...AirbrakeAPI::Client::PER_PAGE]
59
+ errors.sort_by{|_,_,f| f }.reverse[0...PER_PAGE]
56
60
  end
57
61
 
58
62
  def new(options = {})
@@ -62,15 +66,15 @@ module AirbrakeTools
62
66
  end
63
67
 
64
68
  def errors_with_notices(options)
65
- add_notices_to_pages(errors_from_pages(options))
69
+ add_notices_to_pages(options.fetch(:project_id), errors_from_pages(options))
66
70
  end
67
71
 
68
72
  def list(options)
69
73
  need_project_id!(options)
70
74
  list_pages = (options[:pages] ? options[:pages] : DEFAULT_LIST_PAGES)
71
75
  page = 1
72
- while page <= list_pages && errors = AirbrakeAPI.errors(page: page, project_id: options.fetch(:project_id))
73
- select_env(errors, options).each do |error|
76
+ while page <= list_pages && errors = airbrake_errors(options.fetch(:project_id), page, options)
77
+ errors.each do |error|
74
78
  puts "#{error.id} -- #{error.error_class} -- #{error.error_message} -- #{error.created_at}"
75
79
  end
76
80
  $stderr.puts "Page #{page} ----------\n"
@@ -79,7 +83,7 @@ module AirbrakeTools
79
83
  end
80
84
 
81
85
  def summary(error_id, options)
82
- notices = AirbrakeAPI.notices(error_id, :pages => options[:pages] || DEFAULT_SUMMARY_PAGES)
86
+ notices = notices_from_pages(options.fetch(:project_id), error_id, options[:pages] || DEFAULT_SUMMARY_PAGES)
83
87
 
84
88
  puts "last retrieved notice: #{((Time.now - notices.last.created_at) / (60 * 60)).round} hours ago at #{notices.last.created_at}"
85
89
  puts "last 2 hours: #{sparkline(notices, :slots => 60, :interval => 120)}"
@@ -143,25 +147,20 @@ module AirbrakeTools
143
147
  end
144
148
 
145
149
  def grouped_backtraces(notices, options)
146
- notices = notices.compact.select { |n| backtrace(n) }
150
+ notices = notices.compact.select { |n| n.backtrace.any? }
147
151
 
148
152
  compare_depth = if options[:compare_depth]
149
153
  options[:compare_depth]
150
154
  else
151
- average_first_project_line(notices.map { |n| backtrace(n) }) +
155
+ average_first_project_line(notices.map { |n| n.backtrace }) +
152
156
  DEFAULT_COMPARE_DEPTH_ADDITION
153
157
  end
154
158
 
155
159
  notices.group_by do |notice|
156
- backtrace(notice)[0..compare_depth]
160
+ notice.backtrace[0..compare_depth]
157
161
  end
158
162
  end
159
163
 
160
- def backtrace(notice)
161
- return if notice.backtrace.is_a?(String)
162
- [*notice.backtrace.first[1]] # can be string or array
163
- end
164
-
165
164
  def average_first_project_line(backtraces)
166
165
  depths = backtraces.map do |backtrace|
167
166
  backtrace.index { |line| custom_file?(line) }
@@ -174,15 +173,13 @@ module AirbrakeTools
174
173
  line.start_with?("[PROJECT_ROOT]") && !line.start_with?("[PROJECT_ROOT]/vendor/")
175
174
  end
176
175
 
177
- def add_notices_to_pages(errors)
176
+ def add_notices_to_pages(project_id, errors)
178
177
  Parallel.map(errors, :in_threads => 10) do |error|
179
178
  begin
180
179
  pages = 1
181
- notices = AirbrakeAPI.notices(error.id, pages: pages, raw: true).compact
180
+ notices = notices_from_pages(project_id, error.id, pages).compact
182
181
  print "."
183
- [error, notices, frequency(notices, pages * AirbrakeAPI::Client::PER_PAGE)]
184
- rescue Faraday::Error::ParsingError
185
- $stderr.puts "Ignoring #{hot_summary(error)}, got 500 from http://#{AirbrakeAPI.account}.airbrake.io/errors/#{error.id}"
182
+ [error, notices, frequency(notices, pages * PER_PAGE)]
186
183
  rescue Exception => e
187
184
  puts "Ignoring exception <<#{e}>>, most likely bad data from airbrake"
188
185
  end
@@ -192,13 +189,17 @@ module AirbrakeTools
192
189
  def errors_from_pages(options)
193
190
  errors = []
194
191
  options[:pages].times do |i|
195
- errors.concat(AirbrakeAPI.errors(:page => i+1, :project_id => options[:project_id]) || [])
192
+ errors.concat(airbrake_errors(options[:project_id], i+1, options))
196
193
  end
197
- select_env(errors, options)
194
+ errors
198
195
  end
199
196
 
200
- def select_env(errors, options)
201
- errors.select{|e| e.rails_env == (options[:env] || DEFAULT_ENVIRONMENT) }
197
+ def notices_from_pages(project_id, error_id, pages)
198
+ notices = []
199
+ pages.times do |i|
200
+ notices.concat(airbrake_notices(project_id, error_id, i+1))
201
+ end
202
+ notices
202
203
  end
203
204
 
204
205
  def print_errors(hot)
@@ -273,15 +274,77 @@ module AirbrakeTools
273
274
  `#{File.expand_path('../../spark.sh',__FILE__)} #{sparkline_data(notices, options).join(" ")}`.strip
274
275
  end
275
276
 
276
- def projects
277
- @projects ||= AirbrakeAPI.projects
278
- end
279
-
280
277
  def project_id(project_name)
281
278
  return project_name.to_i if project_name =~ /^\d+$/
282
279
  project = projects.detect { |p| p.name == project_name }
283
280
  raise "project with name #{project_name} not found try #{projects.map(&:name).join(", ")}" unless project
284
281
  project.id
285
282
  end
283
+
284
+ def projects
285
+ @projects ||= begin
286
+ response = make_request("https://airbrake.io/api/v3/projects?key=#{@token}")
287
+ case response.code.to_i
288
+ when 200..299
289
+ JSON.parse(response.body)["projects"].compact.map do |raw|
290
+ OpenStruct.new(
291
+ :id => raw["id"].to_s,
292
+ :name => raw["name"]
293
+ )
294
+ end.sort_by{|p| p[:name].to_s.downcase }
295
+ else
296
+ raise "ERROR - Bad response for http://airbrake.io/api/v3/projects - #{response.code} - #{response.message}"
297
+ end
298
+ end
299
+ end
300
+
301
+ def airbrake_errors(project_id, page, options)
302
+ response = make_request("https://airbrake.io/api/v3/projects/#{project_id}/groups?key=#{@token}&page=#{page}&environment=#{options[:env] || DEFAULT_ENVIRONMENT}&resolved=false")
303
+ case response.code.to_i
304
+ when 200..299
305
+ JSON.parse(response.body)["groups"].compact.map do |raw|
306
+ OpenStruct.new(
307
+ :id => raw["id"].to_s,
308
+ :project_id => raw["projectId"].to_s,
309
+ :env => raw["environment"],
310
+ :count => raw["noticeCount"],
311
+ :created_at => Time.parse(raw["createdAt"]),
312
+ :most_recent => Time.parse(raw["lastNoticeAt"]),
313
+ :error_message => raw["errors"][0]["message"].to_s,
314
+ :error_class => raw["errors"][0]["type"].to_s
315
+ )
316
+ end
317
+ else
318
+ puts "ERROR - Bad response for http://airbrake.io/api/v3/projects/#{project_id}/groups - #{response.code} - #{response.message}"
319
+ end
320
+ end
321
+
322
+ def airbrake_notices(project_id, error_id, page=1)
323
+ response = make_request("https://airbrake.io/api/v3/projects/#{project_id}/groups/#{error_id}/notices?key=#{@token}&page=#{page}")
324
+ case response.code.to_i
325
+ when 200..299
326
+ JSON.parse(response.body)["notices"].compact.map do |raw|
327
+ OpenStruct.new(
328
+ :id => raw["id"].to_s,
329
+ :created_at => Time.parse(raw["createdAt"]),
330
+ :message => raw["errors"][0]["message"].to_s,
331
+ :backtrace => (raw["errors"].first['backtrace'] || []).map { |l| "#{l["file"]}:#{l["line"]}" }
332
+ )
333
+ end
334
+ else
335
+ raise "ERROR - Bad response for http://airbrake.io/api/v3/projects/#{project_id}/groups/#{error_id}/notices - #{response.code} - #{response.message}"
336
+ end
337
+ end
338
+
339
+ def make_request(url)
340
+ # stolen from https://github.com/bf4/airbrake_client/blob/master/airbrake_client.rb
341
+ uri = URI(url)
342
+ http = Net::HTTP.new(uri.host, uri.port)
343
+ if http.use_ssl = (uri.scheme == 'https')
344
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
345
+ end
346
+ request = Net::HTTP::Get.new(uri.request_uri)
347
+ http.request(request)
348
+ end
286
349
  end
287
350
  end
@@ -1,3 +1,3 @@
1
1
  module AirbrakeTools
2
- VERSION = "1.1.6"
2
+ VERSION = "1.2.0"
3
3
  end
metadata CHANGED
@@ -1,29 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: airbrake_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.6
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Cheatham
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-14 00:00:00.000000000 Z
11
+ date: 2015-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: airbrake-api
14
+ name: json
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 4.5.1
19
+ version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 4.5.1
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: parallel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description:
28
42
  email: coaxis@gmail.com
29
43
  executables: