logstruct 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3634f56776f895f97747c8165065651aca55098520469624f73f2d75b090f33c
4
- data.tar.gz: c29661ad9e8044bad356a5a699f9aaed053c55ec124226784c60dd78655e794f
3
+ metadata.gz: b138e5ba6e0739ee31d032606f4d7508463deeee00b64e3a870d34d4316e7567
4
+ data.tar.gz: f36bc4a9cb5ee2ab42b77a581efd1b28c7c23a75191429ac5b5926023d33a594
5
5
  SHA512:
6
- metadata.gz: c491c3087d5341e5ab88bc607c2596be06fb90c482b74498b5aebd5a0eb5ef92f917b889b3ea26f56cb5a69be756bbc0afabfb957bff557cbbd7615f45b3934b
7
- data.tar.gz: '090f85673e396225d96254c14fdfcdc941904354d3c11700be60b93e83e7c2e230bcbc15c8436ba2e0491223c77e0498f33d866beddb1e34379b7e4863916d1b'
6
+ metadata.gz: c06589605729de5ac9ea44694e82712d2d175201adab6052b6eff1a2ae2cc8cc6b476ed7200d1fa6bf6a79db6eb280847b4ca34559a501bc327129db4574acfe
7
+ data.tar.gz: 992fc803c47c57367ca906179bf9b356618e42bf840ac6a3de4b7296d90be0db8d513177c57643e1564967624f444d0af00f2582844e4d68825a03f8e911ac37
data/CHANGELOG.md CHANGED
@@ -5,8 +5,24 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ### Changed
11
+
12
+ ## [0.1.10] - 2026-01-23
13
+
14
+ ### Added
15
+
16
+ - **Feature**: `request_id` now appears on all logs during a request, not just the request log
17
+
18
+ ## [0.1.9] - 2026-01-23
19
+
8
20
  ### Changed
9
21
 
22
+ - **Fix**: ActiveJob integration handles Rails main event reporter subscribers
23
+ - **Fix**: Rack error handler avoids deprecated CSRF exception class on Rails main
24
+ - **CI**: Added Rails main daily integration run and updated Rails test matrix (7.1.6, 7.2.3, 8.0.4, 8.1.2)
25
+
10
26
  ## [0.1.8] - 2026-01-22
11
27
 
12
28
  - **Fix**: Lograge custom options now appear in request logs
@@ -32,7 +32,7 @@ module LogStruct
32
32
  SourceIp = new(:source_ip)
33
33
  UserAgent = new(:user_agent)
34
34
  Referer = new(:referer)
35
- RequestId = new(:request_id)
35
+ RequestId = new(:req_id)
36
36
  Host = new(:host)
37
37
  ContentType = new(:content_type)
38
38
  Accept = new(:accept)
@@ -54,12 +54,6 @@ module LogStruct
54
54
  end
55
55
  log_data[:tags] = tags if tags.present?
56
56
 
57
- # Get request_id from ActionDispatch if available
58
- if ::ActionDispatch::Request.respond_to?(:current_request_id) &&
59
- T.unsafe(::ActionDispatch::Request).current_request_id.present?
60
- log_data[:request_id] = T.unsafe(::ActionDispatch::Request).current_request_id
61
- end
62
-
63
57
  # Get job_id from ActiveJob if available
64
58
  if defined?(::ActiveJob::Logging) && ::ActiveJob::Logging.respond_to?(:job_id) &&
65
59
  T.unsafe(::ActiveJob::Logging).job_id.present?
@@ -1,6 +1,7 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
+ require "active_support/log_subscriber"
4
5
  require_relative "../../enums/source"
5
6
  require_relative "../../enums/event"
6
7
  require_relative "../../log/active_job"
@@ -10,7 +11,7 @@ module LogStruct
10
11
  module Integrations
11
12
  module ActiveJob
12
13
  # Structured logging for ActiveJob
13
- class LogSubscriber < ::ActiveJob::LogSubscriber
14
+ class LogSubscriber < ::ActiveSupport::LogSubscriber
14
15
  extend T::Sig
15
16
 
16
17
  sig { params(event: ::ActiveSupport::Notifications::Event).void }
@@ -25,8 +25,13 @@ module LogStruct
25
25
  return nil unless config.integrations.enable_activejob
26
26
 
27
27
  ::ActiveSupport.on_load(:active_job) do
28
- # Detach the default text formatter
29
- ::ActiveJob::LogSubscriber.detach_from :active_job
28
+ if ::ActiveJob::LogSubscriber.respond_to?(:detach_from)
29
+ # Detach the default text formatter
30
+ ::ActiveJob::LogSubscriber.detach_from :active_job
31
+ elsif ::ActiveSupport.respond_to?(:event_reporter)
32
+ reporter = ::ActiveSupport.event_reporter
33
+ reporter.unsubscribe(::ActiveJob::LogSubscriber) if reporter.respond_to?(:unsubscribe)
34
+ end
30
35
 
31
36
  # Attach our structured formatter
32
37
  Integrations::ActiveJob::LogSubscriber.attach_to :active_job
@@ -63,7 +63,6 @@ module LogStruct
63
63
  source_ip: request.ip,
64
64
  user_agent: request.user_agent,
65
65
  referer: request.referer,
66
- request_id: request.request_id,
67
66
  x_forwarded_for: request.x_forwarded_for,
68
67
  allowed_hosts: allowed_hosts_array&.empty? ? nil : allowed_hosts_array,
69
68
  allow_ip_hosts: allow_ip_hosts_value
@@ -101,6 +101,12 @@ module LogStruct
101
101
  headers = event.payload[:headers]
102
102
  return if headers.blank?
103
103
 
104
+ # Rails' ActionDispatch::RequestId middleware stores request_id in headers
105
+ # Only set if not already present in payload (payload takes precedence)
106
+ if options[:request_id].blank? && headers["action_dispatch.request_id"].present?
107
+ options[:request_id] = headers["action_dispatch.request_id"]
108
+ end
109
+
104
110
  options[:user_agent] = headers["HTTP_USER_AGENT"]
105
111
  options[:referer] = headers["HTTP_REFERER"]
106
112
  options[:content_type] = headers["CONTENT_TYPE"]
@@ -144,7 +150,6 @@ module LogStruct
144
150
  view: view,
145
151
  database: db,
146
152
  params: params,
147
- request_id: normalized_data[:request_id]&.to_s,
148
153
  source_ip: normalized_data[:source_ip]&.to_s,
149
154
  user_agent: normalized_data[:user_agent]&.to_s,
150
155
  referer: normalized_data[:referer]&.to_s,
@@ -71,7 +71,6 @@ module LogStruct
71
71
  http_method: env["REQUEST_METHOD"],
72
72
  user_agent: env["HTTP_USER_AGENT"],
73
73
  referer: env["HTTP_REFERER"],
74
- request_id: request.request_id,
75
74
  message: ip_spoof_error.message,
76
75
  client_ip: env["HTTP_CLIENT_IP"],
77
76
  x_forwarded_for: env["HTTP_X_FORWARDED_FOR"],
@@ -81,38 +80,39 @@ module LogStruct
81
80
  ::Rails.logger.warn(security_log)
82
81
 
83
82
  [FORBIDDEN_STATUS, IP_SPOOF_HEADERS.dup, [IP_SPOOF_HTML]]
84
- rescue ::ActionController::InvalidAuthenticityToken => invalid_auth_token_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
- request_id: request.request_id,
93
- message: invalid_auth_token_error.message,
94
- timestamp: Time.now
95
- )
96
- LogStruct.error(security_log)
97
-
98
- # Report to error reporting service and/or re-raise
99
- context = extract_request_context(env, request)
100
- LogStruct.handle_exception(invalid_auth_token_error, source: Source::Security, context: context)
101
-
102
- # If handle_exception raised an exception then Rails will deal with it (e.g. config.exceptions_app)
103
- # If we are only logging or reporting these security errors, then return a default response
104
- [FORBIDDEN_STATUS, CSRF_HEADERS.dup, [CSRF_HTML]]
105
83
  rescue => error
106
- # Extract request context for error reporting
107
- context = extract_request_context(env, request)
108
-
109
- # Create and log a structured exception with request context
110
- exception_log = Log.from_exception(Source::Rails, error, context)
111
- LogStruct.error(exception_log)
112
-
113
- # Re-raise any standard errors to let Rails or error reporter handle it.
114
- # Rails will also log the request details separately
115
- raise 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
116
  end
117
117
  end
118
118
 
@@ -135,7 +135,6 @@ module LogStruct
135
135
  def extract_request_context(env, request = nil)
136
136
  request ||= ::ActionDispatch::Request.new(env)
137
137
  {
138
- request_id: request.request_id,
139
138
  path: request.path,
140
139
  method: request.method,
141
140
  user_agent: request.user_agent,
@@ -146,6 +145,13 @@ module LogStruct
146
145
  {error_extracting_context: error.message}
147
146
  end
148
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
+
149
155
  sig { params(configured_proxies: T.untyped).returns(T.untyped) }
150
156
  def normalized_trusted_proxies(configured_proxies)
151
157
  if configured_proxies.nil? || (configured_proxies.respond_to?(:empty?) && configured_proxies.empty?)
@@ -0,0 +1,28 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module LogStruct
5
+ module Integrations
6
+ module RequestContext
7
+ # Middleware that captures request_id and stores it in SemanticLogger's
8
+ # 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) }
18
+ def call(env)
19
+ request = ::ActionDispatch::Request.new(env)
20
+ ::SemanticLogger.push_named_tags(request_id: request.request_id)
21
+ @app.call(env)
22
+ ensure
23
+ ::SemanticLogger.pop_named_tags
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "request_context/middleware"
5
+
6
+ module LogStruct
7
+ module Integrations
8
+ # Request context integration that captures request_id for all logs
9
+ module RequestContext
10
+ extend T::Sig
11
+ extend IntegrationInterface
12
+
13
+ sig { override.params(config: LogStruct::Configuration).returns(T.nilable(T::Boolean)) }
14
+ def self.setup(config)
15
+ return nil unless config.enabled
16
+
17
+ # Insert after RequestId middleware so request_id is available
18
+ ::Rails.application.middleware.insert_after(
19
+ ::ActionDispatch::RequestId,
20
+ Integrations::RequestContext::Middleware
21
+ )
22
+
23
+ true
24
+ end
25
+ end
26
+ end
27
+ end
@@ -2,6 +2,7 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require_relative "integrations/integration_interface"
5
+ require_relative "integrations/request_context"
5
6
  require_relative "integrations/active_job"
6
7
  require_relative "integrations/active_record"
7
8
  require_relative "integrations/rack_error_handler"
@@ -77,6 +78,7 @@ module LogStruct
77
78
 
78
79
  sig { params(config: LogStruct::Configuration).void }
79
80
  def self.setup_middleware_integrations(config)
81
+ Integrations::RequestContext.setup(config)
80
82
  Integrations::HostAuthorization.setup(config) if config.integrations.enable_host_authorization
81
83
  Integrations::RackErrorHandler.setup(config) if config.integrations.enable_rack_error_handler
82
84
  end
@@ -33,7 +33,6 @@ module LogStruct
33
33
  const :source_ip, T.nilable(String), default: nil
34
34
  const :user_agent, T.nilable(String), default: nil
35
35
  const :referer, T.nilable(String), default: nil
36
- const :request_id, T.nilable(String), default: nil
37
36
 
38
37
  # Event-specific fields
39
38
  const :format, T.nilable(Symbol), default: nil
@@ -69,7 +68,6 @@ module LogStruct
69
68
  h[LogField::SourceIp] = source_ip unless source_ip.nil?
70
69
  h[LogField::UserAgent] = user_agent unless user_agent.nil?
71
70
  h[LogField::Referer] = referer unless referer.nil?
72
- h[LogField::RequestId] = request_id unless request_id.nil?
73
71
  h[LogField::Format] = format unless format.nil?
74
72
  h[LogField::Controller] = controller unless controller.nil?
75
73
  h[LogField::Action] = action unless action.nil?
@@ -34,7 +34,6 @@ module LogStruct
34
34
  const :source_ip, T.nilable(String), default: nil
35
35
  const :user_agent, T.nilable(String), default: nil
36
36
  const :referer, T.nilable(String), default: nil
37
- const :request_id, T.nilable(String), default: nil
38
37
 
39
38
  # Event-specific fields
40
39
  const :message, T.nilable(String), default: nil
@@ -65,7 +64,6 @@ module LogStruct
65
64
  h[LogField::SourceIp] = source_ip unless source_ip.nil?
66
65
  h[LogField::UserAgent] = user_agent unless user_agent.nil?
67
66
  h[LogField::Referer] = referer unless referer.nil?
68
- h[LogField::RequestId] = request_id unless request_id.nil?
69
67
  h[LogField::Message] = message unless message.nil?
70
68
  h[LogField::BlockedHost] = blocked_host unless blocked_host.nil?
71
69
  h[LogField::BlockedHosts] = blocked_hosts unless blocked_hosts.nil?
@@ -34,7 +34,6 @@ module LogStruct
34
34
  const :source_ip, T.nilable(String), default: nil
35
35
  const :user_agent, T.nilable(String), default: nil
36
36
  const :referer, T.nilable(String), default: nil
37
- const :request_id, T.nilable(String), default: nil
38
37
 
39
38
  # Event-specific fields
40
39
  const :message, T.nilable(String), default: nil
@@ -60,7 +59,6 @@ module LogStruct
60
59
  h[LogField::SourceIp] = source_ip unless source_ip.nil?
61
60
  h[LogField::UserAgent] = user_agent unless user_agent.nil?
62
61
  h[LogField::Referer] = referer unless referer.nil?
63
- h[LogField::RequestId] = request_id unless request_id.nil?
64
62
  h[LogField::Message] = message unless message.nil?
65
63
  h
66
64
  end
@@ -34,7 +34,6 @@ module LogStruct
34
34
  const :source_ip, T.nilable(String), default: nil
35
35
  const :user_agent, T.nilable(String), default: nil
36
36
  const :referer, T.nilable(String), default: nil
37
- const :request_id, T.nilable(String), default: nil
38
37
 
39
38
  # Event-specific fields
40
39
  const :message, T.nilable(String), default: nil
@@ -62,7 +61,6 @@ module LogStruct
62
61
  h[LogField::SourceIp] = source_ip unless source_ip.nil?
63
62
  h[LogField::UserAgent] = user_agent unless user_agent.nil?
64
63
  h[LogField::Referer] = referer unless referer.nil?
65
- h[LogField::RequestId] = request_id unless request_id.nil?
66
64
  h[LogField::Message] = message unless message.nil?
67
65
  h[LogField::ClientIp] = client_ip unless client_ip.nil?
68
66
  h[LogField::XForwardedFor] = x_forwarded_for unless x_forwarded_for.nil?
@@ -20,7 +20,6 @@ module LogStruct
20
20
  const :source_ip, T.nilable(String), default: nil
21
21
  const :user_agent, T.nilable(String), default: nil
22
22
  const :referer, T.nilable(String), default: nil
23
- const :request_id, T.nilable(String), default: nil
24
23
 
25
24
  Kwargs = T.type_alias do
26
25
  {
@@ -28,8 +27,7 @@ module LogStruct
28
27
  http_method: T.nilable(String),
29
28
  source_ip: T.nilable(String),
30
29
  user_agent: T.nilable(String),
31
- referer: T.nilable(String),
32
- request_id: T.nilable(String)
30
+ referer: T.nilable(String)
33
31
  }
34
32
  end
35
33
 
@@ -40,8 +38,7 @@ module LogStruct
40
38
  http_method: http_method,
41
39
  source_ip: source_ip,
42
40
  user_agent: user_agent,
43
- referer: referer,
44
- request_id: request_id
41
+ referer: referer
45
42
  }
46
43
  end
47
44
  end
@@ -79,6 +79,15 @@ module LogStruct
79
79
  )
80
80
  @logstruct_formatter.call(log.level, log.time, log.name, plain_log)
81
81
  end
82
+
83
+ # Add request_id from named_tags if present
84
+ request_id = log.named_tags[:request_id]
85
+ if request_id
86
+ data = JSON.parse(json)
87
+ data["req_id"] = request_id
88
+ json = data.to_json
89
+ end
90
+
82
91
  # SemanticLogger appenders typically add their own newline. Avoid double newlines by stripping ours.
83
92
  json.end_with?("\n") ? json.chomp : json
84
93
  end
@@ -148,7 +148,9 @@ module LogStruct
148
148
  end
149
149
 
150
150
  # Proxy object to provide ActiveJob-compatible formatter interface
151
- class FormatterProxy
151
+ # Also implements the standard Logger formatter interface (call method)
152
+ # for compatibility with Ruby's Logger (especially logger gem 1.7.0+)
153
+ class FormatterProxy < ::Logger::Formatter
152
154
  extend T::Sig
153
155
 
154
156
  sig { returns(T::Array[T.any(String, Symbol)]) }
@@ -20,7 +20,6 @@ module LogStruct
20
20
  hash[LogField::SourceIp.serialize] = source_ip if source_ip
21
21
  hash[LogField::UserAgent.serialize] = user_agent if user_agent
22
22
  hash[LogField::Referer.serialize] = referer if referer
23
- hash[LogField::RequestId.serialize] = request_id if request_id
24
23
  end
25
24
  end
26
25
  end
@@ -29,10 +29,6 @@ module LogStruct
29
29
  sig { abstract.returns(T.nilable(String)) }
30
30
  def referer
31
31
  end
32
-
33
- sig { abstract.returns(T.nilable(String)) }
34
- def request_id
35
- end
36
32
  end
37
33
  end
38
34
  end
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module LogStruct
5
- VERSION = "0.1.8"
5
+ VERSION = "0.1.10"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstruct
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.8
4
+ version: 0.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - DocSpring
@@ -281,6 +281,8 @@ files:
281
281
  - lib/log_struct/integrations/rack.rb
282
282
  - lib/log_struct/integrations/rack_error_handler.rb
283
283
  - lib/log_struct/integrations/rack_error_handler/middleware.rb
284
+ - lib/log_struct/integrations/request_context.rb
285
+ - lib/log_struct/integrations/request_context/middleware.rb
284
286
  - lib/log_struct/integrations/shrine.rb
285
287
  - lib/log_struct/integrations/sidekiq.rb
286
288
  - lib/log_struct/integrations/sidekiq/logger.rb