airbrake_tools 0.0.1 → 0.0.2
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/.gitignore +1 -0
- data/Gemfile.lock +1 -1
- data/Readme.md +73 -3
- data/bin/airbrake-tools +5 -0
- data/lib/airbrake_tools/version.rb +1 -1
- data/lib/airbrake_tools.rb +73 -37
- data/spark.sh +89 -0
- data/spec/airbrake_tools_spec.rb +18 -2
- data/spec/fixtures.example.yml +2 -0
- metadata +6 -3
- data/.travis.yml +0 -4
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
spec/fixtures.yml
|
data/Gemfile.lock
CHANGED
data/Readme.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
Power tools for Airbrake
|
2
2
|
|
3
|
+
- hottest errors
|
4
|
+
- list / search errors
|
5
|
+
- analyze 1 error (ocurrance graphs / different backtraces + frequencies)
|
6
|
+
|
3
7
|
Install
|
4
8
|
=======
|
5
9
|
|
@@ -8,11 +12,77 @@ Install
|
|
8
12
|
Usage
|
9
13
|
=====
|
10
14
|
|
11
|
-
|
15
|
+
airbrake_tools your-account your-auth-token
|
16
|
+
|
17
|
+
Output
|
18
|
+
======
|
19
|
+
|
20
|
+
### Hot
|
21
|
+
|
22
|
+
```
|
23
|
+
airbrake-tools your-account your-auth-token hot
|
24
|
+
.............................
|
25
|
+
#1 793.5/hour 2170:total ▁▂▂█
|
26
|
+
--> id: 51344729 -- first: 2012-06-25 15:47:11 UTC -- Mysql2::Error -- Mysql2::Error: Lost connection to MySQL server at 'reading initial communication packet', system error: 110
|
27
|
+
#2 595.6/hour 648:total ▁▂▄█
|
28
|
+
--> id: 53991244 -- first: 2012-12-13 20:31:26 UTC -- ActiveRecord::RecordInvalid -- ActiveRecord::RecordInvalid: Validation failed: Requester is suspended.
|
29
|
+
#3 458.0/hour 191840:total ▁▁▁▄█
|
30
|
+
--> id: 53864752 -- first: 2012-12-06 19:57:12 UTC -- SyntaxError -- [retrying processing mail at 782bcb63887c.eml] SyntaxError: unterminated quoted-word
|
31
|
+
#4 315.3/hour 5184:total ▆▅▁▁▂▁▆▆█▅
|
32
|
+
--> id: 52897649 -- first: 2012-10-14 02:10:41 UTC -- Http::ClientError -- [The server responded with status 500]
|
33
|
+
```
|
34
|
+
|
35
|
+
### List
|
36
|
+
|
37
|
+
Shows all errors divided by pages
|
38
|
+
- search
|
39
|
+
- "fix all errors on page x"
|
40
|
+
|
41
|
+
```
|
42
|
+
airbrake-tools your-account your-auth-token list | grep 'is suspended'
|
43
|
+
Page 1 ----------
|
44
|
+
54054554 -- ActiveRecord::RecordInvalid -- ActiveRecord::RecordInvalid: Validation failed: Requester is suspended.
|
45
|
+
Page 2 ----------
|
46
|
+
...
|
47
|
+
```
|
48
|
+
|
49
|
+
### Summary
|
50
|
+
|
51
|
+
- show details for 1 error (combines 150 notices)
|
52
|
+
- show all different traces that caused this error (play with --compare-depth)
|
53
|
+
|
54
|
+
```
|
55
|
+
airbrake-tools your-account your-auth-token summary 51344729
|
56
|
+
last retrieved notice: 1 hours ago at 2012-12-19 22:43:20 UTC
|
57
|
+
last 2 hours: ▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▂▂▂▅▃█
|
58
|
+
last day: ▁
|
59
|
+
Trace 1: occurred 100 times e.g. 7145616660, 7145614849
|
60
|
+
Mysql2::Error: Lost connection to MySQL server at 'reading initial communication packet', system error
|
61
|
+
./mysql2/lib/mysql2/client.rb:44:in `connect'
|
62
|
+
...
|
63
|
+
|
64
|
+
Trace 2: occurred 10 times e.g. 7145613107, 7145612108
|
65
|
+
Mysql2::Error: Lost connection to MySQL server
|
66
|
+
./mysql2/lib/mysql2/client.rb:58:in `disconnect'
|
67
|
+
...
|
68
|
+
|
69
|
+
Trace 3: occurred 5 times e.g. 7145609979, 7145609161
|
70
|
+
Mysql2::Error: Lost connection to MySQL server during reconnect
|
71
|
+
./mysql2/lib/mysql2/client.rb:78:in `reconnect'
|
72
|
+
...
|
73
|
+
```
|
74
|
+
|
75
|
+
### Options
|
76
|
+
|
77
|
+
```
|
78
|
+
-c, --compare-depth NUM At what level to compare backtraces (default: 7)
|
79
|
+
-e, --environment ENV Only show errors from this environment (default: production)
|
80
|
+
-h, --help Show this.
|
81
|
+
-v, --version Show Version
|
82
|
+
```
|
12
83
|
|
13
84
|
Author
|
14
85
|
======
|
15
86
|
[Jonathan Cheatham](http://github.com/jcheatham)<br/>
|
16
87
|
coaxis@gmail.com<br/>
|
17
|
-
License: MIT
|
18
|
-
[](https://travis-ci.org/jcheatham/airbrake_tools)
|
88
|
+
License: MIT
|
data/bin/airbrake-tools
ADDED
data/lib/airbrake_tools.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
require "airbrake_tools/version"
|
2
3
|
require "airbrake-api"
|
3
4
|
|
4
5
|
module AirbrakeTools
|
6
|
+
DEFAULT_COMPARE_DEPTH = 7
|
7
|
+
DEFAULT_ENVIRONMENT = "production"
|
8
|
+
|
5
9
|
class << self
|
6
10
|
def cli(argv)
|
7
11
|
options = extract_options(argv)
|
@@ -15,41 +19,85 @@ module AirbrakeTools
|
|
15
19
|
return 1
|
16
20
|
end
|
17
21
|
|
18
|
-
|
22
|
+
case ARGV[2]
|
23
|
+
when "hot"
|
24
|
+
errors = hot(options)
|
25
|
+
print_errors(errors)
|
26
|
+
when "list"
|
27
|
+
list(options)
|
28
|
+
when "summary"
|
29
|
+
summary(ARGV[3] || raise("Need error id"), options)
|
30
|
+
else
|
31
|
+
raise "Unknown command try hot/list/summary"
|
32
|
+
end
|
33
|
+
return 0
|
19
34
|
end
|
20
35
|
|
21
|
-
def hot(options)
|
22
|
-
|
23
|
-
|
24
|
-
pages = 1
|
25
|
-
|
36
|
+
def hot(options = {})
|
37
|
+
pages = (options[:pages] || 1).to_i
|
26
38
|
errors = []
|
27
39
|
pages.times do |i|
|
28
|
-
errors.concat
|
40
|
+
errors.concat(AirbrakeAPI.errors(:page => i+1) || [])
|
29
41
|
end
|
42
|
+
select_env!(errors, options)
|
30
43
|
|
31
44
|
errors = Parallel.map(errors, :in_threads => 10) do |error|
|
32
45
|
begin
|
33
46
|
notices = AirbrakeAPI.notices(error.id, :pages => 1, :raw => true).compact
|
34
47
|
print "."
|
35
|
-
[error, notices]
|
48
|
+
[error, notices, frequency(notices)]
|
36
49
|
rescue Faraday::Error::ParsingError
|
37
|
-
$stderr.puts "Ignoring #{
|
50
|
+
$stderr.puts "Ignoring #{hot_summary(error)}, got 500 from http://#{AirbrakeAPI.account}.airbrake.io/errors/#{error.id}"
|
38
51
|
end
|
39
52
|
end.compact
|
40
53
|
|
41
|
-
|
54
|
+
errors.sort_by{|e,n,f| f }.reverse
|
55
|
+
end
|
56
|
+
|
57
|
+
def list(options)
|
58
|
+
page = 1
|
59
|
+
while errors = AirbrakeAPI.errors(:page => page)
|
60
|
+
select_env!(errors, options)
|
61
|
+
errors.each do |error|
|
62
|
+
puts "#{error.id} -- #{error.error_class} -- #{error.error_message} -- #{error.created_at}"
|
63
|
+
end
|
64
|
+
$stderr.puts "Page #{page} ----------\n"
|
65
|
+
page += 1
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def summary(error_id, options)
|
70
|
+
compare_depth = options[:compare_depth] || DEFAULT_COMPARE_DEPTH
|
71
|
+
notices = AirbrakeAPI.notices(error_id, :pages => 5)
|
42
72
|
|
43
|
-
|
44
|
-
|
45
|
-
|
73
|
+
puts "last retrieved notice: #{((Time.now - notices.last.created_at) / (60 * 60)).round} hours ago at #{notices.last.created_at}"
|
74
|
+
puts "last 2 hours: #{sparkline(notices, :slots => 60, :interval => 120)}"
|
75
|
+
puts "last day: #{sparkline(notices, :slots => 24, :interval => 60 * 60)}"
|
76
|
+
|
77
|
+
backtraces = notices.compact.select{|n| n.backtrace }.group_by do |notice|
|
78
|
+
notice.backtrace.first[1][0..compare_depth]
|
46
79
|
end
|
47
80
|
|
48
|
-
|
81
|
+
backtraces.sort_by{|_,notices| notices.size }.reverse.each_with_index do |(backtrace, notices), index|
|
82
|
+
puts "Trace #{index + 1}: occurred #{notices.size} times e.g. #{notices[0..5].map(&:id).join(", ")}"
|
83
|
+
puts notices.first.error_message
|
84
|
+
puts backtrace.map{|line| line.sub("[PROJECT_ROOT]/", "./") }
|
85
|
+
puts ""
|
86
|
+
end
|
49
87
|
end
|
50
88
|
|
51
89
|
private
|
52
90
|
|
91
|
+
def select_env!(errors, options)
|
92
|
+
errors.select!{|e| e.rails_env == options[:env] || DEFAULT_ENVIRONMENT }
|
93
|
+
end
|
94
|
+
|
95
|
+
def print_errors(hot)
|
96
|
+
hot.each_with_index do |(error, notices, rate, deviance), index|
|
97
|
+
puts "\n##{(index+1).to_s.ljust(2)} #{rate.round(2).to_s.rjust(6)}/hour total:#{error.notices_count.to_s.ljust(8)} #{sparkline(notices, :slots => 60, :interval => 60).ljust(61)} -- #{summary(error)}"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
53
101
|
def frequency(notices)
|
54
102
|
hour = 60 * 60
|
55
103
|
sum_of_ages = notices.map { |n| Time.now - n.created_at }.inject(&:+)
|
@@ -59,45 +107,34 @@ module AirbrakeTools
|
|
59
107
|
(rate * hour).round(1)
|
60
108
|
end
|
61
109
|
|
62
|
-
def
|
110
|
+
def hot_summary(error)
|
63
111
|
"id:#{error.id} -- first:#{error.created_at} -- #{error.error_class} -- #{error.error_message}"
|
64
112
|
end
|
65
113
|
|
66
114
|
def extract_options(argv)
|
67
|
-
options = {
|
68
|
-
}
|
115
|
+
options = {}
|
69
116
|
OptionParser.new do |opts|
|
70
117
|
opts.banner = <<-BANNER.gsub(" "*12, "")
|
71
|
-
|
118
|
+
Power tools for airbrake.
|
119
|
+
|
120
|
+
hot: list hottest errors
|
121
|
+
list: list errors 1-by-1 so you can e.g. grep -> search
|
122
|
+
summary: analyze occurrences and backtrace origins
|
72
123
|
|
73
124
|
Usage:
|
74
|
-
airbrake-tools subdomain token [options]
|
75
|
-
token: go to airbrake -> settings, copy your auth
|
125
|
+
airbrake-tools subdomain auth-token command [options]
|
126
|
+
auth-token: go to airbrake -> settings, copy your auth-token
|
76
127
|
|
77
128
|
Options:
|
78
129
|
BANNER
|
130
|
+
opts.on("-c NUM", "--compare-depth NUM", Integer, "How deep to compare backtraces (default: #{DEFAULT_COMPARE_DEPTH})") {|s| options[:env] = s }
|
131
|
+
opts.on("-e ENV", "--environment ENV", String, "Only show errors from this environment (default: #{DEFAULT_ENVIRONMENT})") {|s| options[:env] = s }
|
79
132
|
opts.on("-h", "--help", "Show this.") { puts opts; exit }
|
80
133
|
opts.on("-v", "--version", "Show Version"){ puts "airbrake-tools #{VERSION}"; exit }
|
81
134
|
end.parse!(argv)
|
82
135
|
options
|
83
136
|
end
|
84
137
|
|
85
|
-
def run(cmd)
|
86
|
-
all = ""
|
87
|
-
puts cmd
|
88
|
-
IO.popen(cmd) do |pipe|
|
89
|
-
while str = pipe.gets
|
90
|
-
all << str
|
91
|
-
puts str
|
92
|
-
end
|
93
|
-
end
|
94
|
-
[$?.success?, all]
|
95
|
-
end
|
96
|
-
|
97
|
-
def run!(command)
|
98
|
-
raise "Command failed #{command}" unless run(command).first
|
99
|
-
end
|
100
|
-
|
101
138
|
def sparkline_data(notices, options)
|
102
139
|
last = notices.last.created_at
|
103
140
|
now = Time.now
|
@@ -112,6 +149,5 @@ module AirbrakeTools
|
|
112
149
|
def sparkline(notices, options)
|
113
150
|
`#{File.expand_path('../../spark.sh',__FILE__)} #{sparkline_data(notices, options).join(" ")}`.strip
|
114
151
|
end
|
115
|
-
|
116
152
|
end
|
117
153
|
end
|
data/spark.sh
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
#
|
3
|
+
# spark
|
4
|
+
# https://github.com/holman/spark
|
5
|
+
#
|
6
|
+
# Generates sparklines for a set of data.
|
7
|
+
#
|
8
|
+
# Here's a a good web-based sparkline generator that was a bit of inspiration
|
9
|
+
# for spark:
|
10
|
+
#
|
11
|
+
# https://datacollective.org/sparkblocks
|
12
|
+
#
|
13
|
+
# spark takes a comma-separated list of data and then prints a sparkline out of
|
14
|
+
# it.
|
15
|
+
#
|
16
|
+
# Examples:
|
17
|
+
#
|
18
|
+
# spark 1 5 22 13 53
|
19
|
+
# # => ▁▁▃▂▇
|
20
|
+
#
|
21
|
+
# spark 0 30 55 80 33 150
|
22
|
+
# # => ▁▂▃▅▂▇
|
23
|
+
#
|
24
|
+
# spark -h
|
25
|
+
# # => Prints the spark help text.
|
26
|
+
|
27
|
+
# Generates sparklines.
|
28
|
+
#
|
29
|
+
# $1 - The data we'd like to graph.
|
30
|
+
spark()
|
31
|
+
{
|
32
|
+
local n numbers=
|
33
|
+
|
34
|
+
# find min/max values
|
35
|
+
local min=0xffffffff max=0
|
36
|
+
|
37
|
+
for n in ${@//,/ }
|
38
|
+
do
|
39
|
+
# on Linux (or with bash4) we could use `printf %.0f $n` here to
|
40
|
+
# round the number but that doesn't work on OS X (bash3) nor does
|
41
|
+
# `awk '{printf "%.0f",$1}' <<< $n` work, so just cut it off
|
42
|
+
n=${n%.*}
|
43
|
+
(( n < min )) && min=$n
|
44
|
+
(( n > max )) && max=$n
|
45
|
+
numbers=$numbers${numbers:+ }$n
|
46
|
+
done
|
47
|
+
|
48
|
+
# print ticks
|
49
|
+
local ticks=(▁ ▂ ▃ ▄ ▅ ▆ ▇ █)
|
50
|
+
|
51
|
+
local f=$(( (($max-$min)<<8)/(${#ticks[@]}-1) ))
|
52
|
+
(( f < 1 )) && f=1
|
53
|
+
|
54
|
+
for n in $numbers
|
55
|
+
do
|
56
|
+
echo -n ${ticks[$(( ((($n-$min)<<8)/$f) ))]}
|
57
|
+
done
|
58
|
+
echo
|
59
|
+
}
|
60
|
+
|
61
|
+
# If we're being sourced, don't worry about such things
|
62
|
+
if [ "$BASH_SOURCE" == "$0" ]; then
|
63
|
+
# Prints the help text for spark.
|
64
|
+
help()
|
65
|
+
{
|
66
|
+
cat <<EOF
|
67
|
+
|
68
|
+
USAGE:
|
69
|
+
spark [-h] VALUE,...
|
70
|
+
|
71
|
+
EXAMPLES:
|
72
|
+
spark 1 5 22 13 53
|
73
|
+
▁▁▃▂█
|
74
|
+
spark 0,30,55,80,33,150
|
75
|
+
▁▂▃▄▂█
|
76
|
+
echo 9 13 5 17 1 | spark
|
77
|
+
▄▆▂█▁
|
78
|
+
EOF
|
79
|
+
}
|
80
|
+
|
81
|
+
# show help for no arguments if stdin is a terminal
|
82
|
+
if { [ -z "$1" ] && [ -t 0 ] ; } || [ "$1" == '-h' ]
|
83
|
+
then
|
84
|
+
help
|
85
|
+
exit 0
|
86
|
+
fi
|
87
|
+
|
88
|
+
spark ${@:-`cat`}
|
89
|
+
fi
|
data/spec/airbrake_tools_spec.rb
CHANGED
@@ -44,8 +44,24 @@ describe "airbrake-tools" do
|
|
44
44
|
|
45
45
|
describe "hot" do
|
46
46
|
it "kinda works" do
|
47
|
-
output = airbrake_tools("#{config["subdomain"]} #{config["auth_token"]}")
|
48
|
-
output.should =~ /#\d+\s+\d+\.\d+\/hour\s
|
47
|
+
output = airbrake_tools("#{config["subdomain"]} #{config["auth_token"]} hot")
|
48
|
+
output.should =~ /#\d+\s+\d+\.\d+\/hour\s+total:\d+/
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "list" do
|
53
|
+
it "kinda works" do
|
54
|
+
output = airbrake_tools("#{config["subdomain"]} #{config["auth_token"]} list")
|
55
|
+
output.should include("Page 1 ")
|
56
|
+
output.should =~ /^\d+/
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "summary" do
|
61
|
+
it "kinda works" do
|
62
|
+
output = airbrake_tools("#{config["subdomain"]} #{config["auth_token"]} summary 51344729")
|
63
|
+
output.should include("last retrieved notice: ")
|
64
|
+
output.should include("last 2 hours: ")
|
49
65
|
end
|
50
66
|
end
|
51
67
|
end
|
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.2
|
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: 2012-
|
12
|
+
date: 2012-12-20 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description:
|
15
15
|
email: coaxis@gmail.com
|
@@ -17,15 +17,18 @@ executables: []
|
|
17
17
|
extensions: []
|
18
18
|
extra_rdoc_files: []
|
19
19
|
files:
|
20
|
-
- .
|
20
|
+
- .gitignore
|
21
21
|
- Gemfile
|
22
22
|
- Gemfile.lock
|
23
23
|
- Rakefile
|
24
24
|
- Readme.md
|
25
25
|
- airbrake_tools.gemspec
|
26
|
+
- bin/airbrake-tools
|
26
27
|
- lib/airbrake_tools.rb
|
27
28
|
- lib/airbrake_tools/version.rb
|
29
|
+
- spark.sh
|
28
30
|
- spec/airbrake_tools_spec.rb
|
31
|
+
- spec/fixtures.example.yml
|
29
32
|
- spec/spec_helper.rb
|
30
33
|
homepage: http://github.com/jcheatham/airbrake_tools
|
31
34
|
licenses:
|
data/.travis.yml
DELETED