Module: LogStruct::Integrations::RackErrorHandler::ErrorLines
- Extended by:
- T::Sig
- Defined in:
- lib/log_struct/integrations/rack_error_handler/error_lines.rb
Overview
The structured lines for errors raised while Rails handles a request. Each error is logged once: security errors from the app by DebugExceptions' interceptor, anything else that gets past ShowExceptions by the middleware. Rails still turns each error into its usual response.
Constant Summary collapse
- CSRF_ERROR_NAMES =
T.let( ["ActionController::InvalidAuthenticityToken", "ActionController::InvalidCrossOriginRequest"].freeze, T::Array[String] )
Class Method Summary collapse
- .enabled? ⇒ Boolean
-
.intercept(request, error) ⇒ void
For DebugExceptions.register_interceptor: CSRF and IP spoofing errors from the app, which ShowExceptions would otherwise turn into a response before any middleware saw them.
- .ip_spoof_error?(error) ⇒ Boolean
-
.log_escaped(request, error) ⇒ void
An error that got past ShowExceptions (or came from a middleware outside it).
-
.log_security_error(request, error) ⇒ void
A security line, and the error handled in the security_errors mode (reported by default).
-
.log_unhandled(request, wrapper) ⇒ void
An error the app didn't handle, which DebugExceptions logs before ShowExceptions renders Rails' error page: at config.action_dispatch.debug_exception_log_level (fatal by default), with the trace Rails would log and the response's status.
- .request_context(request) ⇒ Hash{Symbol => T.untyped}
- .security_error?(error) ⇒ Boolean
-
.source_ip(request) ⇒ String?
The client's IP, as Rails works it out, or the connection's when Rails finds a spoofed one.
Class Method Details
.enabled? ⇒ Boolean
21 22 23 |
# File 'lib/log_struct/integrations/rack_error_handler/error_lines.rb', line 21 def self.enabled? LogStruct.enabled? && LogStruct.config.integrations.enable_rack_error_handler end |
.intercept(request, error) ⇒ void
This method returns an undefined value.
For DebugExceptions.register_interceptor: CSRF and IP spoofing errors from the app, which ShowExceptions would otherwise turn into a response before any middleware saw them. Other errors are left to Rails' own logging and error reporting.
29 30 31 32 33 34 35 |
# File 'lib/log_struct/integrations/rack_error_handler/error_lines.rb', line 29 def self.intercept(request, error) return unless enabled? return unless security_error?(error) return if LoggedErrors.logged?(error) log_security_error(request, error) end |
.ip_spoof_error?(error) ⇒ Boolean
154 155 156 |
# File 'lib/log_struct/integrations/rack_error_handler/error_lines.rb', line 154 def self.ip_spoof_error?(error) error.is_a?(::ActionDispatch::RemoteIp::IpSpoofAttackError) end |
.log_escaped(request, error) ⇒ void
This method returns an undefined value.
An error that got past ShowExceptions (or came from a middleware outside it)
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/log_struct/integrations/rack_error_handler/error_lines.rb', line 39 def self.log_escaped(request, error) return unless enabled? return if LoggedErrors.logged?(error) return log_security_error(request, error) if security_error?(error) LoggedErrors.mark(error) LogStruct.error( Log::Error.new( source: Source::Rails, error_class: error.class, message: ParamsErrors.(error).first, backtrace: error.backtrace, path: request.path, http_method: request.method, source_ip: source_ip(request), user_agent: request.user_agent, referer: StringScrubber.without_query(request.referer) ) ) end |
.log_security_error(request, error) ⇒ void
This method returns an undefined value.
A security line, and the error handled in the security_errors mode (reported by default)
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/log_struct/integrations/rack_error_handler/error_lines.rb', line 97 def self.log_security_error(request, error) LoggedErrors.mark(error) if ip_spoof_error?(error) LogStruct.warn( Log::Security::IPSpoof.new( path: request.path, http_method: request.method, user_agent: request.user_agent, referer: StringScrubber.without_query(request.referer), message: error., source_ip: request.ip, client_ip: request.get_header("HTTP_CLIENT_IP"), x_forwarded_for: request.x_forwarded_for, timestamp: Time.now ) ) else LogStruct.error( Log::Security::CSRFViolation.new( path: request.path, http_method: request.method, source_ip: source_ip(request), user_agent: request.user_agent, referer: StringScrubber.without_query(request.referer), message: error., timestamp: Time.now ) ) end reported = error.is_a?(StandardError) ? error : RuntimeError.new(error.) LogStruct.handle_exception(reported, source: Source::Security, context: request_context(request), log: false) end |
.log_unhandled(request, wrapper) ⇒ void
This method returns an undefined value.
An error the app didn't handle, which DebugExceptions logs before ShowExceptions renders Rails' error page: at config.action_dispatch.debug_exception_log_level (fatal by default), with the trace Rails would log and the response's status. A security error already has its own line.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/log_struct/integrations/rack_error_handler/error_lines.rb', line 64 def self.log_unhandled(request, wrapper) error = T.cast(wrapper.exception, Exception) return if LoggedErrors.logged?(error) LoggedErrors.mark(error) # Without a backtrace cleaner, Rails' trace is Thread::Backtrace::Locations trace = Array(wrapper.exception_trace).map(&:to_s) , = ParamsErrors.(error) line = Log::Error.new( source: Source::Rails, error_class: error.class, message: , backtrace: trace.empty? ? error.backtrace : trace, path: request.path, http_method: request.method, source_ip: source_ip(request), user_agent: request.user_agent, referer: StringScrubber.without_query(request.referer), status: wrapper.status_code, cause_class: error.cause&.class&.name, cause_message: ) case request.get_header("action_dispatch.debug_exception_log_level") when ::Logger::DEBUG then LogStruct.debug(line) when ::Logger::INFO then LogStruct.info(line) when ::Logger::WARN then LogStruct.warn(line) when ::Logger::ERROR then LogStruct.error(line) else LogStruct.fatal(line) end end |
.request_context(request) ⇒ Hash{Symbol => T.untyped}
131 132 133 134 135 136 137 138 |
# File 'lib/log_struct/integrations/rack_error_handler/error_lines.rb', line 131 def self.request_context(request) { path: request.path, method: request.method, user_agent: request.user_agent, referer: StringScrubber.without_query(request.referer) } end |
.security_error?(error) ⇒ Boolean
149 150 151 |
# File 'lib/log_struct/integrations/rack_error_handler/error_lines.rb', line 149 def self.security_error?(error) ip_spoof_error?(error) || CSRF_ERROR_NAMES.include?(error.class.name) end |
.source_ip(request) ⇒ String?
The client's IP, as Rails works it out, or the connection's when Rails finds a spoofed one
142 143 144 145 146 |
# File 'lib/log_struct/integrations/rack_error_handler/error_lines.rb', line 142 def self.source_ip(request) request.remote_ip rescue ::ActionDispatch::RemoteIp::IpSpoofAttackError request.ip end |