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.
@@ -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.collect { |e| format_backtrace_element(e) }.join("\n") %>
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
- <%= format_list_item('URL', request.request[:url]) %>
22
- <%= format_list_item('Component', request.request[:component]) %>
23
- <%= format_list_item('Action', request.request[:action]) %>
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 request.request[:params].present? %>
19
+ <% if params[:params].present? %>
26
20
  h2. Parameters:
27
21
 
28
- <%= format_table(request.request[:params]) %>
22
+ <%= format_table(params[:params]) %>
29
23
  <% end %>
30
24
 
31
- <% if request.request[:cgi_data].present? %>
32
- h2. Headers:
33
25
 
34
- <%= format_table(request.request[:cgi_data]) %>
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
- <%= format_table(request.request[:session]) %>
41
- <% end %>
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
- <% if request.env.present? %>
39
+
40
+ <% if params[:environment].present? %>
45
41
  h2. Environment
46
42
 
47
- <%= format_list_item('Name', request.environment_name) %>
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 %>
@@ -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 Fehlernummer"
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
@@ -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 Hash Custom Field
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
@@ -1,8 +1,7 @@
1
1
  Rails.application.routes.draw do
2
- # notifier api
3
- post '/notifier_api/v2/notices' => 'airbrake_notice#notice_xml'
4
- post '/notifier_api/v3/notices' => 'airbrake_notice#notice_json'
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
@@ -4,4 +4,8 @@ module RedmineAirbrakeBackend
4
4
  def self.directory
5
5
  File.expand_path(File.join(File.dirname(__FILE__), '..'))
6
6
  end
7
+
8
+ def self.filter_hex_values(value)
9
+ value.gsub(/0x[0-9a-f]+/, '')
10
+ end
7
11
  end
@@ -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.1.0'
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 :application, :airbrake_hash, :subject, :attachments
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 = @data[:type]
13
+ @type = data[:type]
16
14
 
17
15
  # Message
18
- @message = @data[:message]
16
+ @message = data[:message]
19
17
 
20
18
  # Backtrace
21
- @backtrace = @data[: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
- # Hash
30
- @airbrake_hash = generate_hash
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 generate_hash
36
+ def generate_id
39
37
  h = []
40
- h << filter_hex_values(@type)
41
- h << filter_hex_values(@message)
42
- h += normalized_backtrace
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 = "[#{@airbrake_hash[0..7]}] #{@message}"
49
+ s = "[#{@id[0..7]}] #{@message}"
52
50
  else
53
- s = "[#{@airbrake_hash[0..7]}] #{@type}: #{@message}"
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
@@ -1,4 +1,4 @@
1
1
  module RedmineAirbrakeBackend
2
2
  # Version of this gem
3
- VERSION = '0.6.2'
3
+ VERSION = '1.0.0'
4
4
  end
@@ -18,7 +18,6 @@ Gem::Specification.new do |spec|
18
18
  spec.require_paths = ['lib']
19
19
 
20
20
  spec.add_dependency 'rails'
21
- spec.add_dependency 'nokogiri'
22
21
 
23
22
  spec.add_development_dependency 'bundler'
24
23
  spec.add_development_dependency 'rake'
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.6.2
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: 2015-10-15 00:00:00.000000000 Z
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.4.8
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