logstruct 0.1.2 → 0.1.4
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 +4 -4
- data/CHANGELOG.md +16 -1
- data/README.md +4 -6
- data/lib/log_struct/concerns/configuration.rb +2 -2
- data/lib/log_struct/config_struct/integrations.rb +5 -0
- data/lib/log_struct/enums/log_field.rb +12 -1
- data/lib/log_struct/integrations/action_mailer/error_handling.rb +121 -27
- data/lib/log_struct/integrations/action_mailer/event_logging.rb +30 -14
- data/lib/log_struct/integrations/action_mailer/metadata_collection.rb +18 -24
- data/lib/log_struct/integrations/action_mailer.rb +13 -6
- data/lib/log_struct/integrations/active_job/log_subscriber.rb +2 -2
- data/lib/log_struct/integrations/active_storage.rb +8 -8
- data/lib/log_struct/integrations/ahoy.rb +2 -3
- data/lib/log_struct/integrations/carrierwave.rb +8 -10
- data/lib/log_struct/integrations/good_job/log_subscriber.rb +5 -5
- data/lib/log_struct/integrations/good_job/logger.rb +2 -6
- data/lib/log_struct/integrations/good_job.rb +1 -1
- data/lib/log_struct/integrations/host_authorization.rb +27 -36
- data/lib/log_struct/integrations/lograge.rb +1 -1
- data/lib/log_struct/integrations/rack_error_handler/middleware.rb +54 -16
- data/lib/log_struct/integrations/rack_error_handler.rb +3 -3
- data/lib/log_struct/integrations/shrine.rb +21 -24
- data/lib/log_struct/integrations/sidekiq/logger.rb +8 -1
- data/lib/log_struct/integrations.rb +25 -5
- data/lib/log_struct/log/action_mailer/delivered.rb +14 -49
- data/lib/log_struct/log/action_mailer/delivery.rb +14 -49
- data/lib/log_struct/log/action_mailer/error.rb +72 -0
- data/lib/log_struct/log/action_mailer.rb +15 -2
- data/lib/log_struct/log/active_job/enqueue.rb +9 -73
- data/lib/log_struct/log/active_job/finish.rb +9 -76
- data/lib/log_struct/log/active_job/schedule.rb +9 -73
- data/lib/log_struct/log/active_job/start.rb +9 -76
- data/lib/log_struct/log/active_job.rb +2 -2
- data/lib/log_struct/log/active_model_serializers.rb +5 -45
- data/lib/log_struct/log/active_storage/delete.rb +8 -46
- data/lib/log_struct/log/active_storage/download.rb +9 -55
- data/lib/log_struct/log/active_storage/exist.rb +9 -49
- data/lib/log_struct/log/active_storage/metadata.rb +9 -49
- data/lib/log_struct/log/active_storage/stream.rb +9 -49
- data/lib/log_struct/log/active_storage/upload.rb +9 -64
- data/lib/log_struct/log/active_storage/url.rb +9 -49
- data/lib/log_struct/log/active_storage.rb +2 -2
- data/lib/log_struct/log/ahoy.rb +5 -43
- data/lib/log_struct/log/carrierwave/delete.rb +15 -69
- data/lib/log_struct/log/carrierwave/download.rb +15 -77
- data/lib/log_struct/log/carrierwave/upload.rb +15 -83
- data/lib/log_struct/log/carrierwave.rb +13 -4
- data/lib/log_struct/log/dotenv/load.rb +5 -33
- data/lib/log_struct/log/dotenv/restore.rb +5 -33
- data/lib/log_struct/log/dotenv/save.rb +5 -33
- data/lib/log_struct/log/dotenv/update.rb +5 -33
- data/lib/log_struct/log/error.rb +7 -40
- data/lib/log_struct/log/good_job/enqueue.rb +9 -72
- data/lib/log_struct/log/good_job/error.rb +9 -89
- data/lib/log_struct/log/good_job/finish.rb +9 -78
- data/lib/log_struct/log/good_job/log.rb +11 -75
- data/lib/log_struct/log/good_job/schedule.rb +7 -78
- data/lib/log_struct/log/good_job/start.rb +7 -78
- data/lib/log_struct/log/good_job.rb +2 -2
- data/lib/log_struct/log/plain.rb +5 -32
- data/lib/log_struct/log/puma/shutdown.rb +5 -32
- data/lib/log_struct/log/puma/start.rb +5 -56
- data/lib/log_struct/log/request.rb +7 -90
- data/lib/log_struct/log/security/blocked_host.rb +12 -73
- data/lib/log_struct/log/security/csrf_violation.rb +6 -67
- data/lib/log_struct/log/security/ip_spoof.rb +6 -73
- data/lib/log_struct/log/shrine/delete.rb +6 -41
- data/lib/log_struct/log/shrine/download.rb +6 -44
- data/lib/log_struct/log/shrine/exist.rb +6 -44
- data/lib/log_struct/log/shrine/metadata.rb +8 -46
- data/lib/log_struct/log/shrine/upload.rb +6 -53
- data/lib/log_struct/log/sidekiq.rb +5 -42
- data/lib/log_struct/log/sql.rb +5 -65
- data/lib/log_struct/log.rb +2 -2
- data/lib/log_struct/monkey_patches/active_support/tagged_logging/formatter.rb +12 -1
- data/lib/log_struct/railtie.rb +11 -24
- data/lib/log_struct/semantic_logger/concerns/log_methods.rb +100 -0
- data/lib/log_struct/semantic_logger/logger.rb +46 -15
- data/lib/log_struct/semantic_logger/setup.rb +11 -7
- data/lib/log_struct/shared/{shared/add_request_fields.rb → add_request_fields.rb} +2 -2
- data/lib/log_struct/shared/{shared/merge_additional_data_fields.rb → merge_additional_data_fields.rb} +1 -1
- data/lib/log_struct/shared/{shared/serialize_common.rb → serialize_common.rb} +9 -3
- data/lib/log_struct/{log/shared → shared}/serialize_common_public.rb +2 -2
- data/lib/log_struct/version.rb +1 -1
- data/lib/log_struct.rb +4 -1
- data/logstruct.gemspec +1 -1
- metadata +9 -11
- data/lib/log_struct/integrations/action_mailer/callbacks.rb +0 -100
- data/lib/log_struct/log/shared/add_request_fields.rb +0 -4
- data/lib/log_struct/log/shared/merge_additional_data_fields.rb +0 -4
- data/lib/log_struct/log/shared/serialize_common.rb +0 -4
|
@@ -53,7 +53,7 @@ module LogStruct
|
|
|
53
53
|
|
|
54
54
|
# Log the store operation with structured data
|
|
55
55
|
log_data = Log::CarrierWave::Upload.new(
|
|
56
|
-
storage: storage.class.name,
|
|
56
|
+
storage: storage.class.name.split("::").last.downcase.to_sym,
|
|
57
57
|
file_id: identifier,
|
|
58
58
|
filename: file.filename,
|
|
59
59
|
mime_type: file.content_type,
|
|
@@ -62,11 +62,9 @@ module LogStruct
|
|
|
62
62
|
uploader: self.class.name,
|
|
63
63
|
model: model.class.name,
|
|
64
64
|
mount_point: mounted_as.to_s,
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
extension: file.extension
|
|
69
|
-
}
|
|
65
|
+
version: version_name.to_s,
|
|
66
|
+
store_path: store_path,
|
|
67
|
+
extension: file.extension
|
|
70
68
|
)
|
|
71
69
|
|
|
72
70
|
::Rails.logger.info(log_data)
|
|
@@ -85,7 +83,7 @@ module LogStruct
|
|
|
85
83
|
|
|
86
84
|
# Log the retrieve operation with structured data
|
|
87
85
|
log_data = Log::CarrierWave::Download.new(
|
|
88
|
-
storage: storage.class.name,
|
|
86
|
+
storage: storage.class.name.split("::").last.downcase.to_sym,
|
|
89
87
|
file_id: identifier,
|
|
90
88
|
filename: file&.filename,
|
|
91
89
|
mime_type: file&.content_type,
|
|
@@ -94,9 +92,9 @@ module LogStruct
|
|
|
94
92
|
uploader: self.class.name,
|
|
95
93
|
model: model.class.name,
|
|
96
94
|
mount_point: mounted_as.to_s,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
95
|
+
version: version_name.to_s,
|
|
96
|
+
store_path: store_path,
|
|
97
|
+
extension: file&.extension
|
|
100
98
|
)
|
|
101
99
|
|
|
102
100
|
::Rails.logger.info(log_data)
|
|
@@ -49,7 +49,7 @@ module LogStruct
|
|
|
49
49
|
**base_fields.to_kwargs,
|
|
50
50
|
scheduled_at: (job&.scheduled_at ? Time.at(job.scheduled_at.to_i) : nil),
|
|
51
51
|
duration_ms: event.duration.to_f,
|
|
52
|
-
|
|
52
|
+
enqueue_caller: job&.enqueue_caller_location,
|
|
53
53
|
timestamp: ts
|
|
54
54
|
))
|
|
55
55
|
end
|
|
@@ -91,7 +91,7 @@ module LogStruct
|
|
|
91
91
|
finished_at: end_ts,
|
|
92
92
|
process_id: ::Process.pid,
|
|
93
93
|
thread_id: Thread.current.object_id.to_s(36),
|
|
94
|
-
|
|
94
|
+
result: payload[:result]&.to_s,
|
|
95
95
|
timestamp: start_ts
|
|
96
96
|
))
|
|
97
97
|
end
|
|
@@ -109,9 +109,9 @@ module LogStruct
|
|
|
109
109
|
logger.error(Log::GoodJob::Error.new(
|
|
110
110
|
**base_fields.to_kwargs,
|
|
111
111
|
exception_executions: execution&.exception_executions,
|
|
112
|
-
|
|
112
|
+
error_class: exception&.class&.name,
|
|
113
113
|
error_message: exception&.message,
|
|
114
|
-
backtrace: exception&.backtrace
|
|
114
|
+
backtrace: exception&.backtrace,
|
|
115
115
|
duration_ms: event.duration.to_f,
|
|
116
116
|
process_id: ::Process.pid,
|
|
117
117
|
thread_id: Thread.current.object_id.to_s(36),
|
|
@@ -146,7 +146,7 @@ module LogStruct
|
|
|
146
146
|
Log::GoodJob::BaseFields.new(
|
|
147
147
|
job_id: job&.job_id,
|
|
148
148
|
job_class: job&.job_class,
|
|
149
|
-
queue_name: job&.queue_name,
|
|
149
|
+
queue_name: job&.queue_name&.to_sym,
|
|
150
150
|
arguments: job&.arguments,
|
|
151
151
|
executions: execution&.executions
|
|
152
152
|
)
|
|
@@ -46,11 +46,6 @@ module LogStruct
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
# Emit a GoodJob::Log event with context and extra fields as additional_data
|
|
50
|
-
extras = {}
|
|
51
|
-
extras[:scheduled_at] = job_context[:scheduled_at] if job_context.key?(:scheduled_at)
|
|
52
|
-
extras[:priority] = job_context[:priority] if job_context.key?(:priority)
|
|
53
|
-
|
|
54
49
|
log_struct = Log::GoodJob::Log.new(
|
|
55
50
|
message: message || (block ? block.call : ""),
|
|
56
51
|
process_id: ::Process.pid,
|
|
@@ -59,7 +54,8 @@ module LogStruct
|
|
|
59
54
|
job_class: job_context[:job_class],
|
|
60
55
|
queue_name: job_context[:queue_name],
|
|
61
56
|
executions: job_context[:executions],
|
|
62
|
-
|
|
57
|
+
scheduled_at: job_context[:scheduled_at],
|
|
58
|
+
priority: job_context[:priority]
|
|
63
59
|
)
|
|
64
60
|
|
|
65
61
|
super(log_struct, payload, &nil)
|
|
@@ -82,7 +82,7 @@ module LogStruct
|
|
|
82
82
|
if goodjob_module.respond_to?(:on_thread_error=)
|
|
83
83
|
goodjob_module.on_thread_error = ->(exception) do
|
|
84
84
|
log_entry = LogStruct::Log::GoodJob::Error.new(
|
|
85
|
-
|
|
85
|
+
error_class: exception.class.name,
|
|
86
86
|
error_message: exception.message,
|
|
87
87
|
backtrace: exception.backtrace,
|
|
88
88
|
process_id: ::Process.pid,
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
require "action_dispatch/middleware/host_authorization"
|
|
5
5
|
require_relative "../enums/event"
|
|
6
|
+
require_relative "../log/security/blocked_host"
|
|
6
7
|
|
|
7
8
|
module LogStruct
|
|
8
9
|
module Integrations
|
|
@@ -34,23 +35,6 @@ module LogStruct
|
|
|
34
35
|
return nil unless config.enabled
|
|
35
36
|
return nil unless config.integrations.enable_host_authorization
|
|
36
37
|
|
|
37
|
-
# In test environment, ensure HostAuthorization does not block requests
|
|
38
|
-
# from the default integration test hosts. Allow all hosts explicitly.
|
|
39
|
-
if ::Rails.env.test? && ::Rails.application.config.respond_to?(:hosts)
|
|
40
|
-
begin
|
|
41
|
-
::Rails.application.config.hosts << /.*\z/
|
|
42
|
-
rescue
|
|
43
|
-
# best-effort; ignore if hosts not configurable
|
|
44
|
-
end
|
|
45
|
-
# Additionally, exclude all requests from HostAuthorization in test
|
|
46
|
-
begin
|
|
47
|
-
::Rails.application.config.host_authorization ||= {}
|
|
48
|
-
::Rails.application.config.host_authorization[:exclude] = ->(_request) { true }
|
|
49
|
-
rescue
|
|
50
|
-
# best-effort
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
38
|
# Define the response app as a separate variable to fix block alignment
|
|
55
39
|
response_app = lambda do |env|
|
|
56
40
|
request = ::ActionDispatch::Request.new(env)
|
|
@@ -58,32 +42,39 @@ module LogStruct
|
|
|
58
42
|
# This can be helpful later when reviewing logs.
|
|
59
43
|
blocked_hosts = env["action_dispatch.blocked_hosts"]
|
|
60
44
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
45
|
+
# Build allowed_hosts array
|
|
46
|
+
allowed_hosts_array = T.let(nil, T.nilable(T::Array[String]))
|
|
47
|
+
if blocked_hosts.respond_to?(:allowed_hosts)
|
|
48
|
+
allowed_hosts_array = blocked_hosts.allowed_hosts
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Get allow_ip_hosts value
|
|
52
|
+
allow_ip_hosts_value = T.let(nil, T.nilable(T::Boolean))
|
|
53
|
+
if blocked_hosts.respond_to?(:allow_ip_hosts)
|
|
54
|
+
allow_ip_hosts_value = blocked_hosts.allow_ip_hosts
|
|
55
|
+
end
|
|
65
56
|
|
|
66
|
-
# Create
|
|
67
|
-
|
|
57
|
+
# Create structured log entry for blocked host
|
|
58
|
+
log_entry = LogStruct::Log::Security::BlockedHost.new(
|
|
59
|
+
message: "Blocked host detected: #{request.host}",
|
|
68
60
|
blocked_host: request.host,
|
|
69
|
-
client_ip: request.ip,
|
|
70
|
-
x_forwarded_for: request.x_forwarded_for,
|
|
71
|
-
http_method: request.method,
|
|
72
61
|
path: request.path,
|
|
62
|
+
http_method: request.method,
|
|
63
|
+
source_ip: request.ip,
|
|
73
64
|
user_agent: request.user_agent,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
LogStruct.handle_exception(
|
|
80
|
-
blocked_host_error,
|
|
81
|
-
source: Source::Security,
|
|
82
|
-
context: context
|
|
65
|
+
referer: request.referer,
|
|
66
|
+
request_id: request.request_id,
|
|
67
|
+
x_forwarded_for: request.x_forwarded_for,
|
|
68
|
+
allowed_hosts: allowed_hosts_array&.empty? ? nil : allowed_hosts_array,
|
|
69
|
+
allow_ip_hosts: allow_ip_hosts_value
|
|
83
70
|
)
|
|
84
71
|
|
|
72
|
+
# Log the blocked host
|
|
73
|
+
LogStruct.warn(log_entry)
|
|
74
|
+
|
|
85
75
|
# Use pre-defined headers and response if we are only logging or reporting
|
|
86
|
-
|
|
76
|
+
# Dup the headers so they can be modified by downstream middleware
|
|
77
|
+
[FORBIDDEN_STATUS, RESPONSE_HEADERS.dup, [RESPONSE_HTML]]
|
|
87
78
|
end
|
|
88
79
|
|
|
89
80
|
# Merge our response_app into existing host_authorization config to preserve excludes
|
|
@@ -55,8 +55,14 @@ module LogStruct
|
|
|
55
55
|
def call(env)
|
|
56
56
|
return @app.call(env) unless LogStruct.enabled?
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
request = ::ActionDispatch::Request.new(env)
|
|
59
|
+
|
|
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
|
+
|
|
60
66
|
@app.call(env)
|
|
61
67
|
rescue ::ActionDispatch::RemoteIp::IpSpoofAttackError => ip_spoof_error
|
|
62
68
|
# Create a security log for IP spoofing
|
|
@@ -65,7 +71,7 @@ module LogStruct
|
|
|
65
71
|
http_method: env["REQUEST_METHOD"],
|
|
66
72
|
user_agent: env["HTTP_USER_AGENT"],
|
|
67
73
|
referer: env["HTTP_REFERER"],
|
|
68
|
-
request_id:
|
|
74
|
+
request_id: request.request_id,
|
|
69
75
|
message: ip_spoof_error.message,
|
|
70
76
|
client_ip: env["HTTP_CLIENT_IP"],
|
|
71
77
|
x_forwarded_for: env["HTTP_X_FORWARDED_FOR"],
|
|
@@ -74,16 +80,9 @@ module LogStruct
|
|
|
74
80
|
|
|
75
81
|
::Rails.logger.warn(security_log)
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
context = extract_request_context(env)
|
|
79
|
-
LogStruct.handle_exception(ip_spoof_error, source: Source::Security, context: context)
|
|
80
|
-
|
|
81
|
-
# If handle_exception raised an exception then Rails will deal with it (e.g. config.exceptions_app)
|
|
82
|
-
# If we are only logging or reporting these security errors, then return a default response
|
|
83
|
-
[FORBIDDEN_STATUS, IP_SPOOF_HEADERS, [IP_SPOOF_HTML]]
|
|
83
|
+
[FORBIDDEN_STATUS, IP_SPOOF_HEADERS.dup, [IP_SPOOF_HTML]]
|
|
84
84
|
rescue ::ActionController::InvalidAuthenticityToken => invalid_auth_token_error
|
|
85
85
|
# Create a security log for CSRF error
|
|
86
|
-
request = ::ActionDispatch::Request.new(env)
|
|
87
86
|
security_log = Log::Security::CSRFViolation.new(
|
|
88
87
|
path: request.path,
|
|
89
88
|
http_method: request.method,
|
|
@@ -97,15 +96,15 @@ module LogStruct
|
|
|
97
96
|
LogStruct.error(security_log)
|
|
98
97
|
|
|
99
98
|
# Report to error reporting service and/or re-raise
|
|
100
|
-
context = extract_request_context(env)
|
|
99
|
+
context = extract_request_context(env, request)
|
|
101
100
|
LogStruct.handle_exception(invalid_auth_token_error, source: Source::Security, context: context)
|
|
102
101
|
|
|
103
102
|
# If handle_exception raised an exception then Rails will deal with it (e.g. config.exceptions_app)
|
|
104
103
|
# If we are only logging or reporting these security errors, then return a default response
|
|
105
|
-
[FORBIDDEN_STATUS, CSRF_HEADERS, [CSRF_HTML]]
|
|
104
|
+
[FORBIDDEN_STATUS, CSRF_HEADERS.dup, [CSRF_HTML]]
|
|
106
105
|
rescue => error
|
|
107
106
|
# Extract request context for error reporting
|
|
108
|
-
context = extract_request_context(env)
|
|
107
|
+
context = extract_request_context(env, request)
|
|
109
108
|
|
|
110
109
|
# Create and log a structured exception with request context
|
|
111
110
|
exception_log = Log.from_exception(Source::Rails, error, context)
|
|
@@ -119,9 +118,22 @@ module LogStruct
|
|
|
119
118
|
|
|
120
119
|
private
|
|
121
120
|
|
|
122
|
-
sig { params(
|
|
123
|
-
def
|
|
124
|
-
|
|
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)
|
|
125
137
|
{
|
|
126
138
|
request_id: request.request_id,
|
|
127
139
|
path: request.path,
|
|
@@ -133,6 +145,32 @@ module LogStruct
|
|
|
133
145
|
# If we can't extract request context, return minimal info
|
|
134
146
|
{error_extracting_context: error.message}
|
|
135
147
|
end
|
|
148
|
+
|
|
149
|
+
sig { params(configured_proxies: T.untyped).returns(T.untyped) }
|
|
150
|
+
def normalized_trusted_proxies(configured_proxies)
|
|
151
|
+
if configured_proxies.nil? || (configured_proxies.respond_to?(:empty?) && configured_proxies.empty?)
|
|
152
|
+
return ::ActionDispatch::RemoteIp::TRUSTED_PROXIES
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
return configured_proxies if configured_proxies.respond_to?(:any?)
|
|
156
|
+
|
|
157
|
+
raise(
|
|
158
|
+
ArgumentError,
|
|
159
|
+
<<~EOM
|
|
160
|
+
Setting config.action_dispatch.trusted_proxies to a single value isn't
|
|
161
|
+
supported. Please set this to an enumerable instead. For
|
|
162
|
+
example, instead of:
|
|
163
|
+
|
|
164
|
+
config.action_dispatch.trusted_proxies = IPAddr.new("10.0.0.0/8")
|
|
165
|
+
|
|
166
|
+
Wrap the value in an Array:
|
|
167
|
+
|
|
168
|
+
config.action_dispatch.trusted_proxies = [IPAddr.new("10.0.0.0/8")]
|
|
169
|
+
|
|
170
|
+
Note that passing an enumerable will *replace* the default set of trusted proxies.
|
|
171
|
+
EOM
|
|
172
|
+
)
|
|
173
|
+
end
|
|
136
174
|
end
|
|
137
175
|
end
|
|
138
176
|
end
|
|
@@ -19,9 +19,9 @@ module LogStruct
|
|
|
19
19
|
return nil unless config.integrations.enable_rack_error_handler
|
|
20
20
|
|
|
21
21
|
# Add structured logging middleware for security violations and errors
|
|
22
|
-
# Need to insert
|
|
23
|
-
::Rails.application.middleware.
|
|
24
|
-
::ActionDispatch::
|
|
22
|
+
# Need to insert before RemoteIp to catch IP spoofing errors it raises
|
|
23
|
+
::Rails.application.middleware.insert_before(
|
|
24
|
+
::ActionDispatch::RemoteIp,
|
|
25
25
|
Integrations::RackErrorHandler::Middleware
|
|
26
26
|
)
|
|
27
27
|
|
|
@@ -37,50 +37,47 @@ module LogStruct
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# Create structured log data
|
|
40
|
+
# Ensure storage is always a symbol
|
|
41
|
+
storage_sym = payload[:storage].to_sym
|
|
42
|
+
|
|
40
43
|
log_data = case event_type
|
|
41
44
|
when Event::Upload
|
|
42
45
|
Log::Shrine::Upload.new(
|
|
43
|
-
storage:
|
|
46
|
+
storage: storage_sym,
|
|
44
47
|
location: payload[:location],
|
|
45
|
-
uploader: payload[:uploader],
|
|
48
|
+
uploader: payload[:uploader]&.to_s,
|
|
46
49
|
upload_options: payload[:upload_options],
|
|
47
50
|
options: payload[:options],
|
|
48
|
-
duration_ms: event.duration
|
|
49
|
-
additional_data: payload.except(:storage, :location, :uploader, :upload_options, :options)
|
|
51
|
+
duration_ms: event.duration.to_f
|
|
50
52
|
)
|
|
51
53
|
when Event::Download
|
|
52
54
|
Log::Shrine::Download.new(
|
|
53
|
-
storage:
|
|
55
|
+
storage: storage_sym,
|
|
54
56
|
location: payload[:location],
|
|
55
|
-
download_options: payload[:download_options]
|
|
56
|
-
additional_data: payload.except(:storage, :location, :download_options)
|
|
57
|
+
download_options: payload[:download_options]
|
|
57
58
|
)
|
|
58
59
|
when Event::Delete
|
|
59
60
|
Log::Shrine::Delete.new(
|
|
60
|
-
storage:
|
|
61
|
-
location: payload[:location]
|
|
62
|
-
additional_data: payload.except(:storage, :location)
|
|
61
|
+
storage: storage_sym,
|
|
62
|
+
location: payload[:location]
|
|
63
63
|
)
|
|
64
64
|
when Event::Metadata
|
|
65
|
-
|
|
66
|
-
storage:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
65
|
+
metadata_params = {
|
|
66
|
+
storage: storage_sym,
|
|
67
|
+
metadata: payload[:metadata]
|
|
68
|
+
}
|
|
69
|
+
metadata_params[:location] = payload[:location] if payload[:location]
|
|
70
|
+
Log::Shrine::Metadata.new(**metadata_params)
|
|
71
71
|
when Event::Exist
|
|
72
72
|
Log::Shrine::Exist.new(
|
|
73
|
-
storage:
|
|
73
|
+
storage: storage_sym,
|
|
74
74
|
location: payload[:location],
|
|
75
|
-
exist: payload[:exist]
|
|
76
|
-
additional_data: payload.except(:storage, :location, :exist)
|
|
75
|
+
exist: payload[:exist]
|
|
77
76
|
)
|
|
78
77
|
else
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
metadata: payload[:metadata]
|
|
83
|
-
)
|
|
78
|
+
unknown_params = {storage: storage_sym, metadata: payload[:metadata]}
|
|
79
|
+
unknown_params[:location] = payload[:location] if payload[:location]
|
|
80
|
+
Log::Shrine::Metadata.new(**unknown_params)
|
|
84
81
|
end
|
|
85
82
|
|
|
86
83
|
# Pass the structured hash to the logger
|
|
@@ -23,11 +23,18 @@ module LogStruct
|
|
|
23
23
|
# Override log methods to create Sidekiq log structs
|
|
24
24
|
%i[debug info warn error fatal].each do |level|
|
|
25
25
|
define_method(level) do |message = nil, payload = nil, &block|
|
|
26
|
+
# Ensure message is a String (Sidekiq may pass Hash for config logs)
|
|
27
|
+
msg = if message.nil?
|
|
28
|
+
block ? block.call.to_s : ""
|
|
29
|
+
else
|
|
30
|
+
message.to_s
|
|
31
|
+
end
|
|
32
|
+
|
|
26
33
|
# Create a Sidekiq log struct with the message
|
|
27
34
|
log_struct = Log::Sidekiq.new(
|
|
28
35
|
level: LogStruct::Level.from_severity(level.to_s.upcase),
|
|
29
36
|
event: Event::Log,
|
|
30
|
-
message:
|
|
37
|
+
message: msg,
|
|
31
38
|
process_id: ::Process.pid,
|
|
32
39
|
thread_id: Sidekiq.tid,
|
|
33
40
|
context: ::Sidekiq::Context.current || {}
|
|
@@ -38,11 +38,25 @@ module LogStruct
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
sig { void }
|
|
42
|
-
def self.setup_integrations
|
|
41
|
+
sig { params(stage: Symbol).void }
|
|
42
|
+
def self.setup_integrations(stage: :all)
|
|
43
43
|
config = LogStruct.config
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
case stage
|
|
46
|
+
when :non_middleware
|
|
47
|
+
setup_non_middleware_integrations(config)
|
|
48
|
+
when :middleware
|
|
49
|
+
setup_middleware_integrations(config)
|
|
50
|
+
when :all
|
|
51
|
+
setup_non_middleware_integrations(config)
|
|
52
|
+
setup_middleware_integrations(config)
|
|
53
|
+
else
|
|
54
|
+
raise ArgumentError, "Unknown integration stage: #{stage}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
sig { params(config: LogStruct::Configuration).void }
|
|
59
|
+
def self.setup_non_middleware_integrations(config)
|
|
46
60
|
Integrations::Lograge.setup(config) if config.integrations.enable_lograge
|
|
47
61
|
Integrations::ActionMailer.setup(config) if config.integrations.enable_actionmailer
|
|
48
62
|
Integrations::ActiveJob.setup(config) if config.integrations.enable_activejob
|
|
@@ -51,8 +65,6 @@ module LogStruct
|
|
|
51
65
|
Integrations::GoodJob.setup(config) if config.integrations.enable_goodjob
|
|
52
66
|
Integrations::Ahoy.setup(config) if config.integrations.enable_ahoy
|
|
53
67
|
Integrations::ActiveModelSerializers.setup(config) if config.integrations.enable_active_model_serializers
|
|
54
|
-
Integrations::HostAuthorization.setup(config) if config.integrations.enable_host_authorization
|
|
55
|
-
Integrations::RackErrorHandler.setup(config) if config.integrations.enable_rack_error_handler
|
|
56
68
|
Integrations::Shrine.setup(config) if config.integrations.enable_shrine
|
|
57
69
|
Integrations::ActiveStorage.setup(config) if config.integrations.enable_activestorage
|
|
58
70
|
Integrations::CarrierWave.setup(config) if config.integrations.enable_carrierwave
|
|
@@ -62,5 +74,13 @@ module LogStruct
|
|
|
62
74
|
end
|
|
63
75
|
Integrations::Puma.setup(config) if config.integrations.enable_puma
|
|
64
76
|
end
|
|
77
|
+
|
|
78
|
+
sig { params(config: LogStruct::Configuration).void }
|
|
79
|
+
def self.setup_middleware_integrations(config)
|
|
80
|
+
Integrations::HostAuthorization.setup(config) if config.integrations.enable_host_authorization
|
|
81
|
+
Integrations::RackErrorHandler.setup(config) if config.integrations.enable_rack_error_handler
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private_class_method :setup_non_middleware_integrations, :setup_middleware_integrations
|
|
65
85
|
end
|
|
66
86
|
end
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
require "log_struct/shared/interfaces/common_fields"
|
|
10
10
|
require "log_struct/shared/interfaces/additional_data_field"
|
|
11
11
|
require "log_struct/shared/interfaces/request_fields"
|
|
12
|
-
require "log_struct/shared/
|
|
13
|
-
require "log_struct/shared/
|
|
14
|
-
require "log_struct/shared/
|
|
12
|
+
require "log_struct/shared/serialize_common"
|
|
13
|
+
require "log_struct/shared/merge_additional_data_fields"
|
|
14
|
+
require "log_struct/shared/add_request_fields"
|
|
15
15
|
require_relative "../../enums/source"
|
|
16
16
|
require_relative "../../enums/event"
|
|
17
17
|
require_relative "../../enums/level"
|
|
@@ -21,11 +21,6 @@ module LogStruct
|
|
|
21
21
|
module Log
|
|
22
22
|
class ActionMailer
|
|
23
23
|
class Delivered < T::Struct
|
|
24
|
-
# typed: strict
|
|
25
|
-
# frozen_string_literal: true
|
|
26
|
-
|
|
27
|
-
extend T::Sig
|
|
28
|
-
|
|
29
24
|
extend T::Sig
|
|
30
25
|
|
|
31
26
|
# Shared/common fields
|
|
@@ -37,62 +32,32 @@ module LogStruct
|
|
|
37
32
|
const :to, T.nilable(T::Array[String]), default: nil
|
|
38
33
|
const :from, T.nilable(String), default: nil
|
|
39
34
|
const :subject, T.nilable(String), default: nil
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
const :message_id, T.nilable(String), default: nil
|
|
36
|
+
const :mailer_class, T.nilable(String), default: nil
|
|
37
|
+
const :mailer_action, T.nilable(String), default: nil
|
|
38
|
+
const :attachment_count, T.nilable(Integer), default: nil
|
|
42
39
|
|
|
43
40
|
# Additional data
|
|
44
41
|
include LogStruct::Log::Interfaces::AdditionalDataField
|
|
45
42
|
const :additional_data, T.nilable(T::Hash[T.any(String, Symbol), T.untyped]), default: nil
|
|
46
43
|
include LogStruct::Log::Shared::MergeAdditionalDataFields
|
|
47
44
|
|
|
48
|
-
# Request fields (optional)
|
|
49
|
-
|
|
50
45
|
# Serialize shared fields
|
|
51
46
|
include LogStruct::Log::Interfaces::CommonFields
|
|
52
47
|
include LogStruct::Log::Shared::SerializeCommon
|
|
53
48
|
|
|
54
|
-
sig {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
subject: T.untyped).returns(T::Hash[LogStruct::LogField, T.untyped])
|
|
58
|
-
}
|
|
59
|
-
def self.base_hash(to: nil,
|
|
60
|
-
from: nil,
|
|
61
|
-
subject: nil)
|
|
62
|
-
h = {}
|
|
49
|
+
sig { returns(T::Hash[LogStruct::LogField, T.untyped]) }
|
|
50
|
+
def to_h
|
|
51
|
+
h = T.let({}, T::Hash[LogStruct::LogField, T.untyped])
|
|
63
52
|
h[LogField::To] = to unless to.nil?
|
|
64
53
|
h[LogField::From] = from unless from.nil?
|
|
65
54
|
h[LogField::Subject] = subject unless subject.nil?
|
|
55
|
+
h[LogField::MessageId] = message_id unless message_id.nil?
|
|
56
|
+
h[LogField::MailerClass] = mailer_class unless mailer_class.nil?
|
|
57
|
+
h[LogField::MailerAction] = mailer_action unless mailer_action.nil?
|
|
58
|
+
h[LogField::AttachmentCount] = attachment_count unless attachment_count.nil?
|
|
66
59
|
h
|
|
67
60
|
end
|
|
68
|
-
|
|
69
|
-
sig {
|
|
70
|
-
params(to: T.untyped,
|
|
71
|
-
from: T.untyped,
|
|
72
|
-
subject: T.untyped,
|
|
73
|
-
additional_data: T.untyped,
|
|
74
|
-
timestamp: T.untyped).returns(T::Hash[LogStruct::LogField, T.untyped])
|
|
75
|
-
}
|
|
76
|
-
def self.build(to: nil,
|
|
77
|
-
from: nil,
|
|
78
|
-
subject: nil,
|
|
79
|
-
additional_data: nil,
|
|
80
|
-
timestamp: Time.now)
|
|
81
|
-
base_hash(to: to,
|
|
82
|
-
from: from,
|
|
83
|
-
subject: subject)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
sig { returns(T::Hash[LogStruct::LogField, T.untyped]) }
|
|
87
|
-
def to_h
|
|
88
|
-
self.class.build(
|
|
89
|
-
to: to,
|
|
90
|
-
from: from,
|
|
91
|
-
subject: subject,
|
|
92
|
-
additional_data: additional_data,
|
|
93
|
-
timestamp: timestamp
|
|
94
|
-
)
|
|
95
|
-
end
|
|
96
61
|
end
|
|
97
62
|
end
|
|
98
63
|
end
|