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 +4 -4
- data/Gemfile.lock +5 -15
- data/airbrake_tools.gemspec +2 -1
- data/lib/airbrake_tools.rb +99 -36
- data/lib/airbrake_tools/version.rb +1 -1
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 36fac1e64067233ec999c77a709664e713fb3d1e
|
4
|
+
data.tar.gz: a4ab31edbfca13e3c5bda4a7f5e4bce73662db05
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91ca4a6fd975468cce389a972ce4823d0e1011d6165e49abc14edbd9adcd418051e98d17df9f8fb5c44dc40528d371948a801e033a72ef3e1083ca2c9e8d4e84
|
7
|
+
data.tar.gz: 0ebcf50c02b4652fb2da1e4f834ff00fc7c9ec5ac0ea72f9b4238ad7ba81b15a6069af1436fe8b50adfaadb64928c73262d9a76c30fbed24d6b4c53ac6d5a7c4
|
data/Gemfile.lock
CHANGED
@@ -1,18 +1,14 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
airbrake_tools (1.
|
5
|
-
|
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
|
-
|
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
|
-
|
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)
|
data/airbrake_tools.gemspec
CHANGED
@@ -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 "
|
14
|
+
s.add_runtime_dependency "json"
|
15
|
+
s.add_runtime_dependency "parallel"
|
15
16
|
end
|
data/lib/airbrake_tools.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
1
|
require "airbrake_tools/version"
|
2
|
-
require "
|
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
|
-
|
24
|
-
|
25
|
-
|
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...
|
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 =
|
73
|
-
|
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 =
|
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
|
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
|
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
|
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 =
|
180
|
+
notices = notices_from_pages(project_id, error.id, pages).compact
|
182
181
|
print "."
|
183
|
-
[error, notices, frequency(notices, pages *
|
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(
|
192
|
+
errors.concat(airbrake_errors(options[:project_id], i+1, options))
|
196
193
|
end
|
197
|
-
|
194
|
+
errors
|
198
195
|
end
|
199
196
|
|
200
|
-
def
|
201
|
-
|
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
|
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.
|
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-
|
11
|
+
date: 2015-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: json
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
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:
|
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:
|