From 14c6e828820073da658eb39f61d17d1f3cbdb3aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Tue, 22 Jan 2019 13:33:18 +0100 Subject: [PATCH] Create error handler and helper for streaming to multiple receivers --- app/controllers/application_controller.rb | 1 + app/helpers/open_project_error_helper.rb | 35 ++++++++++ lib/open_project.rb | 11 +++ lib/open_project/logging/log_delegator.rb | 82 +++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 app/helpers/open_project_error_helper.rb create mode 100644 lib/open_project/logging/log_delegator.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5206578617f..d39ab3eca4d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -44,6 +44,7 @@ class ApplicationController < ActionController::Base include HookHelper include ::OpenProject::Authentication::SessionExpiry include AdditionalUrlHelpers + include OpenProjectErrorHelper layout 'base' diff --git a/app/helpers/open_project_error_helper.rb b/app/helpers/open_project_error_helper.rb new file mode 100644 index 00000000000..a427368ebda --- /dev/null +++ b/app/helpers/open_project_error_helper.rb @@ -0,0 +1,35 @@ +## +# Logging helper to forward to the OpenProject log delegator +# which will log and report errors appropriately. +module OpenProjectErrorHelper + def op_logger + ::OpenProject.logger + end + + def op_handle_error(message_or_exception, context = {}) + ::OpenProject.logger.error message_or_exception, context.merge(op_logging_context) + end + + def op_handle_warning(message_or_exception, context = {}) + ::OpenProject.logger.warn message_or_exception, context.merge(op_logging_context) + end + + def op_handle_info(message_or_exception, context = {}) + ::OpenProject.logger.info message_or_exception, context.merge(op_logging_context) + end + + def op_handle_debug(message_or_exception, context = {}) + ::OpenProject.logger.debug message_or_exception, context.merge(op_logging_context) + end + + private + + def op_logging_context + { + current_user: current_user, + params: params, + request: try(:request), + env: try(:env), + } + end +end diff --git a/lib/open_project.rb b/lib/open_project.rb index efdabdf7ad3..9c06d2d17bd 100644 --- a/lib/open_project.rb +++ b/lib/open_project.rb @@ -32,6 +32,7 @@ require 'redmine/menu_manager' require 'redmine/activity' require 'redmine/search' require 'open_project/custom_field_format' +require 'open_project/logging/log_delegator' require 'redmine/mime_type' require 'redmine/core_ext' require 'open_project/design' @@ -41,3 +42,13 @@ require 'redmine/plugin' require 'redmine/notifiable' require 'csv' + +module OpenProject + + ## + # Shortcut to the OpenProject log delegator, which extends + # default Rails error handling with other error handlers such as sentry. + def self.logger + ::OpenProject::Logging::LogDelegator + end +end diff --git a/lib/open_project/logging/log_delegator.rb b/lib/open_project/logging/log_delegator.rb new file mode 100644 index 00000000000..35da3bc08d1 --- /dev/null +++ b/lib/open_project/logging/log_delegator.rb @@ -0,0 +1,82 @@ +module OpenProject + module Logging + class LogDelegator + class << self + + ## + # Consume a message and let it be handled + # by all handlers + def log(exception, context = {}) + message = + if exception.is_a? Exception + context[:exception] = exception + context[:backtrace] = clean_backtrace(exception) + message = "#{exception}: #{exception.message}" + else + exception.to_s + end + + # Set current contexts + context[:level] ||= context[:exception] ? :error : :info + context[:current_user] ||= User.current + + registered_handlers.each do |handler| + handler.call message, context + end + + nil + end + + %i(debug info warn error fatal unknown).each do |level| + define_method(level) do |*args| + message = args.shift + context = args.shift || {} + + log(message, context.merge(level: level)) + end + end + + ## + # Get a clean backtrace + def clean_backtrace(exception) + return nil unless exception&.backtrace + Rails.backtrace_cleaner.clean exception.backtrace + end + + ## + # The active set of error handlers + def registered_handlers + @handlers ||= default_handlers + end + + private + + def default_handlers + [method(:rails_logger_handler)] + end + + ## + # A lambda handler for logging the error + # to rails. + def rails_logger_handler(message, context = {}) + Rails.logger.public_send( + context[:level], + message + context_string(context) + ) + + if backtrace = context[:backtrace] + Rails.logger.debug { backtrace.join($/) } + end + end + + ## + # Create a context string + def context_string(context) + ''.tap do |str| + str << " [user=#{context[:user].id}]" if context[:user] + end + end + end + end + end +end