redmine_airbrake_backend 0.6.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -1
- data/README.md +17 -16
- data/app/controllers/airbrake_controller.rb +113 -70
- data/app/controllers/airbrake_notice_controller.rb +22 -17
- data/app/controllers/airbrake_report_controller.rb +22 -8
- data/app/helpers/airbrake_helper.rb +18 -23
- data/app/views/airbrake/issue_description/default.erb +21 -27
- data/config/locales/de.yml +1 -1
- data/config/locales/en.yml +1 -1
- data/config/routes.rb +3 -4
- data/lib/redmine_airbrake_backend.rb +4 -0
- data/lib/redmine_airbrake_backend/backtrace_element.rb +37 -0
- data/lib/redmine_airbrake_backend/engine.rb +2 -1
- data/lib/redmine_airbrake_backend/error.rb +21 -45
- data/lib/redmine_airbrake_backend/ios_report.rb +87 -0
- data/lib/redmine_airbrake_backend/version.rb +1 -1
- data/redmine_airbrake_backend.gemspec +0 -1
- metadata +5 -21
- data/lib/redmine_airbrake_backend/report/ios.rb +0 -82
- data/lib/redmine_airbrake_backend/request.rb +0 -133
- data/lib/redmine_airbrake_backend/request/json.rb +0 -85
- data/lib/redmine_airbrake_backend/request/xml.rb +0 -122
@@ -1,50 +1,44 @@
|
|
1
|
-
<% if error.present? %>
|
2
1
|
h1. <%= error.message %>
|
3
2
|
|
4
|
-
<% if error.application.present? %>
|
5
|
-
h2. Application:
|
6
|
-
|
7
|
-
<%= format_list_item('Name', error.application[:name]) %>
|
8
|
-
<%= format_list_item('Version', error.application[:version]) %>
|
9
|
-
<% end %>
|
10
3
|
|
11
4
|
<% if error.backtrace.present? %>
|
12
5
|
h2. Stacktrace:
|
13
6
|
|
14
|
-
p((. <%= raw error.backtrace.
|
15
|
-
<% end %>
|
7
|
+
p((. <%= raw error.backtrace.map { |e| format_backtrace_element(e) }.join("\n") %>
|
16
8
|
<% end %>
|
17
9
|
|
18
|
-
<% if request.request.present? %>
|
19
|
-
h2. Request:
|
20
10
|
|
21
|
-
|
22
|
-
|
23
|
-
|
11
|
+
<% if error.application.present? %>
|
12
|
+
h2. Application:
|
13
|
+
|
14
|
+
<%= format_list_item('Name', error.application[:name]) %>
|
15
|
+
<%= format_list_item('Version', error.application[:version]) %>
|
16
|
+
<% end %>
|
17
|
+
|
24
18
|
|
25
|
-
<% if
|
19
|
+
<% if params[:params].present? %>
|
26
20
|
h2. Parameters:
|
27
21
|
|
28
|
-
<%= format_table(
|
22
|
+
<%= format_table(params[:params]) %>
|
29
23
|
<% end %>
|
30
24
|
|
31
|
-
<% if request.request[:cgi_data].present? %>
|
32
|
-
h2. Headers:
|
33
25
|
|
34
|
-
|
26
|
+
<% if params[:session].present? %>
|
27
|
+
h2. Session
|
28
|
+
|
29
|
+
<%= format_table(params[:session]) %>
|
35
30
|
<% end %>
|
36
31
|
|
37
|
-
<% if request.request[:session].present? %>
|
38
|
-
h2. Session:
|
39
32
|
|
40
|
-
|
41
|
-
|
33
|
+
<% if params[:context].present? %>
|
34
|
+
h2. Context
|
35
|
+
|
36
|
+
<%= format_table(params[:context].reject { |k, v| [:notifier].include?(k) }) %>
|
42
37
|
<% end %>
|
43
38
|
|
44
|
-
|
39
|
+
|
40
|
+
<% if params[:environment].present? %>
|
45
41
|
h2. Environment
|
46
42
|
|
47
|
-
<%=
|
48
|
-
<%= format_list_item('Directory', request.env[:project_root]) %>
|
49
|
-
<%= format_list_item('Hostname', request.env[:hostname]) %>
|
43
|
+
<%= format_table(params[:environment]) %>
|
50
44
|
<% end %>
|
data/config/locales/de.yml
CHANGED
@@ -4,7 +4,7 @@ de:
|
|
4
4
|
field_reopen_regexp: Regulärer Ausdruck für erneutes Öffnen
|
5
5
|
field_reopen_repeat_description: Neue Fehlerbeschreibung bei erneutem Öffnen anfügen
|
6
6
|
|
7
|
-
settings_airbrake_hash_field: "Benutzerdefiniertes Feld für
|
7
|
+
settings_airbrake_hash_field: "Benutzerdefiniertes Feld für Fehler-ID"
|
8
8
|
settings_airbrake_occurrences_field: "Benutzerdefiniertes Feld für # Auftreten"
|
9
9
|
|
10
10
|
project_module_airbrake: Airbrake
|
data/config/locales/en.yml
CHANGED
@@ -4,7 +4,7 @@ en:
|
|
4
4
|
field_reopen_regexp: Reopen regular expression
|
5
5
|
field_reopen_repeat_description: Add new description on reopen
|
6
6
|
|
7
|
-
settings_airbrake_hash_field: Notice
|
7
|
+
settings_airbrake_hash_field: Notice ID Custom Field
|
8
8
|
settings_airbrake_occurrences_field: Occurrences Custom Field
|
9
9
|
|
10
10
|
project_module_airbrake: Airbrake
|
data/config/routes.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
|
-
#
|
3
|
-
post '/
|
4
|
-
post '/
|
5
|
-
post '/notifier_api/v3/reports/:type' => 'airbrake_report#report'
|
2
|
+
# api
|
3
|
+
post '/api/v3/projects/:project_id/notices' => 'airbrake_notice#notices'
|
4
|
+
post '/api/v3/projects/:project_id/ios-reports' => 'airbrake_report#ios_reports'
|
6
5
|
|
7
6
|
# settings
|
8
7
|
resources :projects do
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
|
4
|
+
module RedmineAirbrakeBackend
|
5
|
+
# Backtrace element received by airbrake
|
6
|
+
class BacktraceElement
|
7
|
+
attr_reader :file, :line, :function, :column
|
8
|
+
|
9
|
+
def initialize(data)
|
10
|
+
# File
|
11
|
+
@file = data[:file].presence
|
12
|
+
|
13
|
+
# Line
|
14
|
+
@line = data[:line].presence
|
15
|
+
|
16
|
+
# Function
|
17
|
+
@function = data[:function].presence
|
18
|
+
|
19
|
+
# Column
|
20
|
+
@column = data[:column].presence
|
21
|
+
end
|
22
|
+
|
23
|
+
def checksum
|
24
|
+
@_checksum ||= Digest::MD5.hexdigest("#{@file}|#{normalize_function_name(@function)}|#{@line}|#{@column}")
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def normalize_function_name(function_name)
|
30
|
+
name = @function
|
31
|
+
.downcase
|
32
|
+
.gsub(/_\d+_/, '') # ruby blocks
|
33
|
+
|
34
|
+
RedmineAirbrakeBackend.filter_hex_values(name)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -36,10 +36,11 @@ module RedmineAirbrakeBackend
|
|
36
36
|
description 'Airbrake Backend for Redmine'
|
37
37
|
url 'https://github.com/ydkn/redmine_airbrake_backend'
|
38
38
|
version ::RedmineAirbrakeBackend::VERSION
|
39
|
-
requires_redmine version_or_higher: '3.
|
39
|
+
requires_redmine version_or_higher: '3.2.0'
|
40
40
|
directory RedmineAirbrakeBackend.directory
|
41
41
|
|
42
42
|
project_module :airbrake do
|
43
|
+
permission :airbrake, { airbrake_notice: [:notices], airbrake_report: [:ios_reports] }
|
43
44
|
permission :manage_airbrake, { airbrake: [:update] }
|
44
45
|
end
|
45
46
|
|
@@ -1,45 +1,43 @@
|
|
1
1
|
require 'digest/md5'
|
2
|
+
require 'redmine_airbrake_backend/backtrace_element'
|
3
|
+
|
2
4
|
|
3
5
|
module RedmineAirbrakeBackend
|
4
6
|
# Error received by airbrake
|
5
7
|
class Error
|
6
|
-
attr_accessor :request
|
7
8
|
attr_reader :type, :message, :backtrace
|
8
|
-
attr_reader :
|
9
|
-
|
10
|
-
def initialize(error_data)
|
11
|
-
# Data
|
12
|
-
@data = error_data
|
9
|
+
attr_reader :id, :subject, :application, :attachments
|
13
10
|
|
11
|
+
def initialize(data)
|
14
12
|
# Type
|
15
|
-
@type =
|
13
|
+
@type = data[:type]
|
16
14
|
|
17
15
|
# Message
|
18
|
-
@message =
|
16
|
+
@message = data[:message]
|
19
17
|
|
20
18
|
# Backtrace
|
21
|
-
@backtrace =
|
22
|
-
|
23
|
-
# Application
|
24
|
-
@application = @data[:application]
|
25
|
-
|
26
|
-
# Attachments
|
27
|
-
@attachments = @data[:attachments]
|
19
|
+
@backtrace = data[:backtrace].map { |b| BacktraceElement.new(b) }
|
28
20
|
|
29
|
-
#
|
30
|
-
@
|
21
|
+
# Error ID
|
22
|
+
@id = generate_id
|
31
23
|
|
32
24
|
# Subject
|
33
25
|
@subject = generate_subject
|
26
|
+
|
27
|
+
# Attachments
|
28
|
+
@attachments = data[:attachments].presence || []
|
29
|
+
|
30
|
+
# Application
|
31
|
+
@application = data[:application].presence
|
34
32
|
end
|
35
33
|
|
36
34
|
private
|
37
35
|
|
38
|
-
def
|
36
|
+
def generate_id
|
39
37
|
h = []
|
40
|
-
h << filter_hex_values(@type)
|
41
|
-
h << filter_hex_values(@message)
|
42
|
-
h +=
|
38
|
+
h << RedmineAirbrakeBackend.filter_hex_values(@type)
|
39
|
+
h << RedmineAirbrakeBackend.filter_hex_values(@message)
|
40
|
+
h += @backtrace.map(&:checksum).compact
|
43
41
|
|
44
42
|
Digest::MD5.hexdigest(h.compact.join("\n"))
|
45
43
|
end
|
@@ -48,34 +46,12 @@ module RedmineAirbrakeBackend
|
|
48
46
|
s = ''
|
49
47
|
|
50
48
|
if @type.blank? || @message.starts_with?("#{@type}:")
|
51
|
-
s = "[#{@
|
49
|
+
s = "[#{@id[0..7]}] #{@message}"
|
52
50
|
else
|
53
|
-
s = "[#{@
|
51
|
+
s = "[#{@id[0..7]}] #{@type}: #{@message}"
|
54
52
|
end
|
55
53
|
|
56
54
|
s[0..254].strip
|
57
55
|
end
|
58
|
-
|
59
|
-
def normalized_backtrace
|
60
|
-
if @backtrace.present?
|
61
|
-
@backtrace.map do |e|
|
62
|
-
"#{e[:file]}|#{normalize_method_name(e[:method])}|#{e[:number]}" rescue nil
|
63
|
-
end.compact
|
64
|
-
else
|
65
|
-
[]
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def normalize_method_name(method_name)
|
70
|
-
name = e[:method]
|
71
|
-
.downcase
|
72
|
-
.gsub(/_\d+_/, '') # ruby blocks
|
73
|
-
|
74
|
-
filter_hex_values(name)
|
75
|
-
end
|
76
|
-
|
77
|
-
def filter_hex_values(value)
|
78
|
-
value.gsub(/0x[0-9a-f]+/, '')
|
79
|
-
end
|
80
56
|
end
|
81
57
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'redmine_airbrake_backend/error'
|
2
|
+
|
3
|
+
|
4
|
+
module RedmineAirbrakeBackend
|
5
|
+
# iOS Report received by airbrake
|
6
|
+
class IosReport < Error
|
7
|
+
def initialize(data)
|
8
|
+
super(IosReport.parse(data))
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def self.parse(data)
|
14
|
+
error = {
|
15
|
+
application: {},
|
16
|
+
backtrace: [],
|
17
|
+
attachments: [],
|
18
|
+
}
|
19
|
+
|
20
|
+
header_finished = false
|
21
|
+
next_line_is_message = false
|
22
|
+
crashed_thread = false
|
23
|
+
indicent_identifier = nil
|
24
|
+
|
25
|
+
data.split("\n").each do |line|
|
26
|
+
header_finished = true if line =~ /^(Application Specific Information|Last Exception Backtrace|Thread \d+( Crashed)?):$/
|
27
|
+
|
28
|
+
unless header_finished
|
29
|
+
key, value = line.split(':', 2).map { |s| s.strip }
|
30
|
+
|
31
|
+
next if key.blank? || value.blank?
|
32
|
+
|
33
|
+
case key
|
34
|
+
when 'Exception Type'
|
35
|
+
error[:type] = value
|
36
|
+
when 'Exception Codes'
|
37
|
+
error[:message] = value
|
38
|
+
when 'Incident Identifier'
|
39
|
+
indicent_identifier = value
|
40
|
+
when 'Identifier'
|
41
|
+
error[:application][:name] = value
|
42
|
+
when 'Version'
|
43
|
+
error[:application][:version] = value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
if next_line_is_message
|
48
|
+
next_line_is_message = false
|
49
|
+
|
50
|
+
error[:message] = line
|
51
|
+
|
52
|
+
if line =~ /^\*\*\* Terminating app due to uncaught exception '([^']+)', reason: '\*\*\* (.*)'$/
|
53
|
+
error[:type] = Regexp.last_match(1)
|
54
|
+
error[:message] = Regexp.last_match(2)
|
55
|
+
else
|
56
|
+
error[:message] = line
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
crashed_thread = false if line =~ /^Thread \d+:$/
|
61
|
+
|
62
|
+
if crashed_thread
|
63
|
+
if line =~ /^(\d+)\s+([^\s]+)\s+(0x[0-9a-f]+)\s+(.+) \+ (\d+)$/
|
64
|
+
error[:backtrace] << {
|
65
|
+
file: Regexp.last_match(2),
|
66
|
+
function: Regexp.last_match(4),
|
67
|
+
line: Regexp.last_match(5)
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
crashed_thread = true if error[:backtrace].blank? && line =~ /^(Last Exception Backtrace|Thread \d+ Crashed):$/
|
73
|
+
|
74
|
+
next_line_is_message = true if line =~ /^Application Specific Information:$/
|
75
|
+
end
|
76
|
+
|
77
|
+
return nil if error.blank?
|
78
|
+
|
79
|
+
error[:attachments] << {
|
80
|
+
filename: "#{indicent_identifier}.crash",
|
81
|
+
data: data
|
82
|
+
} if indicent_identifier.present?
|
83
|
+
|
84
|
+
error
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redmine_airbrake_backend
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Florian Schwab
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -24,20 +24,6 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: nokogiri
|
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'
|
41
27
|
- !ruby/object:Gem::Dependency
|
42
28
|
name: bundler
|
43
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,17 +81,15 @@ files:
|
|
95
81
|
- db/migrate/20130708102357_create_airbrake_project_settings.rb
|
96
82
|
- db/migrate/20131003151228_add_reopen_repeat_description.rb
|
97
83
|
- lib/redmine_airbrake_backend.rb
|
84
|
+
- lib/redmine_airbrake_backend/backtrace_element.rb
|
98
85
|
- lib/redmine_airbrake_backend/engine.rb
|
99
86
|
- lib/redmine_airbrake_backend/error.rb
|
87
|
+
- lib/redmine_airbrake_backend/ios_report.rb
|
100
88
|
- lib/redmine_airbrake_backend/patches/issue_category.rb
|
101
89
|
- lib/redmine_airbrake_backend/patches/issue_priority.rb
|
102
90
|
- lib/redmine_airbrake_backend/patches/project.rb
|
103
91
|
- lib/redmine_airbrake_backend/patches/projects_helper.rb
|
104
92
|
- lib/redmine_airbrake_backend/patches/tracker.rb
|
105
|
-
- lib/redmine_airbrake_backend/report/ios.rb
|
106
|
-
- lib/redmine_airbrake_backend/request.rb
|
107
|
-
- lib/redmine_airbrake_backend/request/json.rb
|
108
|
-
- lib/redmine_airbrake_backend/request/xml.rb
|
109
93
|
- lib/redmine_airbrake_backend/version.rb
|
110
94
|
- lib/tasks/test.rake
|
111
95
|
- redmine_airbrake_backend.gemspec
|
@@ -131,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
131
115
|
version: '0'
|
132
116
|
requirements: []
|
133
117
|
rubyforge_project:
|
134
|
-
rubygems_version: 2.
|
118
|
+
rubygems_version: 2.5.1
|
135
119
|
signing_key:
|
136
120
|
specification_version: 4
|
137
121
|
summary: This plugin provides the necessary API to use Redmine as a Airbrake backend
|
@@ -1,82 +0,0 @@
|
|
1
|
-
module RedmineAirbrakeBackend
|
2
|
-
# Represents a report contained in a request
|
3
|
-
class Report
|
4
|
-
# iOS report
|
5
|
-
class Ios
|
6
|
-
# Parse an iOS crash log
|
7
|
-
def self.parse(data)
|
8
|
-
error = {
|
9
|
-
backtrace: [],
|
10
|
-
attachments: [],
|
11
|
-
application: {}
|
12
|
-
}
|
13
|
-
|
14
|
-
header_finished = false
|
15
|
-
next_line_is_message = false
|
16
|
-
crashed_thread = false
|
17
|
-
indicent_identifier = nil
|
18
|
-
|
19
|
-
data.split("\n").each do |line|
|
20
|
-
header_finished = true if line =~ /^(Application Specific Information|Last Exception Backtrace|Thread \d+( Crashed)?):$/
|
21
|
-
|
22
|
-
unless header_finished
|
23
|
-
key, value = line.split(':', 2).map { |s| s.strip }
|
24
|
-
|
25
|
-
next if key.blank? || value.blank?
|
26
|
-
|
27
|
-
case key
|
28
|
-
when 'Exception Type'
|
29
|
-
error[:type] = value
|
30
|
-
when 'Exception Codes'
|
31
|
-
error[:message] = value
|
32
|
-
when 'Incident Identifier'
|
33
|
-
indicent_identifier = value
|
34
|
-
when 'Identifier'
|
35
|
-
error[:application][:name] = value
|
36
|
-
when 'Version'
|
37
|
-
error[:application][:version] = value
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
if next_line_is_message
|
42
|
-
next_line_is_message = false
|
43
|
-
|
44
|
-
error[:message] = line
|
45
|
-
|
46
|
-
if line =~ /^\*\*\* Terminating app due to uncaught exception '([^']+)', reason: '\*\*\* (.*)'$/
|
47
|
-
error[:type] = Regexp.last_match(1)
|
48
|
-
error[:message] = Regexp.last_match(2)
|
49
|
-
else
|
50
|
-
error[:message] = line
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
crashed_thread = false if line =~ /^Thread \d+:$/
|
55
|
-
|
56
|
-
if crashed_thread
|
57
|
-
if line =~ /^(\d+)\s+([^\s]+)\s+(0x[0-9a-f]+)\s+(.+) \+ (\d+)$/
|
58
|
-
error[:backtrace] << {
|
59
|
-
file: Regexp.last_match(2),
|
60
|
-
method: Regexp.last_match(4),
|
61
|
-
number: Regexp.last_match(5),
|
62
|
-
}
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
crashed_thread = true if error[:backtrace].blank? && line =~ /^(Last Exception Backtrace|Thread \d+ Crashed):$/
|
67
|
-
|
68
|
-
next_line_is_message = true if line =~ /^Application Specific Information:$/
|
69
|
-
end
|
70
|
-
|
71
|
-
return nil if error.blank?
|
72
|
-
|
73
|
-
error[:attachments] << {
|
74
|
-
filename: "#{indicent_identifier}.crash",
|
75
|
-
data: data
|
76
|
-
} if indicent_identifier.present?
|
77
|
-
|
78
|
-
error
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|