logstruct 0.1.11 → 0.2.0

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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +146 -1
  3. data/README.md +76 -68
  4. data/lib/log_struct/concerns/configuration.rb +139 -91
  5. data/lib/log_struct/concerns/error_handling.rb +36 -6
  6. data/lib/log_struct/config_struct/error_handling_modes.rb +3 -1
  7. data/lib/log_struct/config_struct/filters.rb +126 -22
  8. data/lib/log_struct/config_struct/integrations.rb +0 -4
  9. data/lib/log_struct/config_struct/limits.rb +29 -0
  10. data/lib/log_struct/configuration.rb +2 -0
  11. data/lib/log_struct/enums/event.rb +3 -1
  12. data/lib/log_struct/enums/log_field.rb +5 -0
  13. data/lib/log_struct/formatter.rb +304 -56
  14. data/lib/log_struct/hash_utils.rb +33 -4
  15. data/lib/log_struct/integrations/action_mailer/error_handling.rb +78 -20
  16. data/lib/log_struct/integrations/action_mailer/fallback_error_handlers.rb +81 -0
  17. data/lib/log_struct/integrations/action_mailer/metadata_collection.rb +0 -20
  18. data/lib/log_struct/integrations/action_mailer.rb +22 -17
  19. data/lib/log_struct/integrations/active_job/log_subscriber.rb +177 -25
  20. data/lib/log_struct/integrations/active_job.rb +2 -7
  21. data/lib/log_struct/integrations/active_model_serializers.rb +9 -3
  22. data/lib/log_struct/integrations/active_record.rb +45 -17
  23. data/lib/log_struct/integrations/active_storage.rb +31 -31
  24. data/lib/log_struct/integrations/ahoy.rb +19 -15
  25. data/lib/log_struct/integrations/carrierwave.rb +55 -51
  26. data/lib/log_struct/integrations/dotenv.rb +22 -32
  27. data/lib/log_struct/integrations/good_job/logger.rb +30 -24
  28. data/lib/log_struct/integrations/good_job.rb +111 -40
  29. data/lib/log_struct/integrations/host_authorization.rb +47 -36
  30. data/lib/log_struct/integrations/lograge.rb +39 -4
  31. data/lib/log_struct/integrations/puma.rb +74 -34
  32. data/lib/log_struct/integrations/rack_error_handler/debug_exceptions_logging.rb +35 -0
  33. data/lib/log_struct/integrations/rack_error_handler/error_lines.rb +160 -0
  34. data/lib/log_struct/integrations/rack_error_handler/middleware.rb +14 -169
  35. data/lib/log_struct/integrations/rack_error_handler/parse_error_logging.rb +36 -0
  36. data/lib/log_struct/integrations/rack_error_handler.rb +20 -4
  37. data/lib/log_struct/integrations/rack_middleware.rb +23 -0
  38. data/lib/log_struct/integrations/rails_log_subscribers.rb +32 -0
  39. data/lib/log_struct/integrations/request_context/middleware.rb +4 -9
  40. data/lib/log_struct/integrations/shrine.rb +48 -73
  41. data/lib/log_struct/integrations/sidekiq/job_context_middleware.rb +32 -0
  42. data/lib/log_struct/integrations/sidekiq/logger.rb +17 -9
  43. data/lib/log_struct/integrations/sidekiq.rb +98 -7
  44. data/lib/log_struct/integrations/sorbet.rb +10 -46
  45. data/lib/log_struct/integrations.rb +3 -2
  46. data/lib/log_struct/log/{good_job/enqueue.rb → active_job/discard.rb} +13 -13
  47. data/lib/log_struct/log/active_job/enqueue.rb +0 -4
  48. data/lib/log_struct/log/{good_job/schedule.rb → active_job/error.rb} +15 -15
  49. data/lib/log_struct/log/{good_job/start.rb → active_job/retry.rb} +13 -13
  50. data/lib/log_struct/log/{good_job/finish.rb → active_job/retry_stopped.rb} +13 -17
  51. data/lib/log_struct/log/active_job.rb +4 -0
  52. data/lib/log_struct/log/active_storage/delete.rb +6 -2
  53. data/lib/log_struct/log/active_storage/download.rb +2 -4
  54. data/lib/log_struct/log/active_storage/exist.rb +2 -2
  55. data/lib/log_struct/log/active_storage/metadata.rb +4 -4
  56. data/lib/log_struct/log/active_storage/upload.rb +2 -10
  57. data/lib/log_struct/log/active_storage/url.rb +2 -6
  58. data/lib/log_struct/log/active_storage.rb +2 -3
  59. data/lib/log_struct/log/error.rb +24 -1
  60. data/lib/log_struct/log/good_job.rb +0 -4
  61. data/lib/log_struct/log/puma/error.rb +63 -0
  62. data/lib/log_struct/log/puma.rb +1 -0
  63. data/lib/log_struct/log/security/blocked_host.rb +0 -4
  64. data/lib/log_struct/log/shrine/exist.rb +0 -2
  65. data/lib/log_struct/log/shrine/metadata.rb +2 -4
  66. data/lib/log_struct/log/shrine/upload.rb +4 -0
  67. data/lib/log_struct/log/sidekiq/error.rb +63 -0
  68. data/lib/log_struct/log/{active_storage/stream.rb → sidekiq/log.rb} +17 -11
  69. data/lib/log_struct/log/sidekiq.rb +3 -45
  70. data/lib/log_struct/log.rb +4 -2
  71. data/lib/log_struct/logged_errors.rb +25 -0
  72. data/lib/log_struct/monkey_patches/active_support/tagged_logging/formatter.rb +2 -3
  73. data/lib/log_struct/param_filters.rb +51 -38
  74. data/lib/log_struct/params_errors.rb +78 -0
  75. data/lib/log_struct/rails_boot_banner_silencer.rb +7 -2
  76. data/lib/log_struct/railtie.rb +29 -14
  77. data/lib/log_struct/semantic_logger/color_formatter.rb +60 -53
  78. data/lib/log_struct/semantic_logger/concerns/log_methods.rb +65 -57
  79. data/lib/log_struct/semantic_logger/formatter.rb +79 -57
  80. data/lib/log_struct/semantic_logger/logger.rb +29 -53
  81. data/lib/log_struct/semantic_logger/reopen_after_fork.rb +40 -0
  82. data/lib/log_struct/semantic_logger/setup.rb +115 -112
  83. data/lib/log_struct/shared/merge_additional_data_fields.rb +14 -1
  84. data/lib/log_struct/shared/serialize_common.rb +2 -7
  85. data/lib/log_struct/string_scrubber.rb +229 -20
  86. data/lib/log_struct/version.rb +1 -1
  87. data/lib/log_struct.rb +6 -4
  88. data/logstruct.gemspec +19 -10
  89. metadata +63 -11
  90. data/lib/log_struct/builders/active_job.rb +0 -84
  91. data/lib/log_struct/integrations/good_job/log_subscriber.rb +0 -179
  92. data/lib/log_struct/integrations/rack.rb +0 -31
@@ -0,0 +1,160 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../../params_errors"
5
+
6
+ module LogStruct
7
+ module Integrations
8
+ module RackErrorHandler
9
+ # The structured lines for errors raised while Rails handles a request. Each error is logged once:
10
+ # security errors from the app by DebugExceptions' interceptor, anything else that gets past
11
+ # ShowExceptions by the middleware. Rails still turns each error into its usual response.
12
+ module ErrorLines
13
+ extend T::Sig
14
+
15
+ CSRF_ERROR_NAMES = T.let(
16
+ ["ActionController::InvalidAuthenticityToken", "ActionController::InvalidCrossOriginRequest"].freeze,
17
+ T::Array[String]
18
+ )
19
+
20
+ sig { returns(T::Boolean) }
21
+ def self.enabled?
22
+ LogStruct.enabled? && LogStruct.config.integrations.enable_rack_error_handler
23
+ end
24
+
25
+ # For DebugExceptions.register_interceptor: CSRF and IP spoofing errors from the app, which
26
+ # ShowExceptions would otherwise turn into a response before any middleware saw them.
27
+ # Other errors are left to Rails' own logging and error reporting.
28
+ sig { params(request: ::ActionDispatch::Request, error: Exception).void }
29
+ def self.intercept(request, error)
30
+ return unless enabled?
31
+ return unless security_error?(error)
32
+ return if LoggedErrors.logged?(error)
33
+
34
+ log_security_error(request, error)
35
+ end
36
+
37
+ # An error that got past ShowExceptions (or came from a middleware outside it)
38
+ sig { params(request: ::ActionDispatch::Request, error: StandardError).void }
39
+ def self.log_escaped(request, error)
40
+ return unless enabled?
41
+ return if LoggedErrors.logged?(error)
42
+ return log_security_error(request, error) if security_error?(error)
43
+
44
+ LoggedErrors.mark(error)
45
+ LogStruct.error(
46
+ Log::Error.new(
47
+ source: Source::Rails,
48
+ error_class: error.class,
49
+ message: ParamsErrors.messages(error).first,
50
+ backtrace: error.backtrace,
51
+ path: request.path,
52
+ http_method: request.method,
53
+ source_ip: source_ip(request),
54
+ user_agent: request.user_agent,
55
+ referer: StringScrubber.without_query(request.referer)
56
+ )
57
+ )
58
+ end
59
+
60
+ # An error the app didn't handle, which DebugExceptions logs before ShowExceptions renders Rails'
61
+ # error page: at config.action_dispatch.debug_exception_log_level (fatal by default), with the
62
+ # trace Rails would log and the response's status. A security error already has its own line.
63
+ sig { params(request: ::ActionDispatch::Request, wrapper: T.untyped).void }
64
+ def self.log_unhandled(request, wrapper)
65
+ error = T.cast(wrapper.exception, Exception)
66
+ return if LoggedErrors.logged?(error)
67
+
68
+ LoggedErrors.mark(error)
69
+ # Without a backtrace cleaner, Rails' trace is Thread::Backtrace::Locations
70
+ trace = Array(wrapper.exception_trace).map(&:to_s)
71
+ message, cause_message = ParamsErrors.messages(error)
72
+ line = Log::Error.new(
73
+ source: Source::Rails,
74
+ error_class: error.class,
75
+ message: message,
76
+ backtrace: trace.empty? ? error.backtrace : trace,
77
+ path: request.path,
78
+ http_method: request.method,
79
+ source_ip: source_ip(request),
80
+ user_agent: request.user_agent,
81
+ referer: StringScrubber.without_query(request.referer),
82
+ status: wrapper.status_code,
83
+ cause_class: error.cause&.class&.name,
84
+ cause_message: cause_message
85
+ )
86
+ case request.get_header("action_dispatch.debug_exception_log_level")
87
+ when ::Logger::DEBUG then LogStruct.debug(line)
88
+ when ::Logger::INFO then LogStruct.info(line)
89
+ when ::Logger::WARN then LogStruct.warn(line)
90
+ when ::Logger::ERROR then LogStruct.error(line)
91
+ else LogStruct.fatal(line)
92
+ end
93
+ end
94
+
95
+ # A security line, and the error handled in the security_errors mode (reported by default)
96
+ sig { params(request: ::ActionDispatch::Request, error: Exception).void }
97
+ def self.log_security_error(request, error)
98
+ LoggedErrors.mark(error)
99
+ if ip_spoof_error?(error)
100
+ LogStruct.warn(
101
+ Log::Security::IPSpoof.new(
102
+ path: request.path,
103
+ http_method: request.method,
104
+ user_agent: request.user_agent,
105
+ referer: StringScrubber.without_query(request.referer),
106
+ message: error.message,
107
+ source_ip: request.ip,
108
+ client_ip: request.get_header("HTTP_CLIENT_IP"),
109
+ x_forwarded_for: request.x_forwarded_for,
110
+ timestamp: Time.now
111
+ )
112
+ )
113
+ else
114
+ LogStruct.error(
115
+ Log::Security::CSRFViolation.new(
116
+ path: request.path,
117
+ http_method: request.method,
118
+ source_ip: source_ip(request),
119
+ user_agent: request.user_agent,
120
+ referer: StringScrubber.without_query(request.referer),
121
+ message: error.message,
122
+ timestamp: Time.now
123
+ )
124
+ )
125
+ end
126
+ reported = error.is_a?(StandardError) ? error : RuntimeError.new(error.message)
127
+ LogStruct.handle_exception(reported, source: Source::Security, context: request_context(request), log: false)
128
+ end
129
+
130
+ sig { params(request: ::ActionDispatch::Request).returns(T::Hash[Symbol, T.untyped]) }
131
+ def self.request_context(request)
132
+ {
133
+ path: request.path,
134
+ method: request.method,
135
+ user_agent: request.user_agent,
136
+ referer: StringScrubber.without_query(request.referer)
137
+ }
138
+ end
139
+
140
+ # The client's IP, as Rails works it out, or the connection's when Rails finds a spoofed one
141
+ sig { params(request: ::ActionDispatch::Request).returns(T.nilable(String)) }
142
+ def self.source_ip(request)
143
+ request.remote_ip
144
+ rescue ::ActionDispatch::RemoteIp::IpSpoofAttackError
145
+ request.ip
146
+ end
147
+
148
+ sig { params(error: Exception).returns(T::Boolean) }
149
+ def self.security_error?(error)
150
+ ip_spoof_error?(error) || CSRF_ERROR_NAMES.include?(error.class.name)
151
+ end
152
+
153
+ sig { params(error: Exception).returns(T::Boolean) }
154
+ def self.ip_spoof_error?(error)
155
+ error.is_a?(::ActionDispatch::RemoteIp::IpSpoofAttackError)
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -1,181 +1,26 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require_relative "../rack_middleware"
5
+ require_relative "error_lines"
6
+
4
7
  module LogStruct
5
8
  module Integrations
6
9
  module RackErrorHandler
7
- # Custom middleware to enhance Rails error logging with JSON format and request details
8
- class Middleware
9
- extend T::Sig
10
-
11
- # IP Spoofing error response
12
- IP_SPOOF_HTML = T.let(
13
- "<html><head><title>IP Spoofing Detected</title></head><body>" \
14
- "<h1>Forbidden</h1>" \
15
- "<p>IP spoofing detected. This request has been blocked for security reasons.</p>" \
16
- "</body></html>",
17
- String
18
- )
19
-
20
- # CSRF error response
21
- CSRF_HTML = T.let(
22
- "<html><head><title>CSRF Error</title></head><body>" \
23
- "<h1>Forbidden</h1>" \
24
- "<p>Invalid authenticity token. This request has been blocked to prevent cross-site request forgery.</p>" \
25
- "</body></html>",
26
- String
27
- )
28
-
29
- # Response headers calculated at load time
30
- IP_SPOOF_HEADERS = T.let(
31
- {
32
- "Content-Type" => "text/html",
33
- "Content-Length" => IP_SPOOF_HTML.bytesize.to_s
34
- }.freeze,
35
- T::Hash[String, String]
36
- )
37
-
38
- CSRF_HEADERS = T.let(
39
- {
40
- "Content-Type" => "text/html",
41
- "Content-Length" => CSRF_HTML.bytesize.to_s
42
- }.freeze,
43
- T::Hash[String, String]
44
- )
45
-
46
- # HTTP status code for forbidden responses
47
- FORBIDDEN_STATUS = T.let(403, Integer)
48
-
49
- sig { params(app: T.untyped).void }
50
- def initialize(app)
51
- @app = app
52
- end
53
-
54
- sig { params(env: T.untyped).returns(T.untyped) }
10
+ # Logs the errors that get past ShowExceptions, or come from the middleware outside it (an IP
11
+ # spoofing error from a middleware that reads remote_ip, for example), then raises them again
12
+ # for the server to handle as it would without LogStruct
13
+ class Middleware < RackMiddleware
14
+ sig { override.params(env: T.untyped).returns(T.untyped) }
55
15
  def call(env)
56
- return @app.call(env) unless LogStruct.enabled?
57
-
58
- request = ::ActionDispatch::Request.new(env)
59
-
60
- begin
61
- # Trigger the same spoofing checks that ActionDispatch::RemoteIp performs after
62
- # it is initialized in the middleware stack. We run this manually because we
63
- # execute before that middleware and still want spoofing attacks to surface here.
64
- perform_remote_ip_check!(request)
65
-
66
- @app.call(env)
67
- rescue ::ActionDispatch::RemoteIp::IpSpoofAttackError => ip_spoof_error
68
- # Create a security log for IP spoofing
69
- security_log = Log::Security::IPSpoof.new(
70
- path: env["PATH_INFO"],
71
- http_method: env["REQUEST_METHOD"],
72
- user_agent: env["HTTP_USER_AGENT"],
73
- referer: env["HTTP_REFERER"],
74
- message: ip_spoof_error.message,
75
- client_ip: env["HTTP_CLIENT_IP"],
76
- x_forwarded_for: env["HTTP_X_FORWARDED_FOR"],
77
- timestamp: Time.now
78
- )
79
-
80
- ::Rails.logger.warn(security_log)
81
-
82
- [FORBIDDEN_STATUS, IP_SPOOF_HEADERS.dup, [IP_SPOOF_HTML]]
83
- rescue => error
84
- if csrf_error?(error)
85
- # Create a security log for CSRF error
86
- security_log = Log::Security::CSRFViolation.new(
87
- path: request.path,
88
- http_method: request.method,
89
- source_ip: request.remote_ip,
90
- user_agent: request.user_agent,
91
- referer: request.referer,
92
- message: error.message,
93
- timestamp: Time.now
94
- )
95
- LogStruct.error(security_log)
96
-
97
- # Report to error reporting service and/or re-raise
98
- context = extract_request_context(env, request)
99
- LogStruct.handle_exception(error, source: Source::Security, context: context)
100
-
101
- # If handle_exception raised an exception then Rails will deal with it (e.g. config.exceptions_app)
102
- # If we are only logging or reporting these security errors, then return a default response
103
- [FORBIDDEN_STATUS, CSRF_HEADERS.dup, [CSRF_HTML]]
104
- else
105
- # Extract request context for error reporting
106
- context = extract_request_context(env, request)
107
-
108
- # Create and log a structured exception with request context
109
- exception_log = Log.from_exception(Source::Rails, error, context)
110
- LogStruct.error(exception_log)
111
-
112
- # Re-raise any standard errors to let Rails or error reporter handle it.
113
- # Rails will also log the request details separately
114
- raise error
115
- end
116
- end
117
- end
118
-
119
- private
120
-
121
- sig { params(request: ::ActionDispatch::Request).void }
122
- def perform_remote_ip_check!(request)
123
- action_dispatch_config = ::Rails.application.config.action_dispatch
124
- check_ip = action_dispatch_config.ip_spoofing_check
125
- return unless check_ip
126
-
127
- proxies = normalized_trusted_proxies(action_dispatch_config.trusted_proxies)
128
-
129
- ::ActionDispatch::RemoteIp::GetIp
130
- .new(request, check_ip, proxies)
131
- .to_s
132
- end
133
-
134
- sig { params(env: T::Hash[String, T.untyped], request: T.nilable(::ActionDispatch::Request)).returns(T::Hash[Symbol, T.untyped]) }
135
- def extract_request_context(env, request = nil)
136
- request ||= ::ActionDispatch::Request.new(env)
137
- {
138
- path: request.path,
139
- method: request.method,
140
- user_agent: request.user_agent,
141
- referer: request.referer
142
- }
16
+ @app.call(env)
143
17
  rescue => error
144
- # If we can't extract request context, return minimal info
145
- {error_extracting_context: error.message}
146
- end
147
-
148
- sig { params(error: StandardError).returns(T::Boolean) }
149
- def csrf_error?(error)
150
- error_name = error.class.name
151
- error_name == "ActionController::InvalidAuthenticityToken" ||
152
- error_name == "ActionController::InvalidCrossOriginRequest"
153
- end
154
-
155
- sig { params(configured_proxies: T.untyped).returns(T.untyped) }
156
- def normalized_trusted_proxies(configured_proxies)
157
- if configured_proxies.nil? || (configured_proxies.respond_to?(:empty?) && configured_proxies.empty?)
158
- return ::ActionDispatch::RemoteIp::TRUSTED_PROXIES
18
+ begin
19
+ ErrorLines.log_escaped(::ActionDispatch::Request.new(env), error)
20
+ rescue => logging_error
21
+ LogStruct.handle_exception(logging_error, source: Source::Internal)
159
22
  end
160
-
161
- return configured_proxies if configured_proxies.respond_to?(:any?)
162
-
163
- raise(
164
- ArgumentError,
165
- <<~EOM
166
- Setting config.action_dispatch.trusted_proxies to a single value isn't
167
- supported. Please set this to an enumerable instead. For
168
- example, instead of:
169
-
170
- config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8")
171
-
172
- Wrap the value in an Array:
173
-
174
- config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")]
175
-
176
- Note that passing an enumerable will *replace* the default set of trusted proxies.
177
- EOM
178
- )
23
+ raise error
179
24
  end
180
25
  end
181
26
  end
@@ -0,0 +1,36 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LogStruct
5
+ module Integrations
6
+ module RackErrorHandler
7
+ # Prepended to ActionDispatch::Request. When Rails can't parse a request's body (malformed JSON), it logs the
8
+ # whole body at debug level, where nothing filters it. LogStruct logs the body's size and type instead. The
9
+ # request line and the ParseError's error line are written as usual.
10
+ module ParseErrorLogging
11
+ extend T::Sig
12
+ extend T::Helpers
13
+
14
+ requires_ancestor { ::ActionDispatch::Request }
15
+
16
+ private
17
+
18
+ sig { returns(T.untyped) }
19
+ def log_parse_error_once
20
+ return super unless ErrorLines.enabled?
21
+
22
+ @logstruct_parse_error_logged ||= T.let(
23
+ begin
24
+ (logger || ::ActiveSupport::Logger.new($stderr)).debug(
25
+ "Error occurred while parsing request parameters. LogStruct leaves out the body " \
26
+ "(#{raw_post.to_s.bytesize} bytes of #{media_type}), which it can't filter."
27
+ )
28
+ true
29
+ end,
30
+ T.nilable(TrueClass)
31
+ )
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -2,8 +2,10 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "rack"
5
- require "action_dispatch/middleware/show_exceptions"
5
+ require "action_dispatch/middleware/debug_exceptions"
6
6
  require_relative "rack_error_handler/middleware"
7
+ require_relative "rack_error_handler/debug_exceptions_logging"
8
+ require_relative "rack_error_handler/parse_error_logging"
7
9
  require_relative "rack_setup"
8
10
 
9
11
  module LogStruct
@@ -13,17 +15,31 @@ module LogStruct
13
15
  extend T::Sig
14
16
  extend IntegrationInterface
15
17
 
16
- # Set up Rack middleware for structured error logging
18
+ @interceptor_registered = T.let(false, T::Boolean)
19
+
20
+ # Set up structured lines for CSRF and IP spoofing errors, errors the app didn't handle, and
21
+ # errors that get past ShowExceptions
17
22
  sig { override.params(config: LogStruct::Configuration).returns(T.nilable(T::Boolean)) }
18
23
  def self.setup(config)
19
24
  return nil unless RackSetup.enabled?(config)
20
25
 
21
- # Add structured logging middleware for security violations and errors
22
- # Need to insert before RemoteIp to catch IP spoofing errors it raises
26
+ # Errors from the middleware outside ShowExceptions, and any that get past it
23
27
  RackSetup.insert_before(
24
28
  ::ActionDispatch::RemoteIp,
25
29
  Integrations::RackErrorHandler::Middleware
26
30
  )
31
+ # DebugExceptions calls its interceptors with each error from the app (in every environment),
32
+ # before ShowExceptions turns it into Rails' usual response: 422 for a CSRF error
33
+ unless @interceptor_registered
34
+ ::ActionDispatch::DebugExceptions.register_interceptor(ErrorLines.method(:intercept))
35
+ @interceptor_registered = true
36
+ end
37
+ # An error line instead of the text Rails logs for an error the app didn't handle
38
+ unless ::ActionDispatch::DebugExceptions <= DebugExceptionsLogging
39
+ ::ActionDispatch::DebugExceptions.prepend(DebugExceptionsLogging)
40
+ end
41
+ # The size of a body Rails can't parse, instead of the body
42
+ ::ActionDispatch::Request.prepend(ParseErrorLogging) unless ::ActionDispatch::Request <= ParseErrorLogging
27
43
 
28
44
  true
29
45
  end
@@ -0,0 +1,23 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LogStruct
5
+ module Integrations
6
+ # A Rack middleware around the app: subclasses define call, and pass the request on to @app
7
+ class RackMiddleware
8
+ extend T::Sig
9
+ extend T::Helpers
10
+
11
+ abstract!
12
+
13
+ sig { params(app: T.untyped).void }
14
+ def initialize(app)
15
+ @app = app
16
+ end
17
+
18
+ sig { abstract.params(env: T.untyped).returns(T.untyped) }
19
+ def call(env)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,32 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LogStruct
5
+ module Integrations
6
+ # Rails' own log subscribers, which LogStruct's lines replace
7
+ module RailsLogSubscribers
8
+ extend T::Sig
9
+
10
+ # Detaches a framework's log subscriber (ActiveStorage::LogSubscriber, :active_storage). Up to Rails 8.1 it's an
11
+ # ActiveSupport::LogSubscriber attached to the framework's notifications; from Rails 8.2 it's an
12
+ # ActiveSupport::EventReporter::LogSubscriber, subscribed to Rails.event when its file loads.
13
+ sig { params(subscriber_class: T.untyped, namespace: Symbol).void }
14
+ def self.detach(subscriber_class, namespace)
15
+ if subscriber_class.respond_to?(:detach_from)
16
+ subscriber_class.detach_from(namespace)
17
+ else
18
+ unsubscribe_from_events(subscriber_class)
19
+ end
20
+ end
21
+
22
+ # Unsubscribes a Rails 8.2 log subscriber from Rails.event. Does nothing for one that isn't subscribed there.
23
+ sig { params(subscriber_class: T.untyped).void }
24
+ def self.unsubscribe_from_events(subscriber_class)
25
+ return unless ::ActiveSupport.respond_to?(:event_reporter)
26
+
27
+ reporter = ::ActiveSupport.event_reporter
28
+ reporter.unsubscribe(subscriber_class) if reporter.respond_to?(:unsubscribe)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,20 +1,15 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require_relative "../rack_middleware"
5
+
4
6
  module LogStruct
5
7
  module Integrations
6
8
  module RequestContext
7
9
  # Middleware that captures request_id and stores it in SemanticLogger's
8
10
  # named_tags so all logs during the request include the request_id.
9
- class Middleware
10
- extend T::Sig
11
-
12
- sig { params(app: T.untyped).void }
13
- def initialize(app)
14
- @app = app
15
- end
16
-
17
- sig { params(env: T.untyped).returns(T.untyped) }
11
+ class Middleware < RackMiddleware
12
+ sig { override.params(env: T.untyped).returns(T.untyped) }
18
13
  def call(env)
19
14
  request = ::ActionDispatch::Request.new(env)
20
15
  request_id = request.request_id