├── Gemfile
├── app
├── views
│ ├── airbrake
│ │ ├── index.html.erb
│ │ └── _issue_description.en.erb
│ └── airbrake_server_project_settings
│ │ └── _show.html.erb
├── controllers
│ ├── airbrake_server_project_settings_controller.rb
│ └── airbrake_controller.rb
└── models
│ └── airbrake_server_project_settings.rb
├── config
├── routes.rb
└── locales
│ └── en.yml
├── test
├── test_helper.rb
├── unit
│ └── airbrake_server_project_settings_test.rb
└── integration
│ └── airbrake_server_test.rb
├── lib
└── airbrake_server
│ └── hooks.rb
├── init.rb
├── db
└── migrate
│ └── 001_create_airbrake_server_project_settings.rb
└── README.md
/Gemfile:
--------------------------------------------------------------------------------
1 | gem "hpricot"
--------------------------------------------------------------------------------
/app/views/airbrake/index.html.erb:
--------------------------------------------------------------------------------
1 |
2 | <%= @issue.id %>
3 | <%= issue_url(@issue) %>
4 | <%= @issue.id %>
5 |
6 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | ActionController::Routing::Routes.draw do |map|
2 | map.connect '/notifier_api/v2/notices/', :controller => 'airbrake', :action => 'index', :conditions => { :method => :post }
3 | end
4 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # Load the normal Rails helper
2 | require File.expand_path(File.dirname(__FILE__) + '/../../../../test/test_helper')
3 |
4 | # Ensure that we are using the temporary fixture path
5 | Engines::Testing.set_fixture_path
6 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | airbrake:
3 | heading: Airbrake Server
4 | label: Airbrake Server
5 | fixed_version: Default target version
6 | category: Default issue category
7 | assign_to: Assign new tickets to user
8 | author: Author for new tickets
9 | priority: Priority for new tickets
10 | tracker: Tracker for new tickets
11 | reopen_strategy: Don't reopen tickets belonging to environment
12 |
--------------------------------------------------------------------------------
/lib/airbrake_server/hooks.rb:
--------------------------------------------------------------------------------
1 | module AirbrakeServer
2 | module Hooks
3 | class ProjectSettingsTabHook < Redmine::Hook::ViewListener
4 | def helper_projects_settings_tabs(tabs)
5 | tabs[:tabs] << {:name => 'airbrake_server', :controller => 'airbrake_server_project_settings', :action => :show, :partial => 'airbrake_server_project_settings/show', :label => 'airbrake.heading'}
6 | tabs
7 | end
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/init.rb:
--------------------------------------------------------------------------------
1 | require 'redmine'
2 | require_dependency 'airbrake_server/hooks'
3 |
4 | Redmine::Plugin.register :redmine_airbrake_server do
5 | name 'Redmine Airbrake Server plugin'
6 | author 'Marcus Ilgner'
7 | description 'Allows Redmine to receive error notifications Airbrake-style'
8 | version '0.2'
9 | url 'https://github.com/milgner/redmine_airbrake_server'
10 | author_url 'http://marcusilgner.com'
11 |
12 | requires_redmine_plugin :project_settings_hook_plugin, :version_or_higher => '0.0.1'
13 | end
14 |
15 | config.gem 'hpricot'
16 |
--------------------------------------------------------------------------------
/app/controllers/airbrake_server_project_settings_controller.rb:
--------------------------------------------------------------------------------
1 | class AirbrakeServerProjectSettingsController < ApplicationController
2 | unloadable
3 |
4 | before_filter :load_project_and_settings
5 |
6 | def update
7 | @settings.update_attributes(params[:settings])
8 | redirect_to :controller => 'projects', :action => "settings", :id => @project, :tab => 'airbrake_server'
9 | end
10 |
11 | private
12 |
13 | def load_project_and_settings
14 | @project = Project.find(params[:project_id])
15 | @settings = AirbrakeServerProjectSettings.find_or_create(@project)
16 | end
17 | end
--------------------------------------------------------------------------------
/db/migrate/001_create_airbrake_server_project_settings.rb:
--------------------------------------------------------------------------------
1 | class CreateAirbrakeServerProjectSettings < ActiveRecord::Migration
2 | def self.up
3 | create_table :airbrake_server_project_settings do |t|
4 | t.column :project_id, :integer
5 | t.column :fixed_version_id, :integer
6 | t.column :category_id, :integer
7 | t.column :assign_to_id, :integer
8 | t.column :author_id, :integer
9 | t.column :tracker_id, :integer
10 | t.column :reopen_strategy, :string
11 | t.column :priority_id, :integer
12 | end
13 | end
14 |
15 | def self.down
16 | drop_table :airbrake_server_project_settings
17 | end
18 | end
--------------------------------------------------------------------------------
/test/unit/airbrake_server_project_settings_test.rb:
--------------------------------------------------------------------------------
1 | class ActivityTest < ActiveSupport::TestCase
2 | fixtures :projects, :issue_categories, :versions, :users,
3 | :trackers, :projects_trackers, :issue_statuses, :enumerations
4 |
5 | def test_find_or_create
6 | p = Project.find(1)
7 | s = AirbrakeServerProjectSettings.find_or_create(p)
8 | assert_equal p, s.project
9 | assert_equal User.anonymous, s.author
10 | assert_equal p.versions.last, s.fixed_version
11 | assert_not_nil s.category
12 | assert_equal p.issue_categories.first, s.category
13 | assert_equal p.trackers.first, s.tracker
14 | end
15 |
16 | end
17 |
--------------------------------------------------------------------------------
/app/models/airbrake_server_project_settings.rb:
--------------------------------------------------------------------------------
1 | class AirbrakeServerProjectSettings < ActiveRecord::Base
2 | belongs_to :project
3 |
4 | belongs_to :tracker
5 | belongs_to :category, :class_name => 'IssueCategory'
6 | belongs_to :fixed_version, :class_name => 'Version'
7 |
8 | belongs_to :author, :class_name => 'User'
9 | belongs_to :assign_to, :class_name => 'User'
10 | belongs_to :priority, :class_name => 'IssuePriority'
11 |
12 | def self.find_or_create(project)
13 | settings = find_by_project_id(project.id)
14 | unless settings
15 | settings = AirbrakeServerProjectSettings.new
16 | settings.project = project
17 | settings.author = User.anonymous
18 | settings.fixed_version = project.versions.last
19 | settings.category = project.issue_categories.first
20 | settings.tracker = project.trackers.first
21 | settings.save
22 | end
23 | settings
24 | end
25 | end
--------------------------------------------------------------------------------
/app/views/airbrake/_issue_description.en.erb:
--------------------------------------------------------------------------------
1 | h3. <%= @notice['error']['message'] %>
2 |
3 | <% if @notice['error']['backtrace']['line'].is_a?(Array) %>
4 | h4. Backtrace:
5 |
6 | p((. <% @notice['error']['backtrace']['line'].each do |element|
7 | if (element['file'].start_with?('[PROJECT_ROOT]') && @settings[:project].repository && @settings[:project].repository.entry(element['file'][14..-1])) # don't create source link if the file isn't in the repository
8 | %>source:"<%= element['file'][14..-1] %>#L<%= element['number'] %>" in ??<%= element['method'] %>??<%
9 | else
10 | %>@<%=element['file']%>:<%=element['number']%>@ in ??<%=element['method']%>??<%
11 | end %>
12 | <% end %>
13 | <% end %>
14 | <% unless @notice['request'].nil? %>
15 | h4. Request:
16 |
17 | p((. URL: <%= @notice['request']['url'] %>
18 | Component: <%= @notice['request']['component']%>
19 | Action: <%= @notice['request']['action'] %>
20 |
21 | <% def explode_vars(vars)
22 | result = "|_.Key|_.Value|\n"
23 | vars.each_pair do |key, value|
24 | result << "|#{key}|#{value}|\n"
25 | end
26 | result
27 | end
28 |
29 | unless (@notice['request']['params'].nil?) %>
30 | h4. Parameters:
31 |
32 | <%= explode_vars(@notice['request']['params']) %>
33 | <% end %>
34 |
35 | <% # remove the 'true ||' bit if you want to save headers
36 | unless true || (@notice['request']['cgi_data'].nil?) %>
37 | h4. Headers:
38 |
39 | <%= explode_vars(@notice['request']['cgi_data']) %>
40 | <% end %>
41 | <% unless (@notice['request']['session'].nil?) %>
42 | h4. Session:
43 |
44 | <%= explode_vars(@notice['request']['session']) %>
45 | <% end %>
46 |
47 | <% end %>
48 |
--------------------------------------------------------------------------------
/app/views/airbrake_server_project_settings/_show.html.erb:
--------------------------------------------------------------------------------
1 | <%
2 | @settings = AirbrakeServerProjectSettings.find_or_create(@project)
3 | %>
4 | <% form_for :settings, :url => { :controller => 'airbrake_server_project_settings', :action => 'update' } do |f| -%>
5 | <%= hidden_field_tag 'project_id', @project.id %>
6 |
7 |
8 | | <%= f.label 'author_id', l('airbrake.author') %> |
9 | <%= f.select 'author_id', @project.members.collect {|u| [ u.name, u.id ] }, {:include_blank => false} %> |
10 |
11 |
12 | | <%= f.label 'assign_to_id', l('airbrake.assign_to') %> |
13 | <%= f.select 'assign_to_id', @project.members.collect{|u| [ u.name, u.id ]}, {:include_blank => true} %> |
14 |
15 |
16 | | <%= f.label 'priority_id', l('airbrake.priority') %> |
17 | <%= f.select 'priority_id', IssuePriority.all.collect{|ip| [ip.name, ip.id]} %> |
18 |
19 |
20 | | <%= f.label 'tracker_id', l('airbrake.tracker') %> |
21 | <%= f.select 'tracker_id', @project.trackers.collect{|t| [t.name, t.id]} %> |
22 |
23 |
24 | | <%= f.label 'reopen_strategy', l('airbrake.reopen_strategy') %> |
25 | <%= f.text_field 'reopen_strategy', {:size => 20} %> |
26 |
27 |
28 | | <%= f.label 'fixed_version_id', l('airbrake.fixed_version')%> |
29 | <%= f.select 'fixed_version_id', @project.versions.collect{|v| [v.name, v.id]} %> |
30 |
31 |
32 | | <%= f.label 'category_id', l('airbrake.category')%> |
33 | <%= f.select 'category_id', @project.issue_categories.all.collect{|ic| [ic.name, ic.id]} %> |
34 |
35 |
36 | <%= f.submit t('button_save') %>
37 |
38 | <% end -%>
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Airbrake Server for Redmine
2 |
3 | An implementation of the [Airbrake](http://airbrakeapp.com) (formerly known as Hoptoad) protocol v2 for Redmine which automatically creates issues from notices submitted from your applications notifier.
4 | Inspired by and loosely based on the [Hoptoad server by Jan Schulz-Hofen](https://github.com/yeah/redmine_hoptoad_server)
5 |
6 |
7 | ## Installation
8 |
9 | Install the hpricot gem, then install the plugin into Redmine [as usual](http://www.redmine.org/projects/redmine/wiki/Plugins).
10 | Now go to _Administration -> Settings -> Incoming emails_ and, if neccessary, check _Enable WS for incoming emails_ and generate an API key. This is the key you will need in the next step to configure your notifier.
11 |
12 |
13 | ## Client-Configuration
14 |
15 | For a Rails application, just setup the [Airbrake notifier](https://github.com/airbrake/airbrake) as usual, then modify `config/initializers/airbrake.rb` according to your needs using this template:
16 |
17 | HoptoadNotifier.configure do |config|
18 | config.api_key = {:project => 'redmine_project_identifier', # the identifier you specified for your project in Redmine
19 | :tracker => 'Bug', # the name of your desired tracker
20 | :api_key => 'redmine_api_key', # the key you generated in the previous step
21 | :category => 'Development', # the name of a ticket category, optional
22 | :fixed_version_id => 25, # the default version, optional
23 | :assigned_to => 'admin', # the login of a user the ticket should get assigned to by default, optional
24 | :login => 'airbrake', # the login who should be displayed as author of the tickets. Defaults to anonymous.
25 | :reopen_strategy => 'production', # will only reopen if the error occurs on the specified environment. Defaults to all, optional.
26 | :priority => 5 # the default priority (use id instead of name, optional.)
27 | }.to_yaml
28 | config.host = 'my_redmine_host.com' # the hostname your Redmine runs at
29 | config.port = 443 # the port your Redmine runs at
30 | config.secure = true # sends data to your server using SSL, optional
31 | end
32 |
33 | Note: as of version 0.2, it is possible to specify defaults for all settings except :project and :api_key on the server using the web interface. Settings supplied by the client will take precedence however.
34 |
35 | That's it. You may run `rake airbrake:test` to generate a test issue.
36 | If it doesn't work, check your logs and configuration, then [submit an issue on Github](https://github.com/milgner/redmine_airbrake_server/issues)
37 |
38 |
39 | ## License
40 |
41 | This plugin is licensed under the [Apache license 2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
42 |
43 |
44 | ## Author
45 |
46 | Written by Marcus Ilgner (mail@marcusilgner.com)
47 |
48 | [](http://flattr.com/thing/307397/Airbrake-server-plugin-for-the-Redmine-issue-tracker)
49 |
--------------------------------------------------------------------------------
/test/integration/airbrake_server_test.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../../test_helper', __FILE__)
2 |
3 | class AirbrakeServerTest < ActionController::IntegrationTest
4 | NOTIFIER_URL = '/notifier_api/v2/notices/'
5 |
6 | fixtures :projects, :versions, :users, :trackers, :projects_trackers, :issue_statuses, :enabled_modules, :enumerations
7 |
8 | @@xml_notice_data = <
10 | Airbrake Notifier
11 | 1.2.4
12 | http://airbrakeapp.com
13 |
14 |
15 | RuntimeError
16 | RuntimeError: I've made a huge mistake
17 |
18 |
19 |
20 |
21 |
22 |
23 | http://example.com
24 |
25 |
26 |
27 | example.org
28 | Mozilla
29 |
30 |
31 |
32 | /testapp
33 | production
34 | 1.0.0
35 |
36 |
37 | EOF
38 |
39 | @@xml_notice_data_full = "
40 |
41 | --- \n:project: ecookbook\n:priority: 5\n:assigned_to: jsmith\n:fixed_version_id: 3\n:tracker: Bug\n:login: jsmith\n:category: Development\n:api_key: \"1234567890\"\n:reopen_strategy: production\n" + @@xml_notice_data
42 |
43 | @@xml_notice_data_slim = "
44 |
45 | --- \n:api_key: \"1234567890\"\n:project: ecookbook\n" + @@xml_notice_data
46 |
47 | def setup
48 | Setting['mail_handler_api_key'] = "1234567890"
49 | end
50 |
51 | def test_routing
52 | assert_routing(
53 | {:method => :post, :path => '/notifier_api/v2/notices'},
54 | :controller => 'airbrake', :action => 'index'
55 | )
56 | end
57 |
58 | def test_create_new_issues
59 | i = post_and_find_issue(@@xml_notice_data_full)
60 | # 8b6a2e77 = Digest::MD5.hexdigest("RuntimeError: I've made a huge mistake")[0..7]
61 | assert_equal '[Airbrake] 8b6a2e77 RuntimeError in /testapp/app/models/user.rb:53', i.subject
62 | assert i.description.include?("RuntimeError: I've made a huge mistake")
63 | assert i.description.include?("/testapp/app/controllers/users_controller.rb")
64 | assert_equal "production", i.custom_value_for(IssueCustomField.find_by_name('Environment')).value
65 | assert_equal "1.0.0", i.custom_value_for(IssueCustomField.find_by_name('Version')).value
66 | assert_equal 'jsmith', i.author.login
67 | assert_equal 3, i.fixed_version_id
68 | end
69 |
70 | def test_with_local_settings
71 | AirbrakeServerProjectSettings.create(:project_id => 1,
72 | :category_id => 1,
73 | :fixed_version_id => 3,
74 | :tracker_id => 1,
75 | :author_id => 2,
76 | :assign_to_id => 3,
77 | :priority_id => 5,
78 | :reopen_strategy => 'production')
79 | s = AirbrakeServerProjectSettings.find_by_project_id(1)
80 | assert_equal 1, s.category_id
81 | i = post_and_find_issue(@@xml_notice_data_slim)
82 | assert_equal(1, i.category_id)
83 | assert_equal(3, i.fixed_version_id)
84 | assert_equal(2, i.author_id)
85 | assert_equal(5, i.priority_id)
86 | assert_equal(3, i.assigned_to_id)
87 | end
88 |
89 | def test_increase_occurrences_for_existing_issues
90 | i = post_and_find_issue(@@xml_notice_data_full)
91 | assert_equal "1", i.custom_value_for(IssueCustomField.find_by_name('# Occurrences').id).value
92 | i2 = post_and_find_issue(@@xml_notice_data_full)
93 | assert_equal i, i2
94 | assert_equal "2", i2.custom_value_for(IssueCustomField.find_by_name('# Occurrences').id).value
95 | end
96 |
97 | def test_reopen_journal
98 | i = post_and_find_issue(@@xml_notice_data_full)
99 | i.status = IssueStatus.find(:first, :conditions => {:is_closed => true}, :order => 'position ASC')
100 | i.save
101 | assert_equal 0, i.journals.size
102 | i2 = post_and_find_issue(@@xml_notice_data_full)
103 | assert !i2.status.is_closed?
104 | assert_equal 1, i2.journals.size
105 | end
106 |
107 | def test_settings_page
108 | log_user("admin", "admin")
109 | get(url_for(:controller => 'projects', :action => "settings", :id => 'ecookbook', :tab => 'airbrake_server'))
110 | assert_response :success
111 | end
112 |
113 | private
114 |
115 | #def filtered_backtrace
116 | # if exception = @response.template.instance_variable_get(:@exception)
117 | # filter_backtrace(exception.backtrace).join("\n")
118 | # end
119 | #end
120 |
121 | def post_and_find_issue(issue_data)
122 | post(NOTIFIER_URL, issue_data, {"Content-type" => "text/xml"})
123 | assert_response :success #, filtered_backtrace
124 | Issue.find :last
125 | end
126 | end
127 |
--------------------------------------------------------------------------------
/app/controllers/airbrake_controller.rb:
--------------------------------------------------------------------------------
1 | require 'hpricot'
2 |
3 | class AirbrakeController < ApplicationController
4 | skip_before_filter :check_if_login_required
5 | before_filter :find_or_create_custom_fields
6 |
7 | unloadable
8 |
9 | def index
10 | redirect_to root_path unless request.post? && !params[:notice].nil?
11 | @notice = params[:notice]
12 | if (@notice['version'] != "2.0")
13 | logger.warn("Expected Airbrake notice version 2.0 but got #{@notice['version']}. You should consider filing an enhancement request or updating the plugin.")
14 | end
15 |
16 | restore_var_elements(request.body)
17 |
18 | redmine_params = YAML.load(@notice['api_key'])
19 | raise ArgumentError.new("Invalid API key #{Setting.mail_handler_api_key} != #{redmine_params[:api_key]}") unless Setting.mail_handler_api_key == redmine_params[:api_key]
20 |
21 | read_settings(redmine_params)
22 |
23 | subject = build_subject
24 | @issue = Issue.find_by_subject_and_project_id_and_tracker_id(subject, @settings[:project].id, @settings[:tracker].id)
25 |
26 | if @issue.nil?
27 | create_new_issue
28 | else
29 | update_existing_issue
30 | end
31 |
32 | render :layout => false
33 | end
34 |
35 | private
36 |
37 | # The automagic XML parsing by Rails ignores the text elements
38 | # This method replaces the garbled elements with new hashes
39 | def restore_var_elements(original_xml)
40 | return if @notice['request'].nil?
41 |
42 | doc = Hpricot::XML(request.body)
43 |
44 | unless @notice['request']['params'].nil?
45 | request_params = convert_var_elements(doc/'/notice/request/params/var')
46 | request_params.delete('action') # already known
47 | request_params.delete('controller') # already known
48 | @notice['request']['params'] = request_params
49 | end
50 |
51 | unless @notice['request']['cgi_data'].nil?
52 | cgi_data = convert_var_elements(doc/'notice/request/cgi-data/var')
53 | @notice['request']['cgi_data'] = cgi_data
54 | end
55 |
56 | unless @notice['request']['session'].nil?
57 | session_vars = convert_var_elements(doc/'/notice/request/session/var')
58 | @notice['request']['session'] = session_vars
59 | end
60 | end
61 |
62 | def convert_var_elements(elements)
63 | result = {}
64 | elements.each do |elem|
65 | result[elem.attributes['key']] = elem.inner_text
66 | end
67 | result
68 | end
69 |
70 | def read_settings(params)
71 | project = Project.find_by_identifier(params[:project]) or raise ArgumentError.new("invalid project #{params[:project]}")
72 | @settings = {}
73 | @settings[:project] = project
74 | @settings[:tracker] = project.trackers.find_by_name(params[:tracker]) if params.has_key?(:tracker)
75 | # these are optional
76 | [:reopen_strategy, :fixed_version_id].each do |key|
77 | @settings[key] = params[key] if params.has_key?(key)
78 | end
79 | @settings[:priority] = IssuePriority.find_by_id(params[:priority]) if params.has_key?(:priority)
80 | @settings[:author] = User.find_by_login(params[:login]) if params.has_key?(:login)
81 | @settings[:category] = IssueCategory.find_by_name(params[:category]) if params.has_key?(:category)
82 | @settings[:assign_to] = User.find_by_login(params[:assigned_to]) if params.has_key?(:assigned_to)
83 |
84 | read_local_settings
85 | check_custom_field_assignments
86 | end
87 |
88 | def read_local_settings
89 | local_settings = AirbrakeServerProjectSettings.find_by_project_id(@settings[:project].id)
90 | return if local_settings.nil?
91 | [:author, :priority, :reopen_strategy, :tracker, :category, :assign_to, :fixed_version_id].each do |key|
92 | @settings[key] = local_settings.send(key.to_s) unless @settings.has_key?(key)
93 | end
94 | end
95 |
96 | def create_new_issue
97 | @issue = Issue.new
98 | @issue.author = @settings[:author]
99 | @issue.subject = build_subject
100 | @issue.tracker = @settings[:tracker]
101 | @issue.project = @settings[:project]
102 | @issue.category = @settings[:category]
103 | @issue.fixed_version_id = @settings[:fixed_version_id]
104 | @issue.assigned_to = @settings[:assign_to]
105 | @issue.priority = @settings[:priority] unless @settings[:priority].nil?
106 | @issue.description = render_to_string(:partial => 'issue_description')
107 | @issue.status = issue_status_open
108 | @issue.custom_values.build(:custom_field => @occurrences_field, :value => '1')
109 | @issue.custom_values.build(:custom_field => @environment_field, :value => @notice['server_environment']['environment_name'])
110 | @issue.custom_values.build(:custom_field => @version_field, :value => @notice['server_environment']['app_version']) unless @notice['server_environment']['app_version'].nil?
111 | @issue.save!
112 | end
113 |
114 | def check_custom_field_assignments
115 | [@occurrences_field, @environment_field, @version_field].each do |field|
116 | @settings[:project].issue_custom_fields << field unless @settings[:project].issue_custom_fields.include?(field)
117 | @settings[:tracker].custom_fields << field unless @settings[:tracker].custom_fields.include?(field)
118 | end
119 | end
120 |
121 | def update_existing_issue
122 | environment_name = @notice['server_environment']['environment_name']
123 | if (['always', environment_name].include?(@settings[:reopen_strategy]))
124 | @issue.status = issue_status_open if @issue.status.is_closed?
125 | @issue.init_journal(@settings[:author], render_to_string(:partial => 'issue_description'))
126 | end
127 | number_occurrences = @issue.custom_value_for(@occurrences_field.id).value
128 | @issue.custom_field_values = { @occurrences_field.id => (number_occurrences.to_i+1).to_s }
129 | @issue.save!
130 | end
131 |
132 | def issue_status_open
133 | IssueStatus.find(:first, :conditions => {:is_default => true}, :order => 'position ASC')
134 | end
135 |
136 | def build_subject
137 | error_class = @notice['error']['message']
138 | # if there's only one line, it gets parsed into a hash instead of an array
139 | if @notice['error']['backtrace']['line'].is_a? Hash
140 | file = @notice['error']['backtrace']['line']['file']
141 | line = @notice['error']['backtrace']['line']['number']
142 | else
143 | file = @notice['error']['backtrace']['line'].first()['file']
144 | line = @notice['error']['backtrace']['line'].first()['number']
145 | end
146 | "[Airbrake] #{build_message_hash} #{error_class} in #{file}:#{line}"[0..254]
147 | end
148 |
149 | def build_message_hash
150 | Digest::MD5.hexdigest(@notice['error']['message'])[0..7]
151 | end
152 |
153 | def find_or_create_custom_fields
154 | @occurrences_field = IssueCustomField.find_or_initialize_by_name('# Occurrences')
155 | if @occurrences_field.new_record?
156 | @occurrences_field.attributes = {:field_format => 'int', :default_value => '1', :is_filter => true}
157 | @occurrences_field.save(false)
158 | end
159 |
160 | @environment_field = IssueCustomField.find_or_initialize_by_name('Environment')
161 | if @environment_field.new_record?
162 | @environment_field.attributes = {:field_format => 'string', :default_value => 'production', :is_filter => true}
163 | @environment_field.save(false)
164 | end
165 |
166 | @version_field = IssueCustomField.find_or_initialize_by_name('Version')
167 | if @version_field.new_record?
168 | @version_field.attributes = {:field_format => 'string', :default_value => '', :is_filter => true}
169 | @version_field.save(false)
170 | end
171 | end
172 | end
173 |
--------------------------------------------------------------------------------