get sentry into canvas
closes CNVS-6016 No more error reports! (soon) this commit builds up sentry integration through the new Canvas::Errors module, along with other things that need to happen on every exception. ErrorReports should now get pushed towards just being used for representing a complaint a user filed via the get help form. I fixed about half the things that got linted as well while I was in here, but because this touches to much I fear divergence from tackling too many (I think we can safely say it's "better than we found it") I left a lot of the infrastructure for error reports in place until other commits for plugins can be merged TEST PLAN: 1) setup your raven.yml config file with the dsn for our sentry install 2) force an error to happen in a request response cycle. 3) see the error in sentry 4) force an error to happen in a job 5) see the error in sentry 6) statsd increments shoudl still fire 7) for the moment, an error report should still get created. Change-Id: I5a9dc7214598f8d5083451fd15f0423f8f939034 Reviewed-on: https://gerrit.instructure.com/51621 Reviewed-by: Simon Williams <simon@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com> Tested-by: Jenkins QA-Review: August Thornton <august@instructure.com> Product-Review: Ethan Vizitei <evizitei@instructure.com>
This commit is contained in:
parent
bd52b0a491
commit
1004e66540
|
@ -35,7 +35,7 @@ gem 'bcrypt-ruby', '3.0.1'
|
||||||
gem 'canvas_connect', '0.3.7'
|
gem 'canvas_connect', '0.3.7'
|
||||||
gem 'adobe_connect', '1.0.2', require: false
|
gem 'adobe_connect', '1.0.2', require: false
|
||||||
gem 'canvas_webex', '0.15'
|
gem 'canvas_webex', '0.15'
|
||||||
gem 'canvas-jobs', '0.9.11'
|
gem 'canvas-jobs', '0.9.12'
|
||||||
|
|
||||||
gem 'ffi', '1.1.5', require: false
|
gem 'ffi', '1.1.5', require: false
|
||||||
gem 'hairtrigger', '0.2.12'
|
gem 'hairtrigger', '0.2.12'
|
||||||
|
@ -97,6 +97,7 @@ gem 'foreigner', '0.9.2'
|
||||||
gem 'crocodoc-ruby', '0.0.1', require: false
|
gem 'crocodoc-ruby', '0.0.1', require: false
|
||||||
gem 'hey', '1.3.0', require: false
|
gem 'hey', '1.3.0', require: false
|
||||||
gem 'aroi', '0.0.2'
|
gem 'aroi', '0.0.2'
|
||||||
|
gem 'sentry-raven', '0.12.3', require: false
|
||||||
|
|
||||||
gem 'active_polymorph', path: 'gems/active_polymorph'
|
gem 'active_polymorph', path: 'gems/active_polymorph'
|
||||||
gem 'activesupport-suspend_callbacks', path: 'gems/activesupport-suspend_callbacks'
|
gem 'activesupport-suspend_callbacks', path: 'gems/activesupport-suspend_callbacks'
|
||||||
|
|
|
@ -932,7 +932,7 @@ class ApplicationController < ActionController::Base
|
||||||
logger.fatal("#{message}\n\n")
|
logger.fatal("#{message}\n\n")
|
||||||
end
|
end
|
||||||
|
|
||||||
if config.consider_all_requests_local || local_request?
|
if config.consider_all_requests_local
|
||||||
rescue_action_locally(exception)
|
rescue_action_locally(exception)
|
||||||
else
|
else
|
||||||
rescue_action_in_public(exception)
|
rescue_action_in_public(exception)
|
||||||
|
@ -971,23 +971,11 @@ class ApplicationController < ActionController::Base
|
||||||
type = '404' if status == '404 Not Found'
|
type = '404' if status == '404 Not Found'
|
||||||
|
|
||||||
unless exception.respond_to?(:skip_error_report?) && exception.skip_error_report?
|
unless exception.respond_to?(:skip_error_report?) && exception.skip_error_report?
|
||||||
error_info = {
|
opts = {type: type}
|
||||||
:url => request.url,
|
info = Canvas::Errors::Info.new(request, @domain_root_account, @current_user, opts)
|
||||||
:user => @current_user,
|
error_info = info.to_h
|
||||||
:user_agent => request.headers['User-Agent'],
|
capture_outputs = Canvas::Errors.capture(exception, error_info)
|
||||||
:request_context_id => RequestContextGenerator.request_id,
|
error = ErrorReport.find(capture_outputs[:error_report])
|
||||||
:account => @domain_root_account,
|
|
||||||
:request_method => request.request_method_symbol,
|
|
||||||
:format => request.format,
|
|
||||||
}.merge(ErrorReport.useful_http_env_stuff_from_request(request))
|
|
||||||
|
|
||||||
error = if @domain_root_account
|
|
||||||
@domain_root_account.shard.activate do
|
|
||||||
ErrorReport.log_exception(type, exception, error_info)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
ErrorReport.log_exception(type, exception, error_info)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if api_request?
|
if api_request?
|
||||||
|
@ -997,8 +985,8 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
# error generating the error page? failsafe.
|
# error generating the error page? failsafe.
|
||||||
|
Canvas::Errors.capture(e)
|
||||||
render_optional_error_file response_code_for_rescue(exception)
|
render_optional_error_file response_code_for_rescue(exception)
|
||||||
ErrorReport.log_exception(:default, e)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1096,10 +1084,6 @@ class ApplicationController < ActionController::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def local_request?
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def claim_session_course(course, user, state=nil)
|
def claim_session_course(course, user, state=nil)
|
||||||
e = course.claim_with_teacher(user)
|
e = course.claim_with_teacher(user)
|
||||||
session[:claimed_enrollment_uuids] ||= []
|
session[:claimed_enrollment_uuids] ||= []
|
||||||
|
|
|
@ -173,12 +173,12 @@ class AssignmentsController < ApplicationController
|
||||||
docs = {}
|
docs = {}
|
||||||
begin
|
begin
|
||||||
docs = google_service_connection.list_with_extension_filter(assignment.allowed_extensions)
|
docs = google_service_connection.list_with_extension_filter(assignment.allowed_extensions)
|
||||||
rescue GoogleDocs::NoTokenError
|
rescue GoogleDocs::NoTokenError => e
|
||||||
#do nothing
|
CanvasErrors.capture_exception(:oauth, e)
|
||||||
rescue ArgumentError
|
rescue ArgumentError => e
|
||||||
#do nothing
|
CanvasErrors.capture_exception(:oauth, e)
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:oauth, e)
|
CanvasErrors.capture_exception(:oauth, e)
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -290,13 +290,14 @@ class ContextController < ApplicationController
|
||||||
def roster_user
|
def roster_user
|
||||||
if authorized_action(@context, @current_user, :read_roster)
|
if authorized_action(@context, @current_user, :read_roster)
|
||||||
if params[:id] !~ Api::ID_REGEX
|
if params[:id] !~ Api::ID_REGEX
|
||||||
# todo stop generating an error report and fix the bad input
|
# TODO: stop generating an error report and fix the bad input
|
||||||
ErrorReport.log_error('invalid_user_id',
|
|
||||||
{message: "invalid user_id in ContextController::roster_user",
|
env_stuff = Canvas::Errors::Info.useful_http_env_stuff_from_request(request)
|
||||||
current_user_id: @current_user.id,
|
Canvas::Errors.capture('invalid_user_id', {
|
||||||
current_user_name: @current_user.sortable_name}.
|
message: "invalid user_id in ContextController::roster_user",
|
||||||
merge(ErrorReport.useful_http_env_stuff_from_request(request))
|
current_user_id: @current_user.id,
|
||||||
)
|
current_user_name: @current_user.sortable_name
|
||||||
|
}.merge(env_stuff))
|
||||||
raise ActiveRecord::RecordNotFound
|
raise ActiveRecord::RecordNotFound
|
||||||
end
|
end
|
||||||
user_id = Shard.relative_id_for(params[:id], Shard.current, @context.shard)
|
user_id = Shard.relative_id_for(params[:id], Shard.current, @context.shard)
|
||||||
|
|
|
@ -52,19 +52,23 @@ class InfoController < ApplicationController
|
||||||
@report.account ||= @domain_root_account
|
@report.account ||= @domain_root_account
|
||||||
backtrace = params[:error].delete(:backtrace) rescue nil
|
backtrace = params[:error].delete(:backtrace) rescue nil
|
||||||
backtrace ||= ""
|
backtrace ||= ""
|
||||||
backtrace += "\n\n-----------------------------------------\n\n" + @report.backtrace if @report.backtrace
|
if @report.backtrace
|
||||||
|
backtrace += "\n\n-----------------------------------------\n\n"
|
||||||
|
backtrace += @report.backtrace
|
||||||
|
end
|
||||||
@report.backtrace = backtrace
|
@report.backtrace = backtrace
|
||||||
@report.http_env ||= ErrorReport.useful_http_env_stuff_from_request(request)
|
@report.http_env ||= Canvas::Errors::Info.useful_http_env_stuff_from_request(request)
|
||||||
@report.request_context_id = RequestContextGenerator.request_id
|
@report.request_context_id = RequestContextGenerator.request_id
|
||||||
@report.assign_data(error)
|
@report.assign_data(error)
|
||||||
@report.save
|
@report.save
|
||||||
@report.send_later(:send_to_external)
|
@report.send_later(:send_to_external)
|
||||||
rescue => e
|
rescue => e
|
||||||
@exception = e
|
@exception = e
|
||||||
ErrorReport.log_exception(:default, e,
|
Canvas::Errors.capture(
|
||||||
:message => "Error Report Creation failed",
|
e,
|
||||||
:user_email => (error[:email] rescue ''),
|
message: "Error Report Creation failed",
|
||||||
:user_id => @current_user.try(:id)
|
user_email: error[:email],
|
||||||
|
user_id: @current_user.try(:id)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -597,7 +597,7 @@ class SubmissionsController < ApplicationController
|
||||||
begin
|
begin
|
||||||
@submissions = @assignment.update_submission(@user, params[:submission])
|
@submissions = @assignment.update_submission(@user, params[:submission])
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:submissions, e)
|
Canvas::Errors.capture_exception(:submissions, e)
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
end
|
end
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|
|
@ -238,9 +238,9 @@ class UsersController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
flash[:notice] = t('google_drive_added', "Google Drive account successfully added!")
|
flash[:notice] = t('google_drive_added', "Google Drive account successfully added!")
|
||||||
redirect_to json['return_to_url'] and return
|
return redirect_to(json['return_to_url'])
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:oauth, e)
|
Canvas::Errors.capture_exception(:oauth, e)
|
||||||
flash[:error] = t('google_drive_fail', "Google Drive authorization failed. Please try again")
|
flash[:error] = t('google_drive_fail', "Google Drive authorization failed. Please try again")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -274,7 +274,7 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
flash[:notice] = t('google_docs_added', "Google Docs access authorized!")
|
flash[:notice] = t('google_docs_added', "Google Docs access authorized!")
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:oauth, e)
|
Canvas::Errors.capture_exception(:oauth, e)
|
||||||
flash[:error] = t('google_docs_fail', "Google Docs authorization failed. Please try again")
|
flash[:error] = t('google_docs_fail', "Google Docs authorization failed. Please try again")
|
||||||
end
|
end
|
||||||
elsif params[:service] == "linked_in"
|
elsif params[:service] == "linked_in"
|
||||||
|
@ -302,7 +302,7 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
flash[:notice] = t('linkedin_added', "LinkedIn account successfully added!")
|
flash[:notice] = t('linkedin_added', "LinkedIn account successfully added!")
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:oauth, e)
|
Canvas::Errors.capture_exception(:oauth, e)
|
||||||
flash[:error] = t('linkedin_fail', "LinkedIn authorization failed. Please try again")
|
flash[:error] = t('linkedin_fail', "LinkedIn authorization failed. Please try again")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -327,7 +327,7 @@ class UsersController < ApplicationController
|
||||||
|
|
||||||
flash[:notice] = t('twitter_added', "Twitter access authorized!")
|
flash[:notice] = t('twitter_added', "Twitter access authorized!")
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:oauth, e)
|
Canvas::Errors.capture_exception(:oauth, e)
|
||||||
flash[:error] = t('twitter_fail_whale', "Twitter authorization failed. Please try again")
|
flash[:error] = t('twitter_fail_whale', "Twitter authorization failed. Please try again")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,7 @@ module AttachmentHelper
|
||||||
begin
|
begin
|
||||||
attrs[:crocodoc_session_url] = attachment.crocodoc_url(@current_user)
|
attrs[:crocodoc_session_url] = attachment.crocodoc_url(@current_user)
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception('crocodoc', e)
|
Canvas::Errors.capture_exception(:crocodoc, e)
|
||||||
end
|
end
|
||||||
elsif attachment.canvadocable?
|
elsif attachment.canvadocable?
|
||||||
attrs[:canvadoc_session_url] = attachment.canvadoc_url(@current_user)
|
attrs[:canvadoc_session_url] = attachment.canvadoc_url(@current_user)
|
||||||
|
|
|
@ -385,17 +385,16 @@ class AccountAuthorizationConfig < ActiveRecord::Base
|
||||||
|
|
||||||
default_timeout = Setting.get('ldap_timelimit', 5.seconds.to_s).to_f
|
default_timeout = Setting.get('ldap_timelimit', 5.seconds.to_s).to_f
|
||||||
|
|
||||||
Canvas.timeout_protection("ldap:#{self.global_id}",
|
timeout_options = { raise_on_timeout: true, fallback_timeout_length: default_timeout }
|
||||||
raise_on_timeout: true,
|
Canvas.timeout_protection("ldap:#{self.global_id}", timeout_options) do
|
||||||
fallback_timeout_length: default_timeout) do
|
ldap = self.ldap_connection
|
||||||
ldap = self.ldap_connection
|
filter = self.ldap_filter(unique_id)
|
||||||
filter = self.ldap_filter(unique_id)
|
ldap.bind_as(base: ldap.base, filter: filter, password: password_plaintext)
|
||||||
ldap.bind_as(:base => ldap.base, :filter => filter, :password => password_plaintext)
|
end
|
||||||
end
|
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:ldap, e, :account => self.account)
|
Canvas::Errors.capture(e, type: :ldap, account: self.account)
|
||||||
if e.is_a?(Timeout::Error)
|
if e.is_a?(Timeout::Error)
|
||||||
self.update_attribute(:last_timeout_failure, Time.now)
|
self.update_attribute(:last_timeout_failure, Time.zone.now)
|
||||||
end
|
end
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -107,8 +107,10 @@ class AssessmentQuestion < ActiveRecord::Base
|
||||||
new_file = file.clone_for(self)
|
new_file = file.clone_for(self)
|
||||||
rescue => e
|
rescue => e
|
||||||
new_file = nil
|
new_file = nil
|
||||||
er = ErrorReport.log_exception(:file_clone_during_translate_links, e)
|
er_id = Canvas::Errors.capture_exception(:file_clone_during_translate_links, e)[:error_report]
|
||||||
logger.error("Error while cloning attachment during AssessmentQuestion#translate_links: id: #{self.id} error_report: #{er.id}")
|
logger.error("Error while cloning attachment during"\
|
||||||
|
" AssessmentQuestion#translate_links: "\
|
||||||
|
"id: #{self.id} error_report: #{er_id}")
|
||||||
end
|
end
|
||||||
new_file.save if new_file
|
new_file.save if new_file
|
||||||
file_substitutions[id_or_path] = new_file
|
file_substitutions[id_or_path] = new_file
|
||||||
|
|
|
@ -1199,7 +1199,7 @@ class Attachment < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
update_attribute(:workflow_state, 'errored')
|
update_attribute(:workflow_state, 'errored')
|
||||||
ErrorReport.log_exception(:canvadocs, e, :attachment_id => id)
|
Canvas::Errors.capture(e, type: :canvadocs, attachment_id: id)
|
||||||
|
|
||||||
if attempt <= Setting.get('max_canvadocs_attempts', '5').to_i
|
if attempt <= Setting.get('max_canvadocs_attempts', '5').to_i
|
||||||
send_later_enqueue_args :submit_to_canvadocs, {
|
send_later_enqueue_args :submit_to_canvadocs, {
|
||||||
|
@ -1219,7 +1219,7 @@ class Attachment < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
update_attribute(:workflow_state, 'errored')
|
update_attribute(:workflow_state, 'errored')
|
||||||
ErrorReport.log_exception(:crocodoc, e, :attachment_id => id)
|
Canvas::Errors.capture(e, type: :canvadocs, attachment_id: id)
|
||||||
|
|
||||||
if attempt <= Setting.get('max_crocodoc_attempts', '5').to_i
|
if attempt <= Setting.get('max_crocodoc_attempts', '5').to_i
|
||||||
send_later_enqueue_args :submit_to_crocodoc, {
|
send_later_enqueue_args :submit_to_crocodoc, {
|
||||||
|
|
|
@ -269,16 +269,17 @@ class ContentExport < ActiveRecord::Base
|
||||||
self.settings[:errors] ||= []
|
self.settings[:errors] ||= []
|
||||||
er = nil
|
er = nil
|
||||||
if exception_or_info.is_a?(Exception)
|
if exception_or_info.is_a?(Exception)
|
||||||
er = ErrorReport.log_exception(:course_export, exception_or_info)
|
out = Canvas::Errors.capture_exception(:course_export, exception_or_info)
|
||||||
self.settings[:errors] << [user_message, "ErrorReport id: #{er.id}"]
|
er = out[:error_report]
|
||||||
|
self.settings[:errors] << [user_message, "ErrorReport id: #{er}"]
|
||||||
else
|
else
|
||||||
self.settings[:errors] << [user_message, exception_or_info]
|
self.settings[:errors] << [user_message, exception_or_info]
|
||||||
end
|
end
|
||||||
if self.content_migration
|
if self.content_migration
|
||||||
self.content_migration.add_issue(user_message, :error, :error_report_id => er && er.id)
|
self.content_migration.add_issue(user_message, :error, error_report_id: er)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def root_account
|
def root_account
|
||||||
self.context.try_rescue(:root_account)
|
self.context.try_rescue(:root_account)
|
||||||
end
|
end
|
||||||
|
|
|
@ -198,8 +198,8 @@ class ContentMigration < ActiveRecord::Base
|
||||||
if opts[:error_report_id]
|
if opts[:error_report_id]
|
||||||
mi.error_report_id = opts[:error_report_id]
|
mi.error_report_id = opts[:error_report_id]
|
||||||
elsif opts[:exception]
|
elsif opts[:exception]
|
||||||
er = ErrorReport.log_exception(:content_migration, opts[:exception])
|
er = Canvas::Errors.capture_exception(:content_migration, opts[:exception])[:error_report]
|
||||||
mi.error_report_id = er.id
|
mi.error_report_id = er
|
||||||
end
|
end
|
||||||
mi.error_message = opts[:error_message]
|
mi.error_message = opts[:error_message]
|
||||||
mi.fix_issue_html_url = opts[:fix_issue_html_url]
|
mi.fix_issue_html_url = opts[:fix_issue_html_url]
|
||||||
|
@ -327,7 +327,7 @@ class ContentMigration < ActiveRecord::Base
|
||||||
self.workflow_state = 'failed'
|
self.workflow_state = 'failed'
|
||||||
message = "The migration plugin #{migration_type} doesn't have a worker."
|
message = "The migration plugin #{migration_type} doesn't have a worker."
|
||||||
migration_settings[:last_error] = message
|
migration_settings[:last_error] = message
|
||||||
ErrorReport.log_exception(:content_migration, $!)
|
Canvas::Errors.capture_exception(:content_migration, $ERROR_INFO)
|
||||||
logger.error message
|
logger.error message
|
||||||
self.save
|
self.save
|
||||||
end
|
end
|
||||||
|
@ -444,8 +444,8 @@ class ContentMigration < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
self.workflow_state = :failed
|
self.workflow_state = :failed
|
||||||
er = ErrorReport.log_exception(:content_migration, e)
|
er_id = Canvas::Errors.capture_exception(:content_migration, e)[:error_report]
|
||||||
migration_settings[:last_error] = "ErrorReport:#{er.id}"
|
migration_settings[:last_error] = "ErrorReport:#{er_id}"
|
||||||
logger.error e
|
logger.error e
|
||||||
self.save
|
self.save
|
||||||
raise e
|
raise e
|
||||||
|
|
|
@ -146,7 +146,7 @@ class CrocodocDocument < ActiveRecord::Base
|
||||||
if status['status'] == 'ERROR'
|
if status['status'] == 'ERROR'
|
||||||
error = status['error'] || 'No explanation given'
|
error = status['error'] || 'No explanation given'
|
||||||
error_uuids << status['uuid']
|
error_uuids << status['uuid']
|
||||||
ErrorReport.log_error 'crocodoc', :message => error
|
Canvas::Errors.capture 'crocodoc', message: error
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -60,9 +60,7 @@ class DelayedNotification < ActiveRecord::Base
|
||||||
self.do_process unless self.new_record?
|
self.do_process unless self.new_record?
|
||||||
res
|
res
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:default, e, {
|
Canvas::Errors.capture(e, message: "Delayed Notification processing failed")
|
||||||
:message => "Delayed Notification processing failed",
|
|
||||||
})
|
|
||||||
logger.error "delayed notification processing failed: #{e.message}\n#{e.backtrace.join "\n"}"
|
logger.error "delayed notification processing failed: #{e.message}\n#{e.backtrace.join "\n"}"
|
||||||
self.workflow_state = 'errored'
|
self.workflow_state = 'errored'
|
||||||
self.save
|
self.save
|
||||||
|
|
|
@ -53,11 +53,11 @@ class ErrorReport < ActiveRecord::Base
|
||||||
@opts = opts
|
@opts = opts
|
||||||
# sanitize invalid encodings
|
# sanitize invalid encodings
|
||||||
@opts[:message] = Utf8Cleaner.strip_invalid_utf8(@opts[:message]) if @opts[:message]
|
@opts[:message] = Utf8Cleaner.strip_invalid_utf8(@opts[:message]) if @opts[:message]
|
||||||
@opts[:exception_message] = Utf8Cleaner.strip_invalid_utf8(@opts[:exception_message]) if @opts[:exception_message]
|
if @opts[:exception_message]
|
||||||
|
@opts[:exception_message] = Utf8Cleaner.strip_invalid_utf8(@opts[:exception_message])
|
||||||
|
end
|
||||||
@opts[:hostname] = self.class.hostname
|
@opts[:hostname] = self.class.hostname
|
||||||
@opts[:pid] = Process.pid
|
@opts[:pid] = Process.pid
|
||||||
CanvasStatsd::Statsd.increment("errors.all")
|
|
||||||
CanvasStatsd::Statsd.increment("errors.#{category}")
|
|
||||||
run_callbacks :on_log_error
|
run_callbacks :on_log_error
|
||||||
create_error_report(opts)
|
create_error_report(opts)
|
||||||
end
|
end
|
||||||
|
@ -101,6 +101,32 @@ class ErrorReport < ActiveRecord::Base
|
||||||
Reporter.new.log_exception(category, exception, opts)
|
Reporter.new.log_exception(category, exception, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.log_captured(type, exception, error_report_info)
|
||||||
|
if exception.is_a?(String) || exception.is_a?(Symbol)
|
||||||
|
log_error(exception, error_report_info)
|
||||||
|
else
|
||||||
|
type = exception.class.name if type == :default
|
||||||
|
log_exception(type, exception, error_report_info)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.log_exception_from_canvas_errors(exception, data)
|
||||||
|
tags = data.fetch(:tags, {})
|
||||||
|
account_id = tags[:account_id]
|
||||||
|
domain_root_account = account_id ? Account.where(id: account_id).first : nil
|
||||||
|
error_report_info = tags.merge(data[:extra])
|
||||||
|
type = tags.fetch(:type, :default)
|
||||||
|
|
||||||
|
if domain_root_account
|
||||||
|
domain_root_account.shard.activate do
|
||||||
|
ErrorReport.log_captured(type, exception, error_report_info)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ErrorReport.log_captured(type, exception, error_report_info)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# assigns data attributes to the column if there's a column with that name,
|
# assigns data attributes to the column if there's a column with that name,
|
||||||
# otherwise goes into the general data hash
|
# otherwise goes into the general data hash
|
||||||
def assign_data(data = {})
|
def assign_data(data = {})
|
||||||
|
@ -153,33 +179,6 @@ class ErrorReport < ActiveRecord::Base
|
||||||
self.where("created_at<?", before_date).delete_all
|
self.where("created_at<?", before_date).delete_all
|
||||||
end
|
end
|
||||||
|
|
||||||
USEFUL_ENV = [
|
|
||||||
"HTTP_ACCEPT",
|
|
||||||
"HTTP_ACCEPT_ENCODING",
|
|
||||||
"HTTP_HOST",
|
|
||||||
"HTTP_REFERER",
|
|
||||||
"HTTP_USER_AGENT",
|
|
||||||
"PATH_INFO",
|
|
||||||
"QUERY_STRING",
|
|
||||||
"REMOTE_HOST",
|
|
||||||
"REQUEST_METHOD",
|
|
||||||
"REQUEST_PATH",
|
|
||||||
"REQUEST_URI",
|
|
||||||
"SERVER_NAME",
|
|
||||||
"SERVER_PORT",
|
|
||||||
"SERVER_PROTOCOL",
|
|
||||||
]
|
|
||||||
def self.useful_http_env_stuff_from_request(request)
|
|
||||||
stuff = request.env.slice(*USEFUL_ENV)
|
|
||||||
stuff['REMOTE_ADDR'] = request.remote_ip # ActionDispatch::Request#remote_ip has proxy smarts
|
|
||||||
stuff['QUERY_STRING'] = LoggingFilter.filter_query_string("?" + stuff['QUERY_STRING'])
|
|
||||||
stuff['REQUEST_URI'] = LoggingFilter.filter_uri(request.url)
|
|
||||||
stuff['path_parameters'] = LoggingFilter.filter_params(request.path_parameters.dup).inspect # params rails picks up from the url
|
|
||||||
stuff['query_parameters'] = LoggingFilter.filter_params(request.query_parameters.dup).inspect # params rails picks up from the query string
|
|
||||||
stuff['request_parameters'] = LoggingFilter.filter_params(request.request_parameters.dup).inspect # params from forms
|
|
||||||
Marshal.load(Marshal.dump(stuff))
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.categories
|
def self.categories
|
||||||
distinct('category')
|
distinct('category')
|
||||||
end
|
end
|
||||||
|
|
|
@ -92,8 +92,9 @@ module Importers
|
||||||
begin
|
begin
|
||||||
self.import_media_objects(mo_attachments, migration)
|
self.import_media_objects(mo_attachments, migration)
|
||||||
rescue => e
|
rescue => e
|
||||||
er = ErrorReport.log_exception(:import_media_objects, e)
|
er = Canvas::Errors.capture_exception(:import_media_objects, e)[:error_report]
|
||||||
migration.add_error(t(:failed_import_media_objects, %{Failed to import media objects}), error_report_id: er.id)
|
error_message = t('Failed to import media objects')
|
||||||
|
migration.add_error(error_message, error_report_id: er)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if migration.canvas_import?
|
if migration.canvas_import?
|
||||||
|
|
|
@ -19,11 +19,12 @@ module Importers
|
||||||
def self.process_migration(data, migration)
|
def self.process_migration(data, migration)
|
||||||
wikis = data['wikis'] ? data['wikis']: []
|
wikis = data['wikis'] ? data['wikis']: []
|
||||||
wikis.each do |wiki|
|
wikis.each do |wiki|
|
||||||
if !wiki
|
unless wiki
|
||||||
ErrorReport.log_error(:content_migration, :message => "There was a nil wiki page imported for ContentMigration:#{migration.id}")
|
message = "There was a nil wiki page imported for ContentMigration:#{migration.id}"
|
||||||
|
Canvas::Errors.capture(:content_migration, message: message)
|
||||||
next
|
next
|
||||||
end
|
end
|
||||||
next unless migration.import_object?("wiki_pages", wiki['migration_id']) || migration.import_object?("wikis", wiki['migration_id'])
|
next unless wiki_page_migration?(migration, wiki)
|
||||||
begin
|
begin
|
||||||
self.import_from_migration(wiki, migration.context, migration) if wiki
|
self.import_from_migration(wiki, migration.context, migration) if wiki
|
||||||
rescue
|
rescue
|
||||||
|
@ -32,6 +33,12 @@ module Importers
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.wiki_page_migration?(migration, wiki)
|
||||||
|
migration.import_object?("wiki_pages", wiki['migration_id']) ||
|
||||||
|
migration.import_object?("wikis", wiki['migration_id'])
|
||||||
|
end
|
||||||
|
private_class_method :wiki_page_migration?
|
||||||
|
|
||||||
def self.import_from_migration(hash, context, migration=nil, item=nil)
|
def self.import_from_migration(hash, context, migration=nil, item=nil)
|
||||||
hash = hash.with_indifferent_access
|
hash = hash.with_indifferent_access
|
||||||
item ||= WikiPage.where(wiki_id: context.wiki, id: hash[:id]).first
|
item ||= WikiPage.where(wiki_id: context.wiki, id: hash[:id]).first
|
||||||
|
|
|
@ -197,12 +197,12 @@ class MediaObject < ActiveRecord::Base
|
||||||
def retrieve_details_ensure_codecs(attempt=0)
|
def retrieve_details_ensure_codecs(attempt=0)
|
||||||
retrieve_details
|
retrieve_details
|
||||||
if (!self.data || !self.data[:extensions] || !self.data[:extensions][:flv]) && self.created_at > 6.hours.ago
|
if (!self.data || !self.data[:extensions] || !self.data[:extensions][:flv]) && self.created_at > 6.hours.ago
|
||||||
if(attempt < 10)
|
if attempt < 10
|
||||||
send_at((5 * attempt).minutes.from_now, :retrieve_details_ensure_codecs, attempt + 1)
|
send_at((5 * attempt).minutes.from_now, :retrieve_details_ensure_codecs, attempt + 1)
|
||||||
else
|
else
|
||||||
ErrorReport.log_error(:default, {
|
Canvas::Errors.capture(:media_object_failure, {
|
||||||
:message => "Kaltura flavor retrieval failed",
|
message: "Kaltura flavor retrieval failed",
|
||||||
:object => self.inspect.to_s,
|
object: self.inspect.to_s,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -694,10 +694,12 @@ class Message < ActiveRecord::Base
|
||||||
raise_error = @exception.to_s !~ /^450/
|
raise_error = @exception.to_s !~ /^450/
|
||||||
log_error = raise_error && !@exception.is_a?(Timeout::Error)
|
log_error = raise_error && !@exception.is_a?(Timeout::Error)
|
||||||
if log_error
|
if log_error
|
||||||
ErrorReport.log_exception(:default, @exception, {
|
Canvas::Errors.capture(
|
||||||
:message => 'Message delivery failed',
|
@exception,
|
||||||
:to => to,
|
message: 'Message delivery failed',
|
||||||
:object => inspect.to_s })
|
to: to,
|
||||||
|
object: inspect.to_s
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.errored_dispatch
|
self.errored_dispatch
|
||||||
|
|
|
@ -97,8 +97,8 @@ class Progress < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def on_permanent_failure(error)
|
def on_permanent_failure(error)
|
||||||
error_report = ErrorReport.log_exception("Progress::Work", error)
|
er_id = Canvas::Errors.capture_exception("Progress::Work", error)[:error_report]
|
||||||
@progress.message = "Unexpected error, ID: #{error_report.id rescue "unknown"}"
|
@progress.message = "Unexpected error, ID: #{er_id || 'unknown'}"
|
||||||
@progress.save
|
@progress.save
|
||||||
@progress.fail
|
@progress.fail
|
||||||
end
|
end
|
||||||
|
|
|
@ -444,10 +444,11 @@ class Pseudonym < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
!!res
|
!!res
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:ldap, e, {
|
Canvas::Errors.capture(e, {
|
||||||
:message => "LDAP authentication error",
|
type: :ldap,
|
||||||
:object => self.inspect.to_s,
|
message: "LDAP authentication error",
|
||||||
:unique_id => self.unique_id,
|
object: self.inspect.to_s,
|
||||||
|
unique_id: self.unique_id,
|
||||||
})
|
})
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -75,10 +75,10 @@ class ZipFileImport < ActiveRecord::Base
|
||||||
self.workflow_state = :imported
|
self.workflow_state = :imported
|
||||||
self.save
|
self.save
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:zip_file_import, e)
|
Canvas::Errors.capture_exception(:zip_file_import, e)
|
||||||
|
|
||||||
self.data[:error_message] = e.to_s
|
self.data[:error_message] = e.to_s
|
||||||
self.data[:stack_trace] = "#{e.to_s}\n#{e.backtrace.join("\n")}"
|
self.data[:stack_trace] = "#{e}\n#{e.backtrace.join("\n")}"
|
||||||
self.workflow_state = "failed"
|
self.workflow_state = "failed"
|
||||||
self.save
|
self.save
|
||||||
end
|
end
|
||||||
|
|
|
@ -206,10 +206,10 @@ class ActiveRecord::Base
|
||||||
def touch_context
|
def touch_context
|
||||||
return if (@@skip_touch_context ||= false || @skip_touch_context ||= false)
|
return if (@@skip_touch_context ||= false || @skip_touch_context ||= false)
|
||||||
if self.respond_to?(:context_type) && self.respond_to?(:context_id) && self.context_type && self.context_id
|
if self.respond_to?(:context_type) && self.respond_to?(:context_id) && self.context_type && self.context_id
|
||||||
self.context_type.constantize.update_all({ :updated_at => Time.now.utc }, { :id => self.context_id })
|
self.context_type.constantize.update_all({ updated_at: Time.now.utc }, { id: self.context_id })
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
ErrorReport.log_exception(:touch_context, $!)
|
Canvas::Errors.capture_exception(:touch_context, $ERROR_INFO)
|
||||||
end
|
end
|
||||||
|
|
||||||
def touch_user
|
def touch_user
|
||||||
|
@ -225,7 +225,7 @@ class ActiveRecord::Base
|
||||||
end
|
end
|
||||||
true
|
true
|
||||||
rescue
|
rescue
|
||||||
ErrorReport.log_exception(:touch_user, $!)
|
Canvas::Errors.capture_exception(:touch_user, $ERROR_INFO)
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -101,5 +101,11 @@ Delayed::Worker.lifecycle.before(:perform) do |job|
|
||||||
end
|
end
|
||||||
|
|
||||||
Delayed::Worker.lifecycle.before(:exceptional_exit) do |worker, exception|
|
Delayed::Worker.lifecycle.before(:exceptional_exit) do |worker, exception|
|
||||||
ErrorReport.log_exception(:delayed_jobs, exception) rescue nil
|
info = Canvas::Errors::JobInfo.new(nil, worker)
|
||||||
|
Canvas::Errors.capture(exception, info.to_h)
|
||||||
|
end
|
||||||
|
|
||||||
|
Delayed::Worker.lifecycle.before(:error) do |worker, job, exception|
|
||||||
|
info = Canvas::Errors::JobInfo.new(job, worker)
|
||||||
|
Canvas::Errors.capture(exception, info.to_h)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# This initializer registers the two interal tools canvas uses
|
||||||
|
# for tracking errors. One (:error_report) creates a record in the
|
||||||
|
# error_reports database table for each exception that occurs. The
|
||||||
|
# other (:error_stats) will send two counter increments to statsd,
|
||||||
|
# one for "all" which tabulates every error that occurs, and one
|
||||||
|
# that includes the class of the exception in the key so you
|
||||||
|
# can see big spikes in a certain kind of error. Either can be
|
||||||
|
# disabled individually with a setting.
|
||||||
|
#
|
||||||
|
Canvas::Errors.register!(:error_report) do |exception, data|
|
||||||
|
setting = Setting.get("error_report_exception_handling", 'true')
|
||||||
|
if setting == 'true'
|
||||||
|
report = ErrorReport.log_exception_from_canvas_errors(exception, data)
|
||||||
|
report.try(:global_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Canvas::Errors.register!(:error_stats) do |exception, data|
|
||||||
|
setting = Setting.get("collect_error_statistics", 'true')
|
||||||
|
Canvas::ErrorStats.capture(exception, data) if setting == 'true'
|
||||||
|
end
|
|
@ -15,7 +15,8 @@ Rails.configuration.after_initialize do
|
||||||
expire_after ||= 1.day
|
expire_after ||= 1.day
|
||||||
|
|
||||||
Delayed::Periodic.cron 'ActiveRecord::SessionStore::Session.delete_all', '*/5 * * * *' do
|
Delayed::Periodic.cron 'ActiveRecord::SessionStore::Session.delete_all', '*/5 * * * *' do
|
||||||
Shard.with_each_shard(exception: -> { ErrorReport.log_exception(:periodic_job, $!) }) do
|
callback = -> { Canvas::Errors.capture_exception(:periodic_job, $ERROR_INFO) }
|
||||||
|
Shard.with_each_shard(exception: callback) do
|
||||||
ActiveRecord::SessionStore::Session.delete_all(['updated_at < ?', expire_after.ago])
|
ActiveRecord::SessionStore::Session.delete_all(['updated_at < ?', expire_after.ago])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# This initializer is for the Sentry exception tracking system.
|
||||||
|
#
|
||||||
|
# "Raven" is the ruby library that is the client to sentry, and it's
|
||||||
|
# config file would be "config/raven.yml". If that config doesn't exist,
|
||||||
|
# nothing happens. If it *does*, we register a callback with Canvas::Errors
|
||||||
|
# so that every time an exception is reported, we can fire off a sentry
|
||||||
|
# call to track it and aggregate it for us.
|
||||||
|
settings = ConfigFile.load("raven")
|
||||||
|
|
||||||
|
if settings.present?
|
||||||
|
require "raven/base"
|
||||||
|
Raven.configure do |config|
|
||||||
|
config.dsn = settings[:dsn]
|
||||||
|
end
|
||||||
|
|
||||||
|
Canvas::Errors.register!(:sentry_notification) do |exception, data|
|
||||||
|
setting = Setting.get("sentry_error_logging_enabled", 'true')
|
||||||
|
if setting == 'true'
|
||||||
|
if exception.is_a?(String) || exception.is_a?(Symbol)
|
||||||
|
Raven.capture_message(exception, data)
|
||||||
|
else
|
||||||
|
Raven.capture_exception(exception, data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
development:
|
||||||
|
dsn: 'your-sanbox-dsn-here'
|
||||||
|
production:
|
||||||
|
dsn: 'your-real-dsn-here'
|
|
@ -124,8 +124,12 @@ module BroadcastPolicy
|
||||||
else
|
else
|
||||||
self.workflow_state != self.prior_version.workflow_state
|
self.workflow_state != self.prior_version.workflow_state
|
||||||
end
|
end
|
||||||
rescue Exception => e
|
rescue StandardError => e
|
||||||
ErrorReport.log_exception(:broadcast_policy, e, message: "Could not check if a record changed state")
|
Canvas::Errors.capture(
|
||||||
|
e,
|
||||||
|
type: :broadcast_policy,
|
||||||
|
message: "Could not check if a record changed state"
|
||||||
|
)
|
||||||
logger.warn "Could not check if a record changed state: #{e.inspect}"
|
logger.warn "Could not check if a record changed state: #{e.inspect}"
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
|
@ -77,14 +77,14 @@ module Twitter
|
||||||
@service.destroy if @service
|
@service.destroy if @service
|
||||||
@twitter_service.destroy if @twitter_service
|
@twitter_service.destroy if @twitter_service
|
||||||
end
|
end
|
||||||
ErrorReport.log_error(:processing, {
|
Canvas::Errors.capture(:processing, {
|
||||||
:backtrace => "Retrieving twitter list for #{@twitter_service.inspect}",
|
backtrace: "Retrieving twitter list for #{@twitter_service.inspect}",
|
||||||
:response => response.inspect,
|
response: response.inspect,
|
||||||
:body => response.body,
|
body: response.body,
|
||||||
:message => response['X-RateLimit-Reset'],
|
message: response['X-RateLimit-Reset'],
|
||||||
:url => url
|
url: url
|
||||||
})
|
})
|
||||||
retry_after = (response['X-RateLimit-Reset'].to_i - Time.now.utc.to_i) rescue 0 #response['Retry-After'].to_i rescue 0
|
retry_after = (response['X-RateLimit-Reset'].to_i - Time.now.utc.to_i) rescue 0
|
||||||
raise "Retry After #{retry_after}"
|
raise "Retry After #{retry_after}"
|
||||||
end
|
end
|
||||||
res
|
res
|
||||||
|
|
|
@ -477,9 +477,11 @@ module Api
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.invalid_time_stamp_error(attribute, message)
|
def self.invalid_time_stamp_error(attribute, message)
|
||||||
ErrorReport.log_error('invalid_date_time',
|
Canvas::Errors.capture(
|
||||||
message: "invalid #{attribute}",
|
'invalid_date_time',
|
||||||
exception_message: message)
|
message: "invalid #{attribute}",
|
||||||
|
exception_message: message
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
# regex for valid iso8601 dates
|
# regex for valid iso8601 dates
|
||||||
|
|
|
@ -179,7 +179,7 @@ module Canvas
|
||||||
raise if options[:raise_on_timeout]
|
raise if options[:raise_on_timeout]
|
||||||
return nil
|
return nil
|
||||||
rescue Timeout::Error => e
|
rescue Timeout::Error => e
|
||||||
ErrorReport.log_exception(:service_timeout, e)
|
Canvas::Errors.capture_exception(:service_timeout, e)
|
||||||
raise if options[:raise_on_timeout]
|
raise if options[:raise_on_timeout]
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
module Canvas
|
||||||
|
# Simple class for shipping errors to statsd based on the format
|
||||||
|
# propogated from callbacks on Canvas::Errors
|
||||||
|
class ErrorStats
|
||||||
|
def self.capture(exception, _data)
|
||||||
|
category = exception
|
||||||
|
unless exception.is_a?(String) || exception.is_a?(Symbol)
|
||||||
|
category = exception.class.name
|
||||||
|
end
|
||||||
|
CanvasStatsd::Statsd.increment("errors.all")
|
||||||
|
CanvasStatsd::Statsd.increment("errors.#{category}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,76 @@
|
||||||
|
module Canvas
|
||||||
|
# The central message bus for errors in canvas.
|
||||||
|
#
|
||||||
|
# This class is injected both in ApplicationController for capturing
|
||||||
|
# exceptions that happen in request/response cycles, and in our
|
||||||
|
# Delayed::Job callback for failed jobs. We also call out to it
|
||||||
|
# from several points throughout the codebase directly to register
|
||||||
|
# an unexpected occurance that doesn't necessarily bubble up to
|
||||||
|
# that point.
|
||||||
|
#
|
||||||
|
# There's a sentry connector built into canvas, but anything one
|
||||||
|
# wants to do with errors can be hooked into this path with the
|
||||||
|
# .register! method.
|
||||||
|
class Errors
|
||||||
|
|
||||||
|
# register something to happen on every exception that occurs.
|
||||||
|
#
|
||||||
|
# The parameter is a unique key for this callback, which is
|
||||||
|
# used when assembling return values from ".capture" and it's friends.
|
||||||
|
#
|
||||||
|
# The block should accept two parameters, one for the exception/message
|
||||||
|
# and one for contextual info in the form of a hash. The hash *will*
|
||||||
|
# have an ":extra" key, and *may* have a ":tags" key. tags would
|
||||||
|
# be things it might be useful to aggreate errors around (job queue), extras
|
||||||
|
# are things that would be useful for tracking down why or in what
|
||||||
|
# circumstance an error might occur (request_context_id)
|
||||||
|
#
|
||||||
|
# Canvas::Errors.register!(:my_key) do |ex, data|
|
||||||
|
# # do something with the exception
|
||||||
|
# end
|
||||||
|
def self.register!(key, &block)
|
||||||
|
registry[key] = block
|
||||||
|
end
|
||||||
|
|
||||||
|
# "capture" is the thing to call if you want to tell Canvas::Errors
|
||||||
|
# that something bad happened. You can pass in an exception, or
|
||||||
|
# just a message. If you don't build your data hash
|
||||||
|
# with a "tags" key and an "extra" key, it will just group all contextual
|
||||||
|
# information under "extra"
|
||||||
|
def self.capture(exception, data={})
|
||||||
|
run_callbacks(exception, wrap_in_extra(data))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.capture_exception(type, exception)
|
||||||
|
self.capture(exception, {type: type})
|
||||||
|
end
|
||||||
|
|
||||||
|
# This is really just for clearing out the registry during tests,
|
||||||
|
# if you call it in production it will dump all registered callbacks
|
||||||
|
# that got fired in initializers and such until the process restarts.
|
||||||
|
def self.clear_callback_registry!
|
||||||
|
@registry = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.run_callbacks(exception, extra)
|
||||||
|
registry.each_with_object({}) do |(key, callback), outputs|
|
||||||
|
outputs[key] = callback.call(exception, extra)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :run_callbacks
|
||||||
|
|
||||||
|
def self.registry
|
||||||
|
@registry ||= {}
|
||||||
|
end
|
||||||
|
private_class_method :registry
|
||||||
|
|
||||||
|
def self.wrap_in_extra(data)
|
||||||
|
if data.key?(:tags) || data.key?(:extra)
|
||||||
|
data
|
||||||
|
else
|
||||||
|
{extra: data}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :wrap_in_extra
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,79 @@
|
||||||
|
require_relative '../errors'
|
||||||
|
module Canvas
|
||||||
|
class Errors
|
||||||
|
|
||||||
|
# This is a class for taking the common context
|
||||||
|
# found in the request/response cycle for an exception
|
||||||
|
# and turning it into a pleasent hash for Canvas::Errors
|
||||||
|
# to make use of.
|
||||||
|
class Info
|
||||||
|
|
||||||
|
attr_reader :req, :account, :user, :rci, :type
|
||||||
|
|
||||||
|
def initialize(request, root_account, user, opts={})
|
||||||
|
@req = request
|
||||||
|
@account = root_account
|
||||||
|
@user = user
|
||||||
|
@rci = opts.fetch(:request_context_id, RequestContextGenerator.request_id)
|
||||||
|
@type = opts.fetch(:type, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
# The ideal hash format to pass to Canvas::Errors.capture().
|
||||||
|
#
|
||||||
|
# If you're trying to find a way to transform some other common
|
||||||
|
# context, this is a decent model to follow.
|
||||||
|
def to_h
|
||||||
|
{
|
||||||
|
tags: {
|
||||||
|
account_id: @account.try(:global_id)
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
request_context_id: @rci,
|
||||||
|
request_method: @req.request_method_symbol,
|
||||||
|
format: @req.format,
|
||||||
|
user_agent: @req.headers['User-Agent'],
|
||||||
|
user_id: @user.try(:global_id),
|
||||||
|
type: @type
|
||||||
|
}.merge(self.class.useful_http_env_stuff_from_request(@req))
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
USEFUL_ENV = [
|
||||||
|
"HTTP_ACCEPT",
|
||||||
|
"HTTP_ACCEPT_ENCODING",
|
||||||
|
"HTTP_HOST",
|
||||||
|
"HTTP_REFERER",
|
||||||
|
"HTTP_USER_AGENT",
|
||||||
|
"PATH_INFO",
|
||||||
|
"QUERY_STRING",
|
||||||
|
"REMOTE_HOST",
|
||||||
|
"REQUEST_METHOD",
|
||||||
|
"REQUEST_PATH",
|
||||||
|
"REQUEST_URI",
|
||||||
|
"SERVER_NAME",
|
||||||
|
"SERVER_PORT",
|
||||||
|
"SERVER_PROTOCOL",
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
def self.useful_http_env_stuff_from_request(req)
|
||||||
|
stuff = req.env.slice(*USEFUL_ENV)
|
||||||
|
req_stuff = stuff.merge(filtered_request_params(req, stuff['QUERY_STRING']))
|
||||||
|
Marshal.load(Marshal.dump(req_stuff))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.filtered_request_params(req, query_string)
|
||||||
|
f = LoggingFilter
|
||||||
|
{
|
||||||
|
# ActionDispatch::Request#remote_ip has proxy smarts
|
||||||
|
'REMOTE_ADDR' => req.remote_ip,
|
||||||
|
'QUERY_STRING' => (f.filter_query_string("?" + (query_string || ''))),
|
||||||
|
'REQUEST_URI' => f.filter_uri(req.url),
|
||||||
|
'path_parameters' => f.filter_params(req.path_parameters.dup).inspect,
|
||||||
|
'query_parameters' => f.filter_params(req.query_parameters.dup).inspect,
|
||||||
|
'request_parameters' => f.filter_params(req.request_parameters.dup).inspect,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
private_class_method :filtered_request_params
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,34 @@
|
||||||
|
require_relative '../errors'
|
||||||
|
module Canvas
|
||||||
|
class Errors
|
||||||
|
class JobInfo
|
||||||
|
def initialize(job, worker)
|
||||||
|
@job = job
|
||||||
|
@worker = worker
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_h
|
||||||
|
{
|
||||||
|
tags: {
|
||||||
|
process_type: "BackgroundJob",
|
||||||
|
job_tag: @job.tag,
|
||||||
|
},
|
||||||
|
extra: extras_hash
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def extras_hash
|
||||||
|
{
|
||||||
|
attempts: @job.attempts,
|
||||||
|
strand: @job.strand,
|
||||||
|
priority: @job.priority,
|
||||||
|
worker_name: @worker.name,
|
||||||
|
handler: @job.handler,
|
||||||
|
run_at: @job.run_at,
|
||||||
|
max_attempts: @job.max_attempts,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -63,8 +63,8 @@ module Canvas::Redis
|
||||||
Rails.logger.error "Failure handling redis command on #{redis_name}: #{e.inspect}"
|
Rails.logger.error "Failure handling redis command on #{redis_name}: #{e.inspect}"
|
||||||
|
|
||||||
if self.ignore_redis_failures?
|
if self.ignore_redis_failures?
|
||||||
ErrorReport.log_exception(:redis, e)
|
Canvas::Errors.capture(e, type: :redis)
|
||||||
last_redis_failure[redis_name] = Time.now
|
last_redis_failure[redis_name] = Time.zone.now
|
||||||
failure_retval
|
failure_retval
|
||||||
else
|
else
|
||||||
raise
|
raise
|
||||||
|
|
|
@ -48,15 +48,13 @@ class ContentZipper
|
||||||
|
|
||||||
begin
|
begin
|
||||||
case attachment.context
|
case attachment.context
|
||||||
when Assignment; zip_assignment(attachment, attachment.context)
|
when Assignment then zip_assignment(attachment, attachment.context)
|
||||||
when Eportfolio; zip_eportfolio(attachment, attachment.context)
|
when Eportfolio then zip_eportfolio(attachment, attachment.context)
|
||||||
when Folder; zip_base_folder(attachment, attachment.context)
|
when Folder then zip_base_folder(attachment, attachment.context)
|
||||||
when Quizzes::Quiz; zip_quiz(attachment, attachment.context)
|
when Quizzes::Quiz then zip_quiz(attachment, attachment.context)
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:default, e, {
|
Canvas::Errors.capture(e, message: "Content zipping failed")
|
||||||
:message => "Content zipping failed",
|
|
||||||
})
|
|
||||||
@logger.debug(e.to_s)
|
@logger.debug(e.to_s)
|
||||||
@logger.debug(e.backtrace.join('\n'))
|
@logger.debug(e.backtrace.join('\n'))
|
||||||
attachment.update_attribute(:workflow_state, 'to_be_zipped')
|
attachment.update_attribute(:workflow_state, 'to_be_zipped')
|
||||||
|
|
|
@ -25,9 +25,9 @@ class CourseLinkValidator
|
||||||
validator.check_course(progress)
|
validator.check_course(progress)
|
||||||
progress.set_results({:issues => validator.issues, :completed_at => Time.now.utc})
|
progress.set_results({:issues => validator.issues, :completed_at => Time.now.utc})
|
||||||
rescue
|
rescue
|
||||||
report = ErrorReport.log_exception(:course_link_validation, $!)
|
report_id = Canvas::Errors.capture_exception(:course_link_validation, $ERROR_INFO)[:error_report]
|
||||||
progress.workflow_state = 'failed'
|
progress.workflow_state = 'failed'
|
||||||
progress.set_results({:error_report_id => report.id, :completed_at => Time.now.utc})
|
progress.set_results({error_report_id: report_id, completed_at: Time.now.utc})
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_accessor :course, :issues, :visited_urls
|
attr_accessor :course, :issues, :visited_urls
|
||||||
|
@ -206,4 +206,4 @@ class CourseLinkValidator
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -100,11 +100,11 @@ class ExternalFeedAggregator
|
||||||
feed.increment(:consecutive_failures)
|
feed.increment(:consecutive_failures)
|
||||||
feed.increment(:failures)
|
feed.increment(:failures)
|
||||||
feed.update_attribute(:refresh_at, Time.now.utc + (FAILURE_WAIT_SECONDS))
|
feed.update_attribute(:refresh_at, Time.now.utc + (FAILURE_WAIT_SECONDS))
|
||||||
ErrorReport.log_exception(:default, e, {
|
Canvas::Errors.capture(e, {
|
||||||
:message => "External Feed aggregation failed",
|
message: "External Feed aggregation failed",
|
||||||
:feed_url => feed.url,
|
feed_url: feed.url,
|
||||||
:feed_id => feed.id,
|
feed_id: feed.id,
|
||||||
:user_id => feed.user_id,
|
user_id: feed.user_id,
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,10 +40,11 @@ class CountsReport
|
||||||
end
|
end
|
||||||
|
|
||||||
def process
|
def process
|
||||||
start_time = Time.now
|
start_time = Time.zone.now
|
||||||
|
|
||||||
Shackles.activate(:slave) do
|
Shackles.activate(:slave) do
|
||||||
Shard.with_each_shard(exception: -> { Shard.default.activate { ErrorReport.log_exception(:periodic_job, $!) } }) do
|
callback = -> { Shard.default.activate { Canvas::Errors.capture_exception(:periodic_job, $ERROR_INFO) } }
|
||||||
|
Shard.with_each_shard(exception: callback) do
|
||||||
Account.root_accounts.active.each do |account|
|
Account.root_accounts.active.each do |account|
|
||||||
next if account.external_status == 'test'
|
next if account.external_status == 'test'
|
||||||
|
|
||||||
|
|
|
@ -59,15 +59,11 @@ module SendToInbox
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:default, e, {
|
Canvas::Errors.capture(e, {message: "SendToInbox failure"})
|
||||||
:message => "SendToInbox failure",
|
|
||||||
})
|
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def inbox_item_recipient_ids
|
attr_reader :inbox_item_recipient_ids
|
||||||
@inbox_item_recipient_ids
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -57,9 +57,7 @@ module SendToStream
|
||||||
generate_stream_items(stream_recipients) if stream_recipients
|
generate_stream_items(stream_recipients) if stream_recipients
|
||||||
rescue => e
|
rescue => e
|
||||||
if Rails.env.production?
|
if Rails.env.production?
|
||||||
ErrorReport.log_exception(:default, e, {
|
Canvas::Errors.capture(e, {message: "SendToStream failure" })
|
||||||
:message => "SendToStream failure",
|
|
||||||
})
|
|
||||||
else
|
else
|
||||||
raise
|
raise
|
||||||
end
|
end
|
||||||
|
@ -82,19 +80,11 @@ module SendToStream
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
ErrorReport.log_exception(:default, e, {
|
Canvas::Errors.capture(e, { message: "SendToStream failure" })
|
||||||
:message => "SendToStream failure",
|
|
||||||
})
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def generated_stream_items
|
attr_reader :generated_stream_items, :stream_item_recipient_ids
|
||||||
@generated_stream_items
|
|
||||||
end
|
|
||||||
|
|
||||||
def stream_item_recipient_ids
|
|
||||||
@stream_item_recipient_ids
|
|
||||||
end
|
|
||||||
|
|
||||||
def stream_item_inactive?
|
def stream_item_inactive?
|
||||||
(self.respond_to?(:workflow_state) && self.workflow_state == 'deleted') || (self.respond_to?(:deleted?) && self.deleted?)
|
(self.respond_to?(:workflow_state) && self.workflow_state == 'deleted') || (self.respond_to?(:deleted?) && self.deleted?)
|
||||||
|
|
|
@ -175,11 +175,17 @@ module SIS
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
if @batch
|
if @batch
|
||||||
error_report = ErrorReport.log_exception(:sis_import, e,
|
message = "Importing CSV for account"\
|
||||||
:message => "Importing CSV for account: #{@root_account.id} (#{@root_account.name}) sis_batch_id: #{@batch.id}: #{e.to_s}",
|
": #{@root_account.id} (#{@root_account.name}) "\
|
||||||
:during_tests => false
|
"sis_batch_id: #{@batch.id}: #{e}"
|
||||||
)
|
err_id = Canvas::Errors.capture(e,{
|
||||||
add_error(nil, I18n.t("Error while importing CSV. Please contact support. (Error report %{number})", number: error_report.id))
|
type: :sis_import,
|
||||||
|
message: message,
|
||||||
|
during_tests: false
|
||||||
|
})[:error_report]
|
||||||
|
error_message = I18n.t("Error while importing CSV. Please contact support."\
|
||||||
|
" (Error report %{number})", number: err_id)
|
||||||
|
add_error(nil, error_message)
|
||||||
else
|
else
|
||||||
add_error(nil, "#{e.message}\n#{e.backtrace.join "\n"}")
|
add_error(nil, "#{e.message}\n#{e.backtrace.join "\n"}")
|
||||||
raise e
|
raise e
|
||||||
|
@ -259,11 +265,16 @@ module SIS
|
||||||
importerObject.process(csv)
|
importerObject.process(csv)
|
||||||
run_next_importer(IMPORTERS[IMPORTERS.index(importer) + 1]) if complete_importer(importer)
|
run_next_importer(IMPORTERS[IMPORTERS.index(importer) + 1]) if complete_importer(importer)
|
||||||
rescue => e
|
rescue => e
|
||||||
error_report = ErrorReport.log_exception(:sis_import, e,
|
message = "Importing CSV for account: "\
|
||||||
:message => "Importing CSV for account: #{@root_account.id} (#{@root_account.name}) sis_batch_id: #{@batch.id}: #{e.to_s}",
|
"#{@root_account.id} (#{@root_account.name}) sis_batch_id: #{@batch.id}: #{e}"
|
||||||
:during_tests => false
|
err_id = Canvas::Errors.capture(e, {
|
||||||
)
|
type: :sis_import,
|
||||||
add_error(nil, I18n.t("Error while importing CSV. Please contact support. (Error report %{number})", number: error_report.id))
|
message: message,
|
||||||
|
during_tests: false
|
||||||
|
})[:error_report]
|
||||||
|
error_message = I18n.t("Error while importing CSV. Please contact support. "\
|
||||||
|
"(Error report %{number})", number: err_id)
|
||||||
|
add_error(nil, error_message)
|
||||||
@batch.processing_errors ||= []
|
@batch.processing_errors ||= []
|
||||||
@batch.processing_warnings ||= []
|
@batch.processing_warnings ||= []
|
||||||
@batch.processing_errors.concat(@errors)
|
@batch.processing_errors.concat(@errors)
|
||||||
|
@ -274,7 +285,7 @@ module SIS
|
||||||
file.close if file
|
file.close if file
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def run_next_importer(importer)
|
def run_next_importer(importer)
|
||||||
|
|
|
@ -471,8 +471,10 @@ describe ApplicationController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should log error reports to the domain_root_accounts shard' do
|
it 'should log error reports to the domain_root_accounts shard' do
|
||||||
ErrorReport.stubs(:log_exception).returns(ErrorReport.new)
|
report = ErrorReport.new
|
||||||
ErrorReport.stubs(:useful_http_env_stuff_from_request).returns({})
|
ErrorReport.stubs(:log_exception).returns(report)
|
||||||
|
ErrorReport.stubs(:find).returns(report)
|
||||||
|
Canvas::Errors::Info.stubs(:useful_http_env_stuff_from_request).returns({})
|
||||||
|
|
||||||
req = mock()
|
req = mock()
|
||||||
req.stubs(:url).returns('url')
|
req.stubs(:url).returns('url')
|
||||||
|
@ -486,7 +488,7 @@ describe ApplicationController do
|
||||||
|
|
||||||
controller.instance_variable_set(:@domain_root_account, @account)
|
controller.instance_variable_set(:@domain_root_account, @account)
|
||||||
|
|
||||||
@shard2.expects(:activate).twice
|
@shard2.expects(:activate)
|
||||||
|
|
||||||
controller.send(:rescue_action_in_public, Exception.new)
|
controller.send(:rescue_action_in_public, Exception.new)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
module Canvas
|
||||||
|
describe ErrorStats do
|
||||||
|
describe ".capture" do
|
||||||
|
before(:each) do
|
||||||
|
CanvasStatsd::Statsd.stubs(:increment)
|
||||||
|
end
|
||||||
|
let(:data){ {} }
|
||||||
|
|
||||||
|
it "increments errors.all always" do
|
||||||
|
CanvasStatsd::Statsd.expects(:increment).with("errors.all")
|
||||||
|
described_class.capture("something", data)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "increments the message name for a string" do
|
||||||
|
CanvasStatsd::Statsd.expects(:increment).with("errors.something")
|
||||||
|
described_class.capture("something", data)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "increments the message name for a symbol" do
|
||||||
|
CanvasStatsd::Statsd.expects(:increment).with("errors.something")
|
||||||
|
described_class.capture(:something, data)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "bumps the exception name for anything else" do
|
||||||
|
CanvasStatsd::Statsd.expects(:increment).with("errors.StandardError")
|
||||||
|
described_class.capture(StandardError.new, data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,76 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
module Canvas
|
||||||
|
class Errors
|
||||||
|
describe Info do
|
||||||
|
let(:request) do
|
||||||
|
stub(env: {}, remote_ip: "", query_parameters: {},
|
||||||
|
request_parameters: {}, path_parameters: {}, url: '',
|
||||||
|
request_method_symbol: '', format: 'HTML', headers: {})
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:request_context_id){ 'abcdefg1234567'}
|
||||||
|
let(:account){ stub(global_id: 1122334455) }
|
||||||
|
let(:user) { stub(global_id: 5544332211)}
|
||||||
|
let(:opts) { { request_context_id: request_context_id }}
|
||||||
|
|
||||||
|
describe 'initialization' do
|
||||||
|
it "grabs the request context id if not provided" do
|
||||||
|
RequestContextGenerator.stubs(:request_id).returns("zzzzzzz")
|
||||||
|
info = described_class.new(request, account, user, {})
|
||||||
|
expect(info.rci).to eq("zzzzzzz")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "#to_h" do
|
||||||
|
let(:output) do
|
||||||
|
info = described_class.new(request, account, user, opts)
|
||||||
|
info.to_h
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'digests request information' do
|
||||||
|
request.stubs(:remote_ip).returns("123.456")
|
||||||
|
expect(output[:tags][:account_id]).to eq(1122334455)
|
||||||
|
expect(output[:extra][:request_context_id]).to eq(request_context_id)
|
||||||
|
expect(output[:extra]['REMOTE_ADDR']).to eq("123.456")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "pulls in the request method" do
|
||||||
|
request.stubs(:request_method_symbol).returns("POST")
|
||||||
|
expect(output[:extra][:request_method]).to eq('POST')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'passes format through' do
|
||||||
|
request.stubs(:format).returns("JSON")
|
||||||
|
expect(output[:extra][:format]).to eq('JSON')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'includes user information' do
|
||||||
|
expect(output[:extra][:user_id]).to eq(5544332211)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'passes important headers' do
|
||||||
|
request.stubs(:headers).returns({'User-Agent'=>'the-agent'})
|
||||||
|
expect(output[:extra][:user_agent]).to eq('the-agent')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe ".useful_http_env_stuff_from_request" do
|
||||||
|
it "duplicates to get away from frozen strings out of the request.env" do
|
||||||
|
dangerous_hash = {
|
||||||
|
"QUERY_STRING".force_encoding(Encoding::ASCII_8BIT).freeze =>
|
||||||
|
"somestuff=blah".force_encoding(Encoding::ASCII_8BIT).freeze,
|
||||||
|
"HTTP_HOST".force_encoding(Encoding::ASCII_8BIT).freeze =>
|
||||||
|
"somehost.com".force_encoding(Encoding::ASCII_8BIT).freeze,
|
||||||
|
}
|
||||||
|
req = stub(env: dangerous_hash, remote_ip: "", url: "",
|
||||||
|
path_parameters: {}, query_parameters: {}, request_parameters: {})
|
||||||
|
env_stuff = described_class.useful_http_env_stuff_from_request(req)
|
||||||
|
expect do
|
||||||
|
Utf8Cleaner.recursively_strip_invalid_utf8!(env_stuff, true)
|
||||||
|
end.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,45 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
module Canvas
|
||||||
|
class Errors
|
||||||
|
describe JobInfo do
|
||||||
|
let(:job) do
|
||||||
|
stub(
|
||||||
|
attempts: 1,
|
||||||
|
strand: 'thing',
|
||||||
|
priority: 1,
|
||||||
|
handler: 'Something',
|
||||||
|
run_at: Time.zone.now,
|
||||||
|
max_attempts: 1,
|
||||||
|
tag: "TAG"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:worker){ stub(name: 'workername') }
|
||||||
|
|
||||||
|
let(:info){ described_class.new(job, worker) }
|
||||||
|
|
||||||
|
describe "#to_h" do
|
||||||
|
subject(:hash){ info.to_h }
|
||||||
|
|
||||||
|
it "tags all exceptions as 'BackgroundJob'" do
|
||||||
|
expect(hash[:tags][:process_type]).to eq("BackgroundJob")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes the tag from the job if there is one" do
|
||||||
|
expect(hash[:tags][:job_tag]).to eq("TAG")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "grabs some common attrs from jobs into extras" do
|
||||||
|
expect(hash[:extra][:attempts]).to eq(1)
|
||||||
|
expect(hash[:extra][:strand]).to eq('thing')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes the worker name" do
|
||||||
|
expect(hash[:extra][:worker_name]).to eq('workername')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,36 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
module Canvas
|
||||||
|
describe Errors do
|
||||||
|
before(:each) do
|
||||||
|
described_class.clear_callback_registry!
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:error){ stub("Some Error") }
|
||||||
|
|
||||||
|
it 'fires callbacks when it handles an exception' do
|
||||||
|
called_with = nil
|
||||||
|
Canvas::Errors.register!(:test_thing) do |exception|
|
||||||
|
called_with = exception
|
||||||
|
end
|
||||||
|
Canvas::Errors.capture(error)
|
||||||
|
expect(called_with).to eq(error)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "passes through extra information if available wrapped in extra" do
|
||||||
|
extra_info = nil
|
||||||
|
Canvas::Errors.register!(:test_thing) do |_exception, details|
|
||||||
|
extra_info = details
|
||||||
|
end
|
||||||
|
Canvas::Errors.capture(stub(), {detail1: 'blah'})
|
||||||
|
expect(extra_info).to eq({extra: {detail1: 'blah'}})
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'captures output from each callback according to their registry tag' do
|
||||||
|
Canvas::Errors.register!(:test_thing) do
|
||||||
|
"FOO-BAR"
|
||||||
|
end
|
||||||
|
outputs = Canvas::Errors.capture(stub())
|
||||||
|
expect(outputs[:test_thing]).to eq('FOO-BAR')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -35,7 +35,7 @@ describe ErrorReport do
|
||||||
expect(m).not_to be_nil
|
expect(m).not_to be_nil
|
||||||
expect(m.to).to eql("nobody@nowhere.com")
|
expect(m.to).to eql("nobody@nowhere.com")
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not send emails if not configured" do
|
it "should not send emails if not configured" do
|
||||||
account_model
|
account_model
|
||||||
report = ErrorReport.new
|
report = ErrorReport.new
|
||||||
|
@ -49,7 +49,8 @@ describe ErrorReport do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should not fail with invalid UTF-8" do
|
it "should not fail with invalid UTF-8" do
|
||||||
ErrorReport.log_error('my error', :message => "he\xffllo")
|
data = { extra: { message: "he\xffllo" } }
|
||||||
|
described_class.log_exception_from_canvas_errors('my error', data)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return categories" do
|
it "should return categories" do
|
||||||
|
@ -71,8 +72,9 @@ describe ErrorReport do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should use class name for category" do
|
it "should use class name for category" do
|
||||||
report = ErrorReport.log_exception(nil, e = Exception.new("error"))
|
e = Exception.new("error")
|
||||||
expect(report.category).to eq e.class.name
|
report = described_class.log_exception_from_canvas_errors(e, {extra:{}})
|
||||||
|
expect(report.category).to eq(e.class.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should filter params" do
|
it "should filter params" do
|
||||||
|
@ -88,29 +90,17 @@ describe ErrorReport do
|
||||||
}
|
}
|
||||||
mock_attrs[:url] = mock_attrs[:env]["REQUEST_URI"]
|
mock_attrs[:url] = mock_attrs[:env]["REQUEST_URI"]
|
||||||
req = mock(mock_attrs)
|
req = mock(mock_attrs)
|
||||||
report = ErrorReport.new
|
report = described_class.new
|
||||||
report.assign_data(ErrorReport.useful_http_env_stuff_from_request(req))
|
report.assign_data(Canvas::Errors::Info.useful_http_env_stuff_from_request(req))
|
||||||
expect(report.data["QUERY_STRING"]).to eq "?access_token=[FILTERED]&pseudonym[password]=[FILTERED]"
|
expect(report.data["QUERY_STRING"]).to eq "?access_token=[FILTERED]&pseudonym[password]=[FILTERED]"
|
||||||
expect(report.data["REQUEST_URI"]).to eq "https://www.instructure.example.com?access_token=[FILTERED]&pseudonym[password]=[FILTERED]"
|
|
||||||
|
expected_uri = "https://www.instructure.example.com?"\
|
||||||
|
"access_token=[FILTERED]&pseudonym[password]=[FILTERED]"
|
||||||
|
expect(report.data["REQUEST_URI"]).to eq(expected_uri)
|
||||||
expect(report.data["path_parameters"]).to eq({ :api_key => "[FILTERED]" }.inspect)
|
expect(report.data["path_parameters"]).to eq({ :api_key => "[FILTERED]" }.inspect)
|
||||||
expect(report.data["query_parameters"]).to eq({ "access_token" => "[FILTERED]", "pseudonym[password]" => "[FILTERED]" }.inspect)
|
q_params = { "access_token" => "[FILTERED]", "pseudonym[password]" => "[FILTERED]" }
|
||||||
|
expect(report.data["query_parameters"]).to eq(q_params.inspect)
|
||||||
expect(report.data["request_parameters"]).to eq({ "client_secret" => "[FILTERED]" }.inspect)
|
expect(report.data["request_parameters"]).to eq({ "client_secret" => "[FILTERED]" }.inspect)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".useful_http_env_stuff_from_request" do
|
|
||||||
it "duplicates to get away from frozen strings out of the request.env" do
|
|
||||||
dangerous_hash = {
|
|
||||||
"QUERY_STRING".force_encoding(Encoding::ASCII_8BIT).freeze =>
|
|
||||||
"somestuff=blah".force_encoding(Encoding::ASCII_8BIT).freeze,
|
|
||||||
"HTTP_HOST".force_encoding(Encoding::ASCII_8BIT).freeze =>
|
|
||||||
"somehost.com".force_encoding(Encoding::ASCII_8BIT).freeze,
|
|
||||||
}
|
|
||||||
req = stub(env: dangerous_hash, remote_ip: "", url: "",
|
|
||||||
path_parameters: {}, query_parameters: {}, request_parameters: {})
|
|
||||||
env_stuff = ErrorReport.useful_http_env_stuff_from_request(req)
|
|
||||||
expect{
|
|
||||||
Utf8Cleaner.recursively_strip_invalid_utf8!(env_stuff, true)
|
|
||||||
}.not_to raise_error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue