airbrake_tools 1.1.6 → 1.2.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.
- 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:
|