exception_notification 4.1.4 → 4.2.0.rc1
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.
- checksums.yaml +4 -4
- data/Appraisals +6 -9
- data/CHANGELOG.rdoc +15 -2
- data/CODE_OF_CONDUCT.md +22 -0
- data/MIT-LICENSE +4 -1
- data/README.md +170 -6
- data/Rakefile +1 -9
- data/exception_notification.gemspec +7 -6
- data/gemfiles/rails4_0.gemfile +2 -1
- data/gemfiles/rails4_1.gemfile +1 -0
- data/gemfiles/rails4_2.gemfile +1 -0
- data/gemfiles/rails5_0.gemfile +8 -0
- data/lib/exception_notifier.rb +13 -1
- data/lib/exception_notifier/email_notifier.rb +23 -3
- data/lib/exception_notifier/mattermost_notifier.rb +159 -0
- data/lib/exception_notifier/slack_notifier.rb +4 -2
- data/lib/exception_notifier/views/exception_notifier/_request.html.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/_request.text.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/background_exception_notification.html.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/background_exception_notification.text.erb +1 -1
- data/lib/exception_notifier/webhook_notifier.rb +1 -0
- data/test/dummy/test/functional/posts_controller_test.rb +40 -46
- data/test/dummy/test/test_helper.rb +0 -6
- data/test/exception_notifier/email_notifier_test.rb +53 -27
- data/test/exception_notifier/mattermost_notifier_test.rb +88 -0
- data/test/exception_notifier/slack_notifier_test.rb +17 -4
- data/test/exception_notifier_test.rb +4 -1
- data/test/test_helper.rb +1 -1
- metadata +35 -18
- data/test/dummy/Gemfile +0 -34
- data/test/dummy/Gemfile.lock +0 -137
- data/test/dummy/test/fixtures/posts.yml +0 -11
@@ -1,4 +1,5 @@
|
|
1
1
|
require "active_support/core_ext/hash/reverse_merge"
|
2
|
+
require 'active_support/core_ext/time'
|
2
3
|
require 'action_mailer'
|
3
4
|
require 'action_dispatch'
|
4
5
|
require 'pp'
|
@@ -33,6 +34,7 @@ module ExceptionNotifier
|
|
33
34
|
@kontroller = env['action_controller.instance'] || MissingController.new
|
34
35
|
@request = ActionDispatch::Request.new(env)
|
35
36
|
@backtrace = exception.backtrace ? clean_backtrace(exception) : []
|
37
|
+
@timestamp = Time.current
|
36
38
|
@sections = @options[:sections]
|
37
39
|
@data = (env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
|
38
40
|
@sections = @sections + %w(data) unless @data.empty?
|
@@ -46,8 +48,10 @@ module ExceptionNotifier
|
|
46
48
|
@exception = exception
|
47
49
|
@options = options.reverse_merge(default_options)
|
48
50
|
@backtrace = exception.backtrace || []
|
51
|
+
@timestamp = Time.current
|
49
52
|
@sections = @options[:background_sections]
|
50
53
|
@data = options[:data] || {}
|
54
|
+
@env = @kontroller = nil
|
51
55
|
|
52
56
|
compose_email
|
53
57
|
end
|
@@ -94,10 +98,11 @@ module ExceptionNotifier
|
|
94
98
|
set_data_variables
|
95
99
|
subject = compose_subject
|
96
100
|
name = @env.nil? ? 'background_exception_notification' : 'exception_notification'
|
101
|
+
exception_recipients = maybe_call(@options[:exception_recipients])
|
97
102
|
|
98
103
|
headers = {
|
99
104
|
:delivery_method => @options[:delivery_method],
|
100
|
-
:to =>
|
105
|
+
:to => exception_recipients,
|
101
106
|
:from => @options[:sender_address],
|
102
107
|
:subject => subject,
|
103
108
|
:template_name => name
|
@@ -118,6 +123,10 @@ module ExceptionNotifier
|
|
118
123
|
self.prepend_view_path Rails.root.nil? ? "app/views" : "#{Rails.root}/app/views"
|
119
124
|
end
|
120
125
|
end
|
126
|
+
|
127
|
+
def maybe_call(maybe_proc)
|
128
|
+
maybe_proc.respond_to?(:call) ? maybe_proc.call : maybe_proc
|
129
|
+
end
|
121
130
|
end
|
122
131
|
end
|
123
132
|
end
|
@@ -150,7 +159,18 @@ module ExceptionNotifier
|
|
150
159
|
end
|
151
160
|
|
152
161
|
def call(exception, options={})
|
153
|
-
create_email(exception, options)
|
162
|
+
message = create_email(exception, options)
|
163
|
+
|
164
|
+
# FIXME: use `if Gem::Version.new(ActionMailer::VERSION::STRING) < Gem::Version.new('4.1')`
|
165
|
+
if deliver_with == :default
|
166
|
+
if message.respond_to?(:deliver_now)
|
167
|
+
message.deliver_now
|
168
|
+
else
|
169
|
+
message.deliver
|
170
|
+
end
|
171
|
+
else
|
172
|
+
message.send(deliver_with)
|
173
|
+
end
|
154
174
|
end
|
155
175
|
|
156
176
|
def create_email(exception, options={})
|
@@ -182,7 +202,7 @@ module ExceptionNotifier
|
|
182
202
|
:email_headers => {},
|
183
203
|
:mailer_parent => 'ActionMailer::Base',
|
184
204
|
:template_path => 'exception_notifier',
|
185
|
-
:deliver_with => :
|
205
|
+
:deliver_with => :default
|
186
206
|
}
|
187
207
|
end
|
188
208
|
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'action_dispatch'
|
2
|
+
require 'active_support/core_ext/time'
|
3
|
+
|
4
|
+
module ExceptionNotifier
|
5
|
+
class MattermostNotifier
|
6
|
+
include ExceptionNotifier::BacktraceCleaner
|
7
|
+
|
8
|
+
attr_accessor :httparty
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
super()
|
12
|
+
@default_options = options
|
13
|
+
@httparty = HTTParty
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(exception, options = {})
|
17
|
+
@options = options.merge(@default_options)
|
18
|
+
@exception = exception
|
19
|
+
@backtrace = exception.backtrace ? clean_backtrace(exception) : nil
|
20
|
+
|
21
|
+
@env = @options.delete(:env)
|
22
|
+
|
23
|
+
@application_name = @options.delete(:app_name) || Rails.application.class.parent_name.underscore
|
24
|
+
@gitlab_url = @options.delete(:git_url)
|
25
|
+
@username = @options.delete(:username) || "Exception Notifier"
|
26
|
+
@avatar = @options.delete(:avatar)
|
27
|
+
|
28
|
+
@channel = @options.delete(:channel)
|
29
|
+
@webhook_url = @options.delete(:webhook_url)
|
30
|
+
raise ArgumentError.new "You must provide 'webhook_url' parameter." unless @webhook_url
|
31
|
+
|
32
|
+
unless @env.nil?
|
33
|
+
@controller = @env['action_controller.instance'] || MissingController.new
|
34
|
+
|
35
|
+
request = ActionDispatch::Request.new(@env)
|
36
|
+
|
37
|
+
@request_items = { url: request.original_url,
|
38
|
+
http_method: request.method,
|
39
|
+
ip_address: request.remote_ip,
|
40
|
+
parameters: request.filtered_parameters,
|
41
|
+
timestamp: Time.current }
|
42
|
+
|
43
|
+
if request.session["warden.user.user.key"]
|
44
|
+
current_user = User.find(request.session["warden.user.user.key"][0][0])
|
45
|
+
@request_items.merge!({ current_user: { id: current_user.id, email: current_user.email } })
|
46
|
+
end
|
47
|
+
else
|
48
|
+
@controller = @request_items = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
payload = message_text.merge(user_info).merge(channel_info)
|
52
|
+
|
53
|
+
@options[:body] = payload.to_json
|
54
|
+
@options[:headers] ||= {}
|
55
|
+
@options[:headers].merge!({ 'Content-Type' => 'application/json' })
|
56
|
+
|
57
|
+
@httparty.post(@webhook_url, @options)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def channel_info
|
63
|
+
if @channel
|
64
|
+
{ channel: @channel }
|
65
|
+
else
|
66
|
+
{}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def user_info
|
71
|
+
infos = {}
|
72
|
+
|
73
|
+
infos.merge!({ username: @username }) if @username
|
74
|
+
infos.merge!({ icon_url: @avatar }) if @avatar
|
75
|
+
|
76
|
+
infos
|
77
|
+
end
|
78
|
+
|
79
|
+
def message_text
|
80
|
+
text = []
|
81
|
+
|
82
|
+
text += ["@channel"]
|
83
|
+
text += message_header
|
84
|
+
text += message_request if @request_items
|
85
|
+
text += message_backtrace if @backtrace
|
86
|
+
text += message_issue_link if @gitlab_url
|
87
|
+
|
88
|
+
{ text: text.join("\n") }
|
89
|
+
end
|
90
|
+
|
91
|
+
def message_header
|
92
|
+
text = []
|
93
|
+
|
94
|
+
text << "### :warning: Error 500 in #{Rails.env} :warning:"
|
95
|
+
text << "An *#{@exception.class}* occured" + if @controller then " in *#{controller_and_method}*." else "." end
|
96
|
+
text << "*#{@exception.message}*"
|
97
|
+
|
98
|
+
text
|
99
|
+
end
|
100
|
+
|
101
|
+
def message_request
|
102
|
+
text = []
|
103
|
+
|
104
|
+
text << "### Request"
|
105
|
+
text << "```"
|
106
|
+
text << hash_presentation(@request_items)
|
107
|
+
text << "```"
|
108
|
+
|
109
|
+
text
|
110
|
+
end
|
111
|
+
|
112
|
+
def message_backtrace(size = 3)
|
113
|
+
text = []
|
114
|
+
|
115
|
+
size = @backtrace.size < size ? @backtrace.size : size
|
116
|
+
text << "### Backtrace"
|
117
|
+
text << "```"
|
118
|
+
size.times { |i| text << "* " + @backtrace[i] }
|
119
|
+
text << "```"
|
120
|
+
|
121
|
+
text
|
122
|
+
end
|
123
|
+
|
124
|
+
def message_issue_link
|
125
|
+
text = []
|
126
|
+
|
127
|
+
link = [@gitlab_url, @application_name, "issues", "new"].join("/")
|
128
|
+
params = {
|
129
|
+
"issue[title]" => ["[BUG] Error 500 :",
|
130
|
+
controller_and_method,
|
131
|
+
"(#{@exception.class})",
|
132
|
+
@exception.message].compact.join(" ")
|
133
|
+
}.to_query
|
134
|
+
|
135
|
+
text << "[Create an issue](#{link}/?#{params})"
|
136
|
+
|
137
|
+
text
|
138
|
+
end
|
139
|
+
|
140
|
+
def controller_and_method
|
141
|
+
if @controller
|
142
|
+
"#{@controller.controller_name}##{@controller.action_name}"
|
143
|
+
else
|
144
|
+
""
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def hash_presentation(hash)
|
149
|
+
text = []
|
150
|
+
|
151
|
+
hash.each do |key, value|
|
152
|
+
text << "* #{key} : #{value}"
|
153
|
+
end
|
154
|
+
|
155
|
+
text.join("\n")
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
@@ -26,8 +26,10 @@ module ExceptionNotifier
|
|
26
26
|
clean_message = exception.message.gsub("`", "'")
|
27
27
|
fields = [ { title: 'Exception', value: clean_message} ]
|
28
28
|
|
29
|
+
fields.push({ title: 'Hostname', value: Socket.gethostname })
|
30
|
+
|
29
31
|
if exception.backtrace
|
30
|
-
formatted_backtrace = "```#{exception.backtrace.
|
32
|
+
formatted_backtrace = "```#{exception.backtrace.join("\n")}```"
|
31
33
|
fields.push({ title: 'Backtrace', value: formatted_backtrace })
|
32
34
|
end
|
33
35
|
|
@@ -40,7 +42,7 @@ module ExceptionNotifier
|
|
40
42
|
attchs = [color: 'danger', text: text, fields: fields, mrkdwn_in: %w(text fields)]
|
41
43
|
|
42
44
|
if valid?
|
43
|
-
send_notice(exception, options, clean_message, @message_opts.merge(attachments: attchs)) do |msg, message_opts|
|
45
|
+
send_notice(exception, options, clean_message, @message_opts.merge(attachments: attchs)) do |msg, message_opts|
|
44
46
|
@notifier.ping '', message_opts
|
45
47
|
end
|
46
48
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
* HTTP Method: <%= raw @request.request_method %>
|
3
3
|
* IP address : <%= raw @request.remote_ip %>
|
4
4
|
* Parameters : <%= raw safe_encode @request.filtered_parameters.inspect %>
|
5
|
-
* Timestamp : <%= raw
|
5
|
+
* Timestamp : <%= raw @timestamp %>
|
6
6
|
* Server : <%= raw Socket.gethostname %>
|
7
7
|
<% if defined?(Rails) && Rails.respond_to?(:root) %>
|
8
8
|
* Rails root : <%= raw Rails.root %>
|
data/lib/exception_notifier/views/exception_notifier/background_exception_notification.html.erb
CHANGED
@@ -30,7 +30,7 @@
|
|
30
30
|
<tr>
|
31
31
|
<td style="padding: 10px; border: 1px solid #eed3d7; background-color: #f2dede">
|
32
32
|
<h3 style="color: #b94a48">
|
33
|
-
<%= @exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A' %> <%= @exception.class %> occurred in background at <%=
|
33
|
+
<%= @exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A' %> <%= @exception.class %> occurred in background at <%= @timestamp %> :
|
34
34
|
</h3>
|
35
35
|
<p style="color: #b94a48"><%= @exception.message %></p>
|
36
36
|
<pre style="font-size: 12px; padding: 5px; background-color:#f5f5f5">
|
data/lib/exception_notifier/views/exception_notifier/background_exception_notification.text.erb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
<%= @exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A' %> <%= @exception.class %> occurred in background at <%= raw
|
1
|
+
<%= @exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A' %> <%= @exception.class %> occurred in background at <%= raw @timestamp %> :
|
2
2
|
|
3
3
|
<%= @exception.message %>
|
4
4
|
<%= @backtrace.first %>
|
@@ -5,8 +5,7 @@ class PostsControllerTest < ActionController::TestCase
|
|
5
5
|
Time.stubs(:current).returns('Sat, 20 Apr 2013 20:58:55 UTC +00:00')
|
6
6
|
@email_notifier = ExceptionNotifier.registered_exception_notifier(:email)
|
7
7
|
begin
|
8
|
-
|
9
|
-
post :create, :post => @post.attributes
|
8
|
+
post :create, method: :post, params: { secret: "secret" }
|
10
9
|
rescue => e
|
11
10
|
@exception = e
|
12
11
|
@mail = @email_notifier.create_email(@exception, {:env => request.env, :data => {:message => 'My Custom Message'}})
|
@@ -71,23 +70,22 @@ class PostsControllerTest < ActionController::TestCase
|
|
71
70
|
|
72
71
|
test "should not send notification if one of ignored exceptions" do
|
73
72
|
begin
|
74
|
-
get :
|
73
|
+
get :invalid
|
75
74
|
rescue => e
|
76
75
|
@ignored_exception = e
|
77
76
|
unless ExceptionNotifier.ignored_exceptions.include?(@ignored_exception.class.name)
|
78
|
-
|
77
|
+
ignored_mail = @email_notifier.create_email(@ignored_exception, {:env => request.env})
|
79
78
|
end
|
80
79
|
end
|
81
80
|
|
82
|
-
assert_equal @ignored_exception.class.inspect, "
|
83
|
-
assert_nil
|
81
|
+
assert_equal @ignored_exception.class.inspect, "ActionController::UrlGenerationError"
|
82
|
+
assert_nil ignored_mail
|
84
83
|
end
|
85
84
|
|
86
85
|
test "should filter session_id on secure requests" do
|
87
86
|
request.env['HTTPS'] = 'on'
|
88
87
|
begin
|
89
|
-
|
90
|
-
post :create, :post => @post.attributes
|
88
|
+
post :create, method: :post
|
91
89
|
rescue => e
|
92
90
|
@secured_mail = @email_notifier.create_email(e, {:env => request.env})
|
93
91
|
end
|
@@ -99,8 +97,7 @@ class PostsControllerTest < ActionController::TestCase
|
|
99
97
|
test "should ignore exception if from unwanted crawler" do
|
100
98
|
request.env['HTTP_USER_AGENT'] = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
|
101
99
|
begin
|
102
|
-
|
103
|
-
post :create, :post => @post.attributes
|
100
|
+
post :create, method: :post
|
104
101
|
rescue => e
|
105
102
|
@exception = e
|
106
103
|
custom_env = request.env
|
@@ -108,17 +105,16 @@ class PostsControllerTest < ActionController::TestCase
|
|
108
105
|
custom_env['exception_notifier.options'].merge!(:ignore_crawlers => %w(Googlebot))
|
109
106
|
ignore_array = custom_env['exception_notifier.options'][:ignore_crawlers]
|
110
107
|
unless ExceptionNotification::Rack.new(Dummy::Application, custom_env['exception_notifier.options']).send(:from_crawler, custom_env, ignore_array)
|
111
|
-
|
108
|
+
ignored_mail = @email_notifier.create_email(@exception, {:env => custom_env})
|
112
109
|
end
|
113
110
|
end
|
114
111
|
|
115
|
-
assert_nil
|
112
|
+
assert_nil ignored_mail
|
116
113
|
end
|
117
114
|
|
118
115
|
test "should send html email when selected html format" do
|
119
116
|
begin
|
120
|
-
|
121
|
-
post :create, :post => @post.attributes
|
117
|
+
post :create, method: :post
|
122
118
|
rescue => e
|
123
119
|
@exception = e
|
124
120
|
custom_env = request.env
|
@@ -136,8 +132,7 @@ class PostsControllerTestWithoutVerboseSubject < ActionController::TestCase
|
|
136
132
|
setup do
|
137
133
|
@email_notifier = ExceptionNotifier::EmailNotifier.new(:verbose_subject => false)
|
138
134
|
begin
|
139
|
-
|
140
|
-
post :create, :post => @post.attributes
|
135
|
+
post :create, method: :post
|
141
136
|
rescue => e
|
142
137
|
@exception = e
|
143
138
|
@mail = @email_notifier.create_email(@exception, {:env => request.env})
|
@@ -145,7 +140,9 @@ class PostsControllerTestWithoutVerboseSubject < ActionController::TestCase
|
|
145
140
|
end
|
146
141
|
|
147
142
|
test "should not include exception message in subject" do
|
148
|
-
|
143
|
+
assert_includes @mail.subject, '[ERROR]'
|
144
|
+
assert_includes @mail.subject, '(NoMethodError)'
|
145
|
+
refute_includes @mail.subject, 'undefined method'
|
149
146
|
end
|
150
147
|
end
|
151
148
|
|
@@ -158,8 +155,7 @@ class PostsControllerTestWithSmtpSettings < ActionController::TestCase
|
|
158
155
|
})
|
159
156
|
|
160
157
|
begin
|
161
|
-
|
162
|
-
post :create, :post => @post.attributes
|
158
|
+
post :create, method: :post
|
163
159
|
rescue => e
|
164
160
|
@exception = e
|
165
161
|
@mail = @email_notifier.create_email(@exception, {:env => request.env})
|
@@ -178,47 +174,45 @@ class PostsControllerTestWithSmtpSettings < ActionController::TestCase
|
|
178
174
|
end
|
179
175
|
end
|
180
176
|
|
181
|
-
class
|
177
|
+
class PostsControllerTestBackgroundNotification < ActionController::TestCase
|
182
178
|
tests PostsController
|
183
179
|
setup do
|
184
180
|
@email_notifier = ExceptionNotifier.registered_exception_notifier(:email)
|
185
181
|
begin
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
# still manages to send off an email in those cases.
|
190
|
-
#
|
191
|
-
# The trick here is to trigger an exception in the template used by
|
192
|
-
# ExceptionNotification. (The original test stuffed request.env with
|
193
|
-
# badly encoded strings, but that only works in Ruby 1.9+.)
|
194
|
-
request.send :instance_variable_set, :@env, {}
|
195
|
-
|
196
|
-
@post = posts(:one)
|
197
|
-
post :create, :post => @post.attributes
|
198
|
-
rescue => e
|
199
|
-
@exception = e
|
200
|
-
@mail = @email_notifier.create_email(@exception, {:env => request.env})
|
182
|
+
post :create, method: :post
|
183
|
+
rescue => exception
|
184
|
+
@mail = @email_notifier.create_email(exception)
|
201
185
|
end
|
202
186
|
end
|
203
187
|
|
204
|
-
test "should
|
205
|
-
|
188
|
+
test "mail should contain the specified section" do
|
189
|
+
assert_includes @mail.encoded, "* New background section for testing"
|
206
190
|
end
|
207
191
|
end
|
208
192
|
|
209
|
-
class
|
193
|
+
class PostsControllerTestWithExceptionRecipientsAsProc < ActionController::TestCase
|
210
194
|
tests PostsController
|
211
195
|
setup do
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
196
|
+
exception_recipients = %w{first@example.com second@example.com}
|
197
|
+
|
198
|
+
@email_notifier = ExceptionNotifier::EmailNotifier.new(
|
199
|
+
exception_recipients: -> { [ exception_recipients.shift ] }
|
200
|
+
)
|
201
|
+
|
202
|
+
@action = proc do
|
203
|
+
begin
|
204
|
+
post :create, method: :post
|
205
|
+
rescue => e
|
206
|
+
@exception = e
|
207
|
+
@mail = @email_notifier.create_email(@exception, {:env => request.env})
|
208
|
+
end
|
218
209
|
end
|
219
210
|
end
|
220
211
|
|
221
|
-
test "
|
222
|
-
|
212
|
+
test "should lazily evaluate exception_recipients" do
|
213
|
+
@action.call
|
214
|
+
assert_equal [ "first@example.com" ], @mail.to
|
215
|
+
@action.call
|
216
|
+
assert_equal [ "second@example.com" ], @mail.to
|
223
217
|
end
|
224
218
|
end
|