airbrake_tools 0.0.8 → 0.0.10
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/Gemfile +1 -1
- data/Gemfile.lock +3 -3
- data/Readme.md +3 -1
- data/lib/airbrake_tools/version.rb +1 -1
- data/lib/airbrake_tools.rb +64 -13
- data/spec/airbrake_tools_spec.rb +112 -44
- metadata +8 -2
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
airbrake_tools (0.0.
|
4
|
+
airbrake_tools (0.0.10)
|
5
5
|
airbrake-api (>= 4.2.2)
|
6
6
|
|
7
7
|
GEM
|
@@ -13,7 +13,7 @@ GEM
|
|
13
13
|
mash
|
14
14
|
multi_xml
|
15
15
|
parallel (~> 0.5.0)
|
16
|
-
bump (0.3.
|
16
|
+
bump (0.3.8)
|
17
17
|
diff-lcs (1.1.3)
|
18
18
|
faraday (0.8.4)
|
19
19
|
multipart-post (~> 1.1)
|
@@ -39,6 +39,6 @@ PLATFORMS
|
|
39
39
|
|
40
40
|
DEPENDENCIES
|
41
41
|
airbrake_tools!
|
42
|
-
bump
|
42
|
+
bump (= 0.3.8)
|
43
43
|
rake
|
44
44
|
rspec (~> 2)
|
data/Readme.md
CHANGED
@@ -50,6 +50,7 @@ Shows all errors divided by pages
|
|
50
50
|
|
51
51
|
- show details for 1 error (combines 150 notices)
|
52
52
|
- show all different traces that caused this error (play with --compare-depth)
|
53
|
+
- shows blame for the line if it's in the project and you are running airrake-tools from the project root
|
53
54
|
|
54
55
|
```
|
55
56
|
airbrake-tools your-account your-auth-token summary 51344729
|
@@ -63,7 +64,8 @@ Mysql2::Error: Lost connection to MySQL server at 'reading initial communication
|
|
63
64
|
|
64
65
|
Trace 2: occurred 10 times e.g. 7145613107, 7145612108
|
65
66
|
Mysql2::Error: Lost connection to MySQL server
|
66
|
-
|
67
|
+
/usr/gems/mysql2/lib/mysql2/client.rb:58:in `disconnect'
|
68
|
+
lib/foo.rb:58:in `bar' acc8204 (<jcheatham@example.com> 2012-11-06 18:45:10 -0800 )
|
67
69
|
...
|
68
70
|
|
69
71
|
Trace 3: occurred 5 times e.g. 7145609979, 7145609161
|
data/lib/airbrake_tools.rb
CHANGED
@@ -6,8 +6,12 @@ module AirbrakeTools
|
|
6
6
|
DEFAULT_HOT_PAGES = 1
|
7
7
|
DEFAULT_NEW_PAGES = 1
|
8
8
|
DEFAULT_SUMMARY_PAGES = 5
|
9
|
-
|
9
|
+
DEFAULT_COMPARE_DEPTH_ADDITION = 3 # first line in project is 6 -> compare at 6 + x depth
|
10
10
|
DEFAULT_ENVIRONMENT = "production"
|
11
|
+
COLORS = {
|
12
|
+
:gray => "\e[0;37m",
|
13
|
+
:clear => "\e[0m"
|
14
|
+
}
|
11
15
|
|
12
16
|
class << self
|
13
17
|
def cli(argv)
|
@@ -63,31 +67,78 @@ module AirbrakeTools
|
|
63
67
|
end
|
64
68
|
|
65
69
|
def summary(error_id, options)
|
66
|
-
compare_depth = options[:compare_depth] || DEFAULT_COMPARE_DEPTH
|
67
70
|
notices = AirbrakeAPI.notices(error_id, :pages => options[:pages] || DEFAULT_SUMMARY_PAGES)
|
68
71
|
|
69
72
|
puts "last retrieved notice: #{((Time.now - notices.last.created_at) / (60 * 60)).round} hours ago at #{notices.last.created_at}"
|
70
73
|
puts "last 2 hours: #{sparkline(notices, :slots => 60, :interval => 120)}"
|
71
74
|
puts "last day: #{sparkline(notices, :slots => 24, :interval => 60 * 60)}"
|
72
75
|
|
73
|
-
|
74
|
-
if notice.backtrace.is_a?(String) # no backtrace recorded...
|
75
|
-
[]
|
76
|
-
else
|
77
|
-
notice.backtrace.first[1][0..compare_depth]
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
backtraces.sort_by{|_,notices| notices.size }.reverse.each_with_index do |(backtrace, notices), index|
|
76
|
+
grouped_backtraces(notices, options).sort_by{|_,notices| notices.size }.reverse.each_with_index do |(backtrace, notices), index|
|
82
77
|
puts "Trace #{index + 1}: occurred #{notices.size} times e.g. #{notices[0..5].map(&:id).join(", ")}"
|
83
78
|
puts notices.first.error_message
|
84
|
-
puts backtrace.map{|line| line
|
79
|
+
puts backtrace.map{|line| present_line(line) }
|
85
80
|
puts ""
|
86
81
|
end
|
87
82
|
end
|
88
83
|
|
89
84
|
private
|
90
85
|
|
86
|
+
def present_line(line)
|
87
|
+
color = :gray if $stdout.tty? && !custom_file?(line)
|
88
|
+
line = line.sub("[PROJECT_ROOT]/", "")
|
89
|
+
line = add_blame(line)
|
90
|
+
if color
|
91
|
+
"#{COLORS.fetch(color)}#{line}#{COLORS.fetch(:clear)}"
|
92
|
+
else
|
93
|
+
line
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_blame(backtrace_line)
|
98
|
+
file, line = backtrace_line.split(":", 2)
|
99
|
+
line = line.to_i
|
100
|
+
if not file.start_with?("/") and line > 0 and File.exist?(".git") and File.exist?(file)
|
101
|
+
result = `git blame #{file} -L #{line},#{line} --show-email -w 2>&1`
|
102
|
+
if $?.success?
|
103
|
+
result.sub!(/ #{line}\) .*/, " )") # cut of source code
|
104
|
+
backtrace_line += " -- #{result.strip}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
backtrace_line
|
108
|
+
end
|
109
|
+
|
110
|
+
def grouped_backtraces(notices, options)
|
111
|
+
notices = notices.compact.select { |n| backtrace(n) }
|
112
|
+
|
113
|
+
compare_depth = if options[:compare_depth]
|
114
|
+
options[:compare_depth]
|
115
|
+
else
|
116
|
+
average_first_project_line(notices.map { |n| backtrace(n) }) +
|
117
|
+
DEFAULT_COMPARE_DEPTH_ADDITION
|
118
|
+
end
|
119
|
+
|
120
|
+
notices.group_by do |notice|
|
121
|
+
backtrace(notice)[0..compare_depth]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def backtrace(notice)
|
126
|
+
return if notice.backtrace.is_a?(String)
|
127
|
+
notice.backtrace.first[1]
|
128
|
+
end
|
129
|
+
|
130
|
+
def average_first_project_line(backtraces)
|
131
|
+
depths = backtraces.map do |backtrace|
|
132
|
+
backtrace.index { |line| custom_file?(line) }
|
133
|
+
end.compact
|
134
|
+
return 0 if depths.size == 0
|
135
|
+
depths.inject(:+) / depths.size
|
136
|
+
end
|
137
|
+
|
138
|
+
def custom_file?(line)
|
139
|
+
line.start_with?("[PROJECT_ROOT]") && !line.start_with?("[PROJECT_ROOT]/vendor/")
|
140
|
+
end
|
141
|
+
|
91
142
|
def add_notices_to_pages(errors)
|
92
143
|
Parallel.map(errors, :in_threads => 10) do |error|
|
93
144
|
begin
|
@@ -151,7 +202,7 @@ module AirbrakeTools
|
|
151
202
|
|
152
203
|
Options:
|
153
204
|
BANNER
|
154
|
-
opts.on("-c NUM", "--compare-depth NUM", Integer, "How deep to compare backtraces in summary (default: #{
|
205
|
+
opts.on("-c NUM", "--compare-depth NUM", Integer, "How deep to compare backtraces in summary (default: first line in project + #{DEFAULT_COMPARE_DEPTH_ADDITION})") {|s| options[:compare_depth] = s }
|
155
206
|
opts.on("-p NUM", "--pages NUM", Integer, "How maybe pages to iterate over (default: hot:#{DEFAULT_HOT_PAGES} new:#{DEFAULT_NEW_PAGES} summary:#{DEFAULT_SUMMARY_PAGES})") {|s| options[:pages] = s }
|
156
207
|
opts.on("-e ENV", "--environment ENV", String, "Only show errors from this environment (default: #{DEFAULT_ENVIRONMENT})") {|s| options[:env] = s }
|
157
208
|
opts.on("-h", "--help", "Show this.") { puts opts; exit }
|
data/spec/airbrake_tools_spec.rb
CHANGED
@@ -1,75 +1,144 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'stringio'
|
2
3
|
require 'airbrake_tools'
|
3
4
|
|
4
5
|
ROOT = File.expand_path('../../', __FILE__)
|
5
6
|
|
6
7
|
describe "airbrake-tools" do
|
7
|
-
|
8
|
-
result = `#{command} 2>&1`
|
9
|
-
message = (options[:fail] ? "SUCCESS BUT SHOULD FAIL" : "FAIL")
|
10
|
-
raise "[#{message}] #{result} [#{command}]" if $?.success? == !!options[:fail]
|
11
|
-
result
|
12
|
-
end
|
8
|
+
before { Dir.chdir ROOT }
|
13
9
|
|
14
|
-
|
15
|
-
run
|
16
|
-
|
10
|
+
describe "CLI" do
|
11
|
+
def run(command, options={})
|
12
|
+
result = `#{command} 2>&1`
|
13
|
+
message = (options[:fail] ? "SUCCESS BUT SHOULD FAIL" : "FAIL")
|
14
|
+
raise "[#{message}] #{result} [#{command}]" if $?.success? == !!options[:fail]
|
15
|
+
result
|
16
|
+
end
|
17
17
|
|
18
|
-
|
18
|
+
def airbrake_tools(args, options={})
|
19
|
+
run "#{ROOT}/bin/airbrake-tools #{args}", options
|
20
|
+
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
let(:config) { YAML.load(File.read("spec/fixtures.yml")) }
|
23
|
+
|
24
|
+
describe "basics" do
|
25
|
+
it "shows its usage without arguments" do
|
26
|
+
airbrake_tools("", :fail => true).should include("Usage")
|
27
|
+
end
|
28
|
+
|
29
|
+
it "shows its usage with -h" do
|
30
|
+
airbrake_tools("-h").should include("Usage")
|
31
|
+
end
|
23
32
|
|
24
|
-
|
25
|
-
|
26
|
-
|
33
|
+
it "shows its usage with --help" do
|
34
|
+
airbrake_tools("--help").should include("Usage")
|
35
|
+
end
|
36
|
+
|
37
|
+
it "shows its version with -v" do
|
38
|
+
airbrake_tools("-v").should =~ /^airbrake-tools \d+\.\d+\.\d+$/
|
39
|
+
end
|
40
|
+
|
41
|
+
it "shows its version with --version" do
|
42
|
+
airbrake_tools("-v").should =~ /^airbrake-tools \d+\.\d+\.\d+$/
|
43
|
+
end
|
27
44
|
end
|
28
45
|
|
29
|
-
|
30
|
-
|
46
|
+
describe "hot" do
|
47
|
+
it "kinda works" do
|
48
|
+
output = airbrake_tools("#{config["subdomain"]} #{config["auth_token"]} hot")
|
49
|
+
output.should =~ /#\d+\s+\d+\.\d+\/hour\s+total:\d+/
|
50
|
+
end
|
31
51
|
end
|
32
52
|
|
33
|
-
|
34
|
-
|
53
|
+
describe "list" do
|
54
|
+
it "kinda works" do
|
55
|
+
output = airbrake_tools("#{config["subdomain"]} #{config["auth_token"]} list")
|
56
|
+
output.should include("Page 1 ")
|
57
|
+
output.should =~ /^\d+/
|
58
|
+
end
|
35
59
|
end
|
36
60
|
|
37
|
-
|
38
|
-
|
61
|
+
describe "summary" do
|
62
|
+
it "kinda works" do
|
63
|
+
output = airbrake_tools("#{config["subdomain"]} #{config["auth_token"]} summary 51344729")
|
64
|
+
output.should include("last retrieved notice: ")
|
65
|
+
output.should include("last 2 hours: ")
|
66
|
+
end
|
39
67
|
end
|
40
68
|
|
41
|
-
|
42
|
-
|
69
|
+
describe "new" do
|
70
|
+
it "kinda works" do
|
71
|
+
output = airbrake_tools("#{config["subdomain"]} #{config["auth_token"]} new")
|
72
|
+
output.should =~ /#\d+\s+\d+\.\d+\/hour\s+total:\d+/
|
73
|
+
end
|
43
74
|
end
|
44
75
|
end
|
45
76
|
|
46
|
-
describe "
|
47
|
-
it "
|
48
|
-
|
49
|
-
output.should =~ /#\d+\s+\d+\.\d+\/hour\s+total:\d+/
|
77
|
+
describe ".average_first_project_line" do
|
78
|
+
it "is 0 for 0" do
|
79
|
+
AirbrakeTools.send(:average_first_project_line, []).should == 0
|
50
80
|
end
|
51
|
-
end
|
52
81
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
82
|
+
it "is 0 for no matching line" do
|
83
|
+
AirbrakeTools.send(:average_first_project_line, [["/usr/local/rvm/rubies/foo.rb"]]).should == 0
|
84
|
+
end
|
85
|
+
|
86
|
+
it "is the average of matching lines" do
|
87
|
+
gem = "/usr/local/rvm/foo.rb:123"
|
88
|
+
local = "[PROJECT_ROOT]/foo.rb:123"
|
89
|
+
AirbrakeTools.send(:average_first_project_line, [
|
90
|
+
[gem, local, local, local], # 1
|
91
|
+
[gem, gem, gem, local, gem], # 3
|
92
|
+
[gem, gem, gem, gem, gem, gem], # 0
|
93
|
+
]).should == 2
|
58
94
|
end
|
59
95
|
end
|
60
96
|
|
61
|
-
describe "
|
62
|
-
it "
|
63
|
-
|
64
|
-
|
65
|
-
|
97
|
+
describe ".custom_file?" do
|
98
|
+
it "is custom if it's not a library" do
|
99
|
+
AirbrakeTools.send(:custom_file?, "[PROJECT_ROOT]/app/foo.rb:123").should == true
|
100
|
+
end
|
101
|
+
|
102
|
+
it "is not custom if it's a system gem" do
|
103
|
+
AirbrakeTools.send(:custom_file?, "/usr/local/rvm/foo.rb:123").should == false
|
104
|
+
end
|
105
|
+
|
106
|
+
it "is not custom if it's a vendored gem" do
|
107
|
+
AirbrakeTools.send(:custom_file?, "[PROJECT_ROOT]/vendor/bundle/xxx.rb:123").should == false
|
66
108
|
end
|
67
109
|
end
|
68
110
|
|
69
|
-
describe "
|
70
|
-
|
71
|
-
|
72
|
-
|
111
|
+
describe ".present_line" do
|
112
|
+
before do
|
113
|
+
$stdout.stub(:tty?).and_return false
|
114
|
+
end
|
115
|
+
|
116
|
+
it "does not add colors" do
|
117
|
+
AirbrakeTools.send(:present_line, "[PROJECT_ROOT]/vendor/bundle/foo.rb:123").should_not include("\e[")
|
118
|
+
AirbrakeTools.send(:present_line, "[PROJECT_ROOT]/app/foo.rb:123").should_not include("\e[")
|
119
|
+
end
|
120
|
+
|
121
|
+
context "on tty" do
|
122
|
+
before do
|
123
|
+
$stdout.stub(:tty?).and_return true
|
124
|
+
end
|
125
|
+
|
126
|
+
it "shows gray for vendor lines" do
|
127
|
+
AirbrakeTools.send(:present_line, "[PROJECT_ROOT]/vendor/bundle/foo.rb:123").should include("\e[")
|
128
|
+
end
|
129
|
+
|
130
|
+
it "does not add colors for project lines" do
|
131
|
+
AirbrakeTools.send(:present_line, "[PROJECT_ROOT]/app/foo.rb:123").should_not include("\e[")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
it "adds blame if file exists" do
|
136
|
+
AirbrakeTools.send(:present_line, "[PROJECT_ROOT]/Gemfile:1 adasdsad").should ==
|
137
|
+
"Gemfile:1 adasdsad -- ^acc8204 (<jcheatham@zendesk.com> 2012-11-06 18:45:10 -0800 )"
|
138
|
+
end
|
139
|
+
|
140
|
+
it "does not add blame to system files" do
|
141
|
+
AirbrakeTools.send(:present_line, "/etc/hosts:1 adasdsad").should == "/etc/hosts:1 adasdsad"
|
73
142
|
end
|
74
143
|
end
|
75
144
|
|
@@ -125,4 +194,3 @@ describe "airbrake-tools" do
|
|
125
194
|
end
|
126
195
|
end
|
127
196
|
end
|
128
|
-
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: airbrake_tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-02-
|
12
|
+
date: 2013-02-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: airbrake-api
|
@@ -60,12 +60,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
60
|
- - ! '>='
|
61
61
|
- !ruby/object:Gem::Version
|
62
62
|
version: '0'
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
hash: -3539053511181789551
|
63
66
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
67
|
none: false
|
65
68
|
requirements:
|
66
69
|
- - ! '>='
|
67
70
|
- !ruby/object:Gem::Version
|
68
71
|
version: '0'
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
hash: -3539053511181789551
|
69
75
|
requirements: []
|
70
76
|
rubyforge_project:
|
71
77
|
rubygems_version: 1.8.23
|