logstruct 0.1.0 → 0.1.1
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 +5 -1
- data/README.md +15 -2
- data/lib/log_struct/boot_buffer.rb +28 -0
- data/lib/log_struct/builders/active_job.rb +84 -0
- data/lib/log_struct/concerns/configuration.rb +126 -13
- data/lib/log_struct/concerns/error_handling.rb +3 -7
- data/lib/log_struct/config_struct/filters.rb +18 -0
- data/lib/log_struct/config_struct/integrations.rb +8 -12
- data/lib/log_struct/configuration.rb +13 -0
- data/lib/log_struct/enums/event.rb +13 -0
- data/lib/log_struct/enums/log_field.rb +154 -0
- data/lib/log_struct/enums/source.rb +4 -1
- data/lib/log_struct/formatter.rb +29 -17
- data/lib/log_struct/integrations/action_mailer/error_handling.rb +3 -11
- data/lib/log_struct/integrations/action_mailer/event_logging.rb +22 -12
- data/lib/log_struct/integrations/active_job/log_subscriber.rb +52 -48
- data/lib/log_struct/integrations/active_model_serializers.rb +8 -14
- data/lib/log_struct/integrations/active_record.rb +35 -5
- data/lib/log_struct/integrations/active_storage.rb +59 -20
- data/lib/log_struct/integrations/ahoy.rb +2 -1
- data/lib/log_struct/integrations/carrierwave.rb +13 -16
- data/lib/log_struct/integrations/dotenv.rb +278 -0
- data/lib/log_struct/integrations/good_job/log_subscriber.rb +86 -136
- data/lib/log_struct/integrations/good_job/logger.rb +8 -10
- data/lib/log_struct/integrations/good_job.rb +5 -7
- data/lib/log_struct/integrations/host_authorization.rb +25 -4
- data/lib/log_struct/integrations/lograge.rb +20 -14
- data/lib/log_struct/integrations/puma.rb +482 -0
- data/lib/log_struct/integrations/rack_error_handler/middleware.rb +11 -18
- data/lib/log_struct/integrations/shrine.rb +44 -19
- data/lib/log_struct/integrations/sorbet.rb +48 -0
- data/lib/log_struct/integrations.rb +21 -0
- data/lib/log_struct/log/action_mailer/delivered.rb +99 -0
- data/lib/log_struct/log/action_mailer/delivery.rb +99 -0
- data/lib/log_struct/log/action_mailer.rb +30 -45
- data/lib/log_struct/log/active_job/enqueue.rb +125 -0
- data/lib/log_struct/log/active_job/finish.rb +130 -0
- data/lib/log_struct/log/active_job/schedule.rb +125 -0
- data/lib/log_struct/log/active_job/start.rb +130 -0
- data/lib/log_struct/log/active_job.rb +41 -54
- data/lib/log_struct/log/active_model_serializers.rb +72 -33
- data/lib/log_struct/log/active_storage/delete.rb +87 -0
- data/lib/log_struct/log/active_storage/download.rb +103 -0
- data/lib/log_struct/log/active_storage/exist.rb +93 -0
- data/lib/log_struct/log/active_storage/metadata.rb +93 -0
- data/lib/log_struct/log/active_storage/stream.rb +93 -0
- data/lib/log_struct/log/active_storage/upload.rb +118 -0
- data/lib/log_struct/log/active_storage/url.rb +93 -0
- data/lib/log_struct/log/active_storage.rb +32 -68
- data/lib/log_struct/log/ahoy.rb +67 -33
- data/lib/log_struct/log/carrierwave/delete.rb +115 -0
- data/lib/log_struct/log/carrierwave/download.rb +131 -0
- data/lib/log_struct/log/carrierwave/upload.rb +141 -0
- data/lib/log_struct/log/carrierwave.rb +37 -72
- data/lib/log_struct/log/dotenv/load.rb +76 -0
- data/lib/log_struct/log/dotenv/restore.rb +76 -0
- data/lib/log_struct/log/dotenv/save.rb +76 -0
- data/lib/log_struct/log/dotenv/update.rb +76 -0
- data/lib/log_struct/log/dotenv.rb +12 -0
- data/lib/log_struct/log/error.rb +58 -47
- data/lib/log_struct/log/good_job/enqueue.rb +126 -0
- data/lib/log_struct/log/good_job/error.rb +151 -0
- data/lib/log_struct/log/good_job/finish.rb +136 -0
- data/lib/log_struct/log/good_job/log.rb +131 -0
- data/lib/log_struct/log/good_job/schedule.rb +136 -0
- data/lib/log_struct/log/good_job/start.rb +136 -0
- data/lib/log_struct/log/good_job.rb +40 -141
- data/lib/log_struct/log/interfaces/additional_data_field.rb +1 -17
- data/lib/log_struct/log/interfaces/common_fields.rb +1 -39
- data/lib/log_struct/log/interfaces/public_common_fields.rb +1 -28
- data/lib/log_struct/log/interfaces/request_fields.rb +1 -33
- data/lib/log_struct/log/plain.rb +59 -34
- data/lib/log_struct/log/puma/shutdown.rb +80 -0
- data/lib/log_struct/log/puma/start.rb +120 -0
- data/lib/log_struct/log/puma.rb +10 -0
- data/lib/log_struct/log/request.rb +132 -48
- data/lib/log_struct/log/security/blocked_host.rb +141 -0
- data/lib/log_struct/log/security/csrf_violation.rb +131 -0
- data/lib/log_struct/log/security/ip_spoof.rb +141 -0
- data/lib/log_struct/log/security.rb +40 -70
- data/lib/log_struct/log/shared/add_request_fields.rb +1 -26
- data/lib/log_struct/log/shared/merge_additional_data_fields.rb +1 -22
- data/lib/log_struct/log/shared/serialize_common.rb +1 -33
- data/lib/log_struct/log/shared/serialize_common_public.rb +9 -9
- data/lib/log_struct/log/shrine/delete.rb +85 -0
- data/lib/log_struct/log/shrine/download.rb +90 -0
- data/lib/log_struct/log/shrine/exist.rb +90 -0
- data/lib/log_struct/log/shrine/metadata.rb +90 -0
- data/lib/log_struct/log/shrine/upload.rb +105 -0
- data/lib/log_struct/log/shrine.rb +10 -67
- data/lib/log_struct/log/sidekiq.rb +65 -26
- data/lib/log_struct/log/sql.rb +113 -106
- data/lib/log_struct/log.rb +29 -36
- data/lib/log_struct/multi_error_reporter.rb +80 -22
- data/lib/log_struct/param_filters.rb +50 -7
- data/lib/log_struct/rails_boot_banner_silencer.rb +123 -0
- data/lib/log_struct/railtie.rb +71 -0
- data/lib/log_struct/semantic_logger/formatter.rb +4 -2
- data/lib/log_struct/semantic_logger/setup.rb +34 -18
- data/lib/log_struct/shared/interfaces/additional_data_field.rb +22 -0
- data/lib/log_struct/shared/interfaces/common_fields.rb +39 -0
- data/lib/log_struct/shared/interfaces/public_common_fields.rb +29 -0
- data/lib/log_struct/shared/interfaces/request_fields.rb +39 -0
- data/lib/log_struct/shared/shared/add_request_fields.rb +28 -0
- data/lib/log_struct/shared/shared/merge_additional_data_fields.rb +27 -0
- data/lib/log_struct/shared/shared/serialize_common.rb +58 -0
- data/lib/log_struct/version.rb +1 -1
- data/lib/log_struct.rb +22 -4
- data/logstruct.gemspec +2 -1
- metadata +78 -9
- data/lib/log_struct/log/interfaces/message_field.rb +0 -20
- data/lib/log_struct/log_keys.rb +0 -102
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b926657fc690c72031cf72973642c3654264b3ea348f2212a625788b62d1ade
|
4
|
+
data.tar.gz: 650b11e3b8b3e975feee474994ae9420b68da2eb933942f28cc495649d71f225
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e0339abeafbea5bf239799dcf1ca843f57aae004e356e407a18909d9c0b6e4d1302c897f358be7bf539b5e0534efd032319a0b3055ff29e169c1d3f22557193
|
7
|
+
data.tar.gz: de385bc25a32f189044fbf1749e4511c1f9411d37a9af8628fc655e9706daba1a6b94b9482de756ef12557cc497deffa6f49a39ab9b434b3be6e25c7cb402214
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,10 @@ 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
|
+
## [0.1.1] - 2025-09-29
|
9
|
+
|
10
|
+
Added dotenv-rails integration. Many other fixes and improvements.
|
11
|
+
|
8
12
|
## [0.1.0] - 2025-09-07
|
9
13
|
|
10
|
-
Initial release.
|
14
|
+
Initial beta release.
|
data/README.md
CHANGED
@@ -48,9 +48,9 @@ Please see the [documentation](https://logstruct.com/docs/configuration/) for co
|
|
48
48
|
|
49
49
|
### Important Notes on Integration
|
50
50
|
|
51
|
-
Once initialized, the gem automatically includes its modules into the appropriate base classes:
|
51
|
+
Once initialized (and enabled), the gem automatically includes its modules into the appropriate base classes:
|
52
52
|
|
53
|
-
- `ActiveSupport::TaggedLogging` is patched to support both Hashes and Strings
|
53
|
+
- `ActiveSupport::TaggedLogging` is patched to support both Hashes and Strings (only when LogStruct is enabled)
|
54
54
|
- `ActionMailer::Base` includes error handling and event logging modules
|
55
55
|
- We configure `Lograge` for request logging
|
56
56
|
- A Rack middleware is inserted to catch and log errors, including security violations (IP spoofing, CSRF, blocked hosts, etc.)
|
@@ -58,6 +58,19 @@ Once initialized, the gem automatically includes its modules into the appropriat
|
|
58
58
|
- Rails `config.filter_parameters` are merged into LogStruct's filters and then cleared (to avoid double filtering). Configure sensitive keys via `LogStruct.config.filters`.
|
59
59
|
- When `RAILS_LOG_TO_STDOUT` is set, we log to STDOUT only. Otherwise, we log to STDOUT by default without adding a file appender to avoid duplicate logs.
|
60
60
|
|
61
|
+
### Development behavior
|
62
|
+
|
63
|
+
- Disabled by default in development. Enable explicitly via `LOGSTRUCT_ENABLED=true` or `LogStruct.configure { |c| c.enabled = true }`.
|
64
|
+
- When enabled in development, LogStruct now defaults to production‑style JSON output so you can preview exactly what will be shipped in prod.
|
65
|
+
- You can opt back into the colorful human formatter with:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
LogStruct.configure do |c|
|
69
|
+
c.prefer_json_in_development = false
|
70
|
+
c.enable_color_output = true
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
61
74
|
## Documentation
|
62
75
|
|
63
76
|
Please see the [documentation](https://logstruct.com/docs) for more details. (All code examples are type-checked and tested, and it's harder to keep a README up to date.)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module LogStruct
|
5
|
+
# Collects structured logs during very early boot before the logger is ready.
|
6
|
+
module BootBuffer
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
@@logs = T.let([], T::Array[LogStruct::Log::Interfaces::CommonFields])
|
10
|
+
|
11
|
+
sig { params(log: LogStruct::Log::Interfaces::CommonFields).void }
|
12
|
+
def self.add(log)
|
13
|
+
@@logs << log
|
14
|
+
end
|
15
|
+
|
16
|
+
sig { void }
|
17
|
+
def self.flush
|
18
|
+
return if @@logs.empty?
|
19
|
+
@@logs.each { |l| LogStruct.info(l) }
|
20
|
+
@@logs.clear
|
21
|
+
end
|
22
|
+
|
23
|
+
sig { void }
|
24
|
+
def self.clear
|
25
|
+
@@logs.clear
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../log/active_job/enqueue"
|
5
|
+
require_relative "../log/active_job/schedule"
|
6
|
+
require_relative "../log/active_job/start"
|
7
|
+
require_relative "../log/active_job/finish"
|
8
|
+
|
9
|
+
module LogStruct
|
10
|
+
module Builders
|
11
|
+
module ActiveJob
|
12
|
+
extend T::Sig
|
13
|
+
|
14
|
+
sig { params(job: T.untyped).returns(T.nilable(String)) }
|
15
|
+
def self.safe_provider_job_id(job)
|
16
|
+
job.respond_to?(:provider_job_id) ? job.provider_job_id : nil
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { params(job: T.untyped).returns(T.nilable(Integer)) }
|
20
|
+
def self.safe_executions(job)
|
21
|
+
job.respond_to?(:executions) ? job.executions : nil
|
22
|
+
end
|
23
|
+
|
24
|
+
sig { params(job: T.untyped).returns(T.nilable(T::Array[T.untyped])) }
|
25
|
+
def self.safe_arguments(job)
|
26
|
+
return nil unless job.class.respond_to?(:log_arguments?)
|
27
|
+
job.class.log_arguments? ? job.arguments : nil
|
28
|
+
end
|
29
|
+
|
30
|
+
sig { params(job: T.untyped).returns(Log::ActiveJob::Enqueue) }
|
31
|
+
def self.enqueue(job)
|
32
|
+
Log::ActiveJob::Enqueue.new(
|
33
|
+
job_id: job.job_id,
|
34
|
+
job_class: job.class.to_s,
|
35
|
+
queue_name: job.queue_name,
|
36
|
+
arguments: safe_arguments(job),
|
37
|
+
executions: safe_executions(job),
|
38
|
+
provider_job_id: safe_provider_job_id(job)
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
sig { params(job: T.untyped, scheduled_at: Time).returns(Log::ActiveJob::Schedule) }
|
43
|
+
def self.schedule(job, scheduled_at:)
|
44
|
+
Log::ActiveJob::Schedule.new(
|
45
|
+
job_id: job.job_id,
|
46
|
+
job_class: job.class.to_s,
|
47
|
+
queue_name: job.queue_name,
|
48
|
+
arguments: safe_arguments(job),
|
49
|
+
executions: safe_executions(job),
|
50
|
+
provider_job_id: safe_provider_job_id(job),
|
51
|
+
scheduled_at: scheduled_at
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
sig { params(job: T.untyped, started_at: Time, attempt: T.nilable(Integer)).returns(Log::ActiveJob::Start) }
|
56
|
+
def self.start(job, started_at:, attempt:)
|
57
|
+
Log::ActiveJob::Start.new(
|
58
|
+
job_id: job.job_id,
|
59
|
+
job_class: job.class.to_s,
|
60
|
+
queue_name: job.queue_name,
|
61
|
+
arguments: safe_arguments(job),
|
62
|
+
executions: safe_executions(job),
|
63
|
+
provider_job_id: safe_provider_job_id(job),
|
64
|
+
started_at: started_at,
|
65
|
+
attempt: attempt
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
sig { params(job: T.untyped, duration_ms: Float, finished_at: Time).returns(Log::ActiveJob::Finish) }
|
70
|
+
def self.finish(job, duration_ms:, finished_at:)
|
71
|
+
Log::ActiveJob::Finish.new(
|
72
|
+
job_id: job.job_id,
|
73
|
+
job_class: job.class.to_s,
|
74
|
+
queue_name: job.queue_name,
|
75
|
+
arguments: safe_arguments(job),
|
76
|
+
executions: safe_executions(job),
|
77
|
+
provider_job_id: safe_provider_job_id(job),
|
78
|
+
duration_ms: duration_ms,
|
79
|
+
finished_at: finished_at
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -41,16 +41,16 @@ module LogStruct
|
|
41
41
|
def set_enabled_from_rails_env!
|
42
42
|
# Set enabled based on current Rails environment and the LOGSTRUCT_ENABLED env var.
|
43
43
|
# Precedence:
|
44
|
-
# 1. Check if LOGSTRUCT_ENABLED env var is defined
|
45
|
-
# - Sets enabled=true only when value is "true"
|
46
|
-
# - Sets enabled=false when value is
|
44
|
+
# 1. Check if LOGSTRUCT_ENABLED env var is defined (not an empty string)
|
45
|
+
# - Sets enabled=true only when value is "true", "yes", "1", etc.
|
46
|
+
# - Sets enabled=false when value is any other value
|
47
47
|
# 2. Otherwise, check if current Rails environment is in enabled_environments
|
48
48
|
# 3. Otherwise, leave as config.enabled (defaults to true)
|
49
49
|
|
50
50
|
# Then check if LOGSTRUCT_ENABLED env var is set
|
51
51
|
config.enabled = if ENV["LOGSTRUCT_ENABLED"]
|
52
52
|
# Override to true only if env var is "true"
|
53
|
-
ENV["LOGSTRUCT_ENABLED"]
|
53
|
+
%w[true t yes y 1].include?(ENV["LOGSTRUCT_ENABLED"]&.strip&.downcase)
|
54
54
|
else
|
55
55
|
config.enabled_environments.include?(::Rails.env.to_sym)
|
56
56
|
end
|
@@ -72,20 +72,133 @@ module LogStruct
|
|
72
72
|
|
73
73
|
rails_filter_params = ::Rails.application.config.filter_parameters
|
74
74
|
return unless rails_filter_params.is_a?(Array)
|
75
|
+
return if rails_filter_params.empty?
|
76
|
+
|
77
|
+
symbol_filters = T.let([], T::Array[Symbol])
|
78
|
+
matchers = T.let([], T::Array[ConfigStruct::FilterMatcher])
|
79
|
+
leftovers = T.let([], T::Array[T.untyped])
|
80
|
+
|
81
|
+
rails_filter_params.each do |entry|
|
82
|
+
matcher = build_filter_matcher(entry)
|
83
|
+
|
84
|
+
if matcher
|
85
|
+
matchers << matcher
|
86
|
+
next
|
87
|
+
end
|
88
|
+
|
89
|
+
normalized_symbol = normalize_filter_symbol(entry)
|
90
|
+
if normalized_symbol
|
91
|
+
symbol_filters << normalized_symbol
|
92
|
+
else
|
93
|
+
leftovers << entry
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
if symbol_filters.any?
|
98
|
+
config.filters.filter_keys |= symbol_filters
|
99
|
+
end
|
75
100
|
|
76
|
-
|
77
|
-
|
78
|
-
|
101
|
+
if matchers.any?
|
102
|
+
matchers.each do |matcher|
|
103
|
+
existing = config.filters.filter_matchers.any? do |registered|
|
104
|
+
registered.label == matcher.label
|
105
|
+
end
|
106
|
+
config.filters.filter_matchers << matcher unless existing
|
107
|
+
end
|
79
108
|
end
|
80
109
|
|
81
|
-
|
82
|
-
|
110
|
+
replace_filter_parameters(rails_filter_params, leftovers)
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
sig { params(filter: T.untyped).returns(T.nilable(Symbol)) }
|
116
|
+
def normalize_filter_symbol(filter)
|
117
|
+
return filter if filter.is_a?(Symbol)
|
118
|
+
return filter.downcase.to_sym if filter.is_a?(String)
|
83
119
|
|
84
|
-
|
85
|
-
|
120
|
+
return nil unless filter.respond_to?(:to_sym)
|
121
|
+
|
122
|
+
begin
|
123
|
+
sym = filter.to_sym
|
124
|
+
sym.is_a?(Symbol) ? sym : nil
|
125
|
+
rescue
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
sig { params(filter: T.untyped).returns(T.nilable(ConfigStruct::FilterMatcher)) }
|
131
|
+
def build_filter_matcher(filter)
|
132
|
+
case filter
|
133
|
+
when ::Regexp
|
134
|
+
callable = Kernel.lambda do |key, _value|
|
135
|
+
filter.match?(key)
|
136
|
+
end
|
137
|
+
return ConfigStruct::FilterMatcher.new(callable: callable, label: filter.inspect)
|
138
|
+
else
|
139
|
+
return build_callable_filter_matcher(filter) if callable_filter?(filter)
|
140
|
+
end
|
141
|
+
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
|
145
|
+
sig { params(filter: T.untyped).returns(T::Boolean) }
|
146
|
+
def callable_filter?(filter)
|
147
|
+
filter.respond_to?(:call)
|
148
|
+
end
|
149
|
+
|
150
|
+
sig { params(filter: T.untyped).returns(T.nilable(ConfigStruct::FilterMatcher)) }
|
151
|
+
def build_callable_filter_matcher(filter)
|
152
|
+
callable = Kernel.lambda do |key, value|
|
153
|
+
call_args = case arity_for_filter(filter)
|
154
|
+
when 0
|
155
|
+
[]
|
156
|
+
when 1
|
157
|
+
[key]
|
158
|
+
else
|
159
|
+
[key, value]
|
160
|
+
end
|
161
|
+
|
162
|
+
result = filter.call(*call_args)
|
163
|
+
!!result
|
164
|
+
rescue ArgumentError
|
165
|
+
begin
|
166
|
+
!!filter.call(key)
|
167
|
+
rescue => e
|
168
|
+
handle_filter_error(e, filter, key)
|
169
|
+
false
|
170
|
+
end
|
171
|
+
rescue => e
|
172
|
+
handle_filter_error(e, filter, key)
|
173
|
+
false
|
174
|
+
end
|
175
|
+
ConfigStruct::FilterMatcher.new(callable: callable, label: filter.inspect)
|
176
|
+
end
|
177
|
+
|
178
|
+
sig { params(filter: T.untyped).returns(Integer) }
|
179
|
+
def arity_for_filter(filter)
|
180
|
+
filter.respond_to?(:arity) ? filter.arity : 2
|
181
|
+
end
|
182
|
+
|
183
|
+
sig { params(filter_params: T::Array[T.untyped], leftovers: T::Array[T.untyped]).void }
|
184
|
+
def replace_filter_parameters(filter_params, leftovers)
|
185
|
+
filter_params.clear
|
186
|
+
filter_params.concat(leftovers)
|
187
|
+
end
|
86
188
|
|
87
|
-
|
88
|
-
|
189
|
+
sig { params(error: StandardError, filter: T.untyped, key: String).void }
|
190
|
+
def handle_filter_error(error, filter, key)
|
191
|
+
context = {
|
192
|
+
filter: filter.class.name,
|
193
|
+
key: key,
|
194
|
+
filter_label: begin
|
195
|
+
filter.inspect
|
196
|
+
rescue
|
197
|
+
"unknown"
|
198
|
+
end
|
199
|
+
}
|
200
|
+
|
201
|
+
LogStruct.handle_exception(error, source: Source::Internal, context: context)
|
89
202
|
end
|
90
203
|
end
|
91
204
|
end
|
@@ -21,12 +21,12 @@ module LogStruct
|
|
21
21
|
case source
|
22
22
|
when Source::TypeChecking
|
23
23
|
config.error_handling_modes.type_checking_errors
|
24
|
-
when Source::
|
24
|
+
when Source::Internal
|
25
25
|
config.error_handling_modes.logstruct_errors
|
26
26
|
when Source::Security
|
27
27
|
config.error_handling_modes.security_errors
|
28
28
|
when Source::Rails, Source::App, Source::Job, Source::Storage, Source::Mailer,
|
29
|
-
Source::Shrine, Source::CarrierWave, Source::Sidekiq
|
29
|
+
Source::Shrine, Source::CarrierWave, Source::Sidekiq, Source::Dotenv, Source::Puma
|
30
30
|
config.error_handling_modes.standard_errors
|
31
31
|
else
|
32
32
|
# Ensures the case statement is exhaustive
|
@@ -38,11 +38,7 @@ module LogStruct
|
|
38
38
|
sig { params(error: StandardError, source: Source, context: T.nilable(T::Hash[Symbol, T.untyped])).void }
|
39
39
|
def log_error(error, source:, context: nil)
|
40
40
|
# Create structured log entry
|
41
|
-
error_log = Log
|
42
|
-
source,
|
43
|
-
error,
|
44
|
-
context || {}
|
45
|
-
)
|
41
|
+
error_log = Log.from_exception(source, error, context || {})
|
46
42
|
LogStruct.error(error_log)
|
47
43
|
end
|
48
44
|
|
@@ -3,6 +3,18 @@
|
|
3
3
|
|
4
4
|
module LogStruct
|
5
5
|
module ConfigStruct
|
6
|
+
class FilterMatcher < T::Struct
|
7
|
+
extend T::Sig
|
8
|
+
|
9
|
+
const :callable, T.proc.params(key: String, value: T.untyped).returns(T::Boolean)
|
10
|
+
const :label, String
|
11
|
+
|
12
|
+
sig { params(key: String, value: T.untyped).returns(T::Boolean) }
|
13
|
+
def matches?(key, value)
|
14
|
+
callable.call(key, value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
6
18
|
class Filters < T::Struct
|
7
19
|
include Sorbet::SerializeSymbolKeys
|
8
20
|
|
@@ -75,6 +87,12 @@ module LogStruct
|
|
75
87
|
# Filter MAC addresses
|
76
88
|
# Default: false
|
77
89
|
prop :mac_addresses, T::Boolean, default: false
|
90
|
+
|
91
|
+
# Additional filter matchers built from Rails filter_parameters entries that aren't simple symbols.
|
92
|
+
# Each matcher receives the key (String) and optional value, returning true when the pair should be filtered.
|
93
|
+
prop :filter_matchers,
|
94
|
+
T::Array[FilterMatcher],
|
95
|
+
factory: -> { [] }
|
78
96
|
end
|
79
97
|
end
|
80
98
|
end
|
@@ -60,18 +60,6 @@ module LogStruct
|
|
60
60
|
# Default: true
|
61
61
|
prop :enable_semantic_logger, T::Boolean, default: true
|
62
62
|
|
63
|
-
# Enable colored JSON output in development
|
64
|
-
# Default: true
|
65
|
-
prop :enable_color_output, T::Boolean, default: true
|
66
|
-
|
67
|
-
# Color configuration for JSON output
|
68
|
-
# Default: nil (uses SemanticLogger defaults)
|
69
|
-
prop :color_map, T.nilable(T::Hash[Symbol, Symbol]), default: nil
|
70
|
-
|
71
|
-
# Filter noisy loggers (ActionView, etc.)
|
72
|
-
# Default: false
|
73
|
-
prop :filter_noisy_loggers, T::Boolean, default: false
|
74
|
-
|
75
63
|
# Enable SQL query logging through ActiveRecord instrumentation
|
76
64
|
# Default: false (can be resource intensive)
|
77
65
|
prop :enable_sql_logging, T::Boolean, default: false
|
@@ -92,6 +80,14 @@ module LogStruct
|
|
92
80
|
# Enable ActiveModelSerializers integration
|
93
81
|
# Default: true (safe no-op unless ActiveModelSerializers is defined)
|
94
82
|
prop :enable_active_model_serializers, T::Boolean, default: true
|
83
|
+
|
84
|
+
# Enable dotenv-rails integration (convert to structured logs)
|
85
|
+
# Default: true
|
86
|
+
prop :enable_dotenv, T::Boolean, default: true
|
87
|
+
|
88
|
+
# Enable Puma integration (convert server lifecycle logs)
|
89
|
+
# Default: true
|
90
|
+
prop :enable_puma, T::Boolean, default: true
|
95
91
|
end
|
96
92
|
end
|
97
93
|
end
|
@@ -20,6 +20,19 @@ module LogStruct
|
|
20
20
|
prop :enabled, T::Boolean, default: true
|
21
21
|
prop :enabled_environments, T::Array[Symbol], factory: -> { [:test, :production] }
|
22
22
|
prop :local_environments, T::Array[Symbol], factory: -> { [:development, :test] }
|
23
|
+
|
24
|
+
# Prefer production-style JSON in development when LogStruct is enabled
|
25
|
+
prop :prefer_json_in_development, T::Boolean, default: true
|
26
|
+
|
27
|
+
# Enable colorful human formatter in development
|
28
|
+
prop :enable_color_output, T::Boolean, default: true
|
29
|
+
|
30
|
+
# Custom color map for the color formatter
|
31
|
+
prop :color_map, T.nilable(T::Hash[Symbol, Symbol]), default: nil
|
32
|
+
|
33
|
+
# Filter noisy loggers (ActionView, etc.)
|
34
|
+
prop :filter_noisy_loggers, T::Boolean, default: false
|
35
|
+
|
23
36
|
const :integrations, ConfigStruct::Integrations, factory: -> { ConfigStruct::Integrations.new }
|
24
37
|
const :filters, ConfigStruct::Filters, factory: -> { ConfigStruct::Filters.new }
|
25
38
|
|
@@ -26,10 +26,23 @@ module LogStruct
|
|
26
26
|
Stream = new(:stream)
|
27
27
|
Url = new(:url)
|
28
28
|
|
29
|
+
# Data generation events
|
30
|
+
Generate = new(:generate)
|
31
|
+
|
29
32
|
# Email events
|
30
33
|
Delivery = new(:delivery)
|
31
34
|
Delivered = new(:delivered)
|
32
35
|
|
36
|
+
# Configuration / boot events
|
37
|
+
Load = new(:load)
|
38
|
+
Update = new(:update)
|
39
|
+
Save = new(:save)
|
40
|
+
Restore = new(:restore)
|
41
|
+
|
42
|
+
# Server lifecycle (e.g., Puma)
|
43
|
+
# Start already defined above
|
44
|
+
Shutdown = new(:shutdown)
|
45
|
+
|
33
46
|
# Security events
|
34
47
|
IPSpoof = new(:ip_spoof)
|
35
48
|
CSRFViolation = new(:csrf_violation)
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# NOTE:
|
5
|
+
# - This enum defines human‑readable field names (constants) that map to compact
|
6
|
+
# JSON key symbols via `serialize` (e.g., Database => :db).
|
7
|
+
# - The enum constant names are code‑generated into
|
8
|
+
# `schemas/meta/log-fields.json` by `scripts/generate_structs.rb` and
|
9
|
+
# referenced from `schemas/meta/log-source-schema.json` to strictly validate
|
10
|
+
# field keys in `schemas/log_sources/*`.
|
11
|
+
# - When adding or renaming fields here, run the generator so schema validation
|
12
|
+
# stays in sync.
|
13
|
+
#
|
14
|
+
# Use human-readable field names as the enum values and short field names for the JSON properties
|
15
|
+
|
16
|
+
module LogStruct
|
17
|
+
class LogField < T::Enum
|
18
|
+
enums do
|
19
|
+
# Shared fields
|
20
|
+
Source = new(:src)
|
21
|
+
Event = new(:evt)
|
22
|
+
Timestamp = new(:ts)
|
23
|
+
Level = new(:lvl)
|
24
|
+
|
25
|
+
# Common fields
|
26
|
+
Message = new(:msg)
|
27
|
+
Data = new(:data)
|
28
|
+
|
29
|
+
# Request-related fields
|
30
|
+
Path = new(:path)
|
31
|
+
HttpMethod = new(:method) # property name was http_method
|
32
|
+
SourceIp = new(:source_ip)
|
33
|
+
UserAgent = new(:user_agent)
|
34
|
+
Referer = new(:referer)
|
35
|
+
RequestId = new(:request_id)
|
36
|
+
|
37
|
+
# HTTP-specific fields
|
38
|
+
Format = new(:format)
|
39
|
+
Controller = new(:controller)
|
40
|
+
Action = new(:action)
|
41
|
+
Status = new(:status)
|
42
|
+
# DurationMs already defined below for general metrics
|
43
|
+
View = new(:view)
|
44
|
+
Database = new(:db)
|
45
|
+
Params = new(:params)
|
46
|
+
|
47
|
+
# Security-specific fields
|
48
|
+
BlockedHost = new(:blocked_host)
|
49
|
+
BlockedHosts = new(:blocked_hosts)
|
50
|
+
ClientIp = new(:client_ip)
|
51
|
+
XForwardedFor = new(:x_forwarded_for)
|
52
|
+
|
53
|
+
# Email-specific fields
|
54
|
+
To = new(:to)
|
55
|
+
From = new(:from)
|
56
|
+
Subject = new(:subject)
|
57
|
+
|
58
|
+
# Error fields
|
59
|
+
ErrClass = new(:err_class)
|
60
|
+
Backtrace = new(:backtrace)
|
61
|
+
|
62
|
+
# Job-specific fields
|
63
|
+
JobId = new(:job_id)
|
64
|
+
JobClass = new(:job_class)
|
65
|
+
QueueName = new(:queue_name)
|
66
|
+
Arguments = new(:arguments)
|
67
|
+
RetryCount = new(:retry_count)
|
68
|
+
Retries = new(:retries)
|
69
|
+
Attempt = new(:attempt)
|
70
|
+
Executions = new(:executions)
|
71
|
+
ExceptionExecutions = new(:exception_executions)
|
72
|
+
ProviderJobId = new(:provider_job_id)
|
73
|
+
ScheduledAt = new(:scheduled_at)
|
74
|
+
StartedAt = new(:started_at)
|
75
|
+
FinishedAt = new(:finished_at)
|
76
|
+
DurationMs = new(:duration_ms)
|
77
|
+
WaitMs = new(:wait_ms)
|
78
|
+
# Deprecated: ExecutionTime/WaitTime/RunTime
|
79
|
+
ExecutionTime = new(:execution_time)
|
80
|
+
WaitTime = new(:wait_time)
|
81
|
+
RunTime = new(:run_time)
|
82
|
+
Priority = new(:priority)
|
83
|
+
CronKey = new(:cron_key)
|
84
|
+
ErrorMessage = new(:error_message)
|
85
|
+
|
86
|
+
# Dotenv fields
|
87
|
+
File = new(:file)
|
88
|
+
Vars = new(:vars)
|
89
|
+
Snapshot = new(:snapshot)
|
90
|
+
|
91
|
+
# Sidekiq-specific fields
|
92
|
+
ProcessId = new(:pid)
|
93
|
+
ThreadId = new(:tid)
|
94
|
+
Context = new(:ctx)
|
95
|
+
|
96
|
+
# Storage-specific fields (ActiveStorage)
|
97
|
+
Checksum = new(:checksum)
|
98
|
+
Exist = new(:exist)
|
99
|
+
Url = new(:url)
|
100
|
+
Prefix = new(:prefix)
|
101
|
+
Range = new(:range)
|
102
|
+
|
103
|
+
# Storage-specific fields (Shrine)
|
104
|
+
Storage = new(:storage)
|
105
|
+
Operation = new(:op)
|
106
|
+
FileId = new(:file_id)
|
107
|
+
Filename = new(:filename)
|
108
|
+
MimeType = new(:mime_type)
|
109
|
+
Size = new(:size)
|
110
|
+
Metadata = new(:metadata)
|
111
|
+
Location = new(:location)
|
112
|
+
UploadOptions = new(:upload_opts)
|
113
|
+
DownloadOptions = new(:download_opts)
|
114
|
+
Options = new(:opts)
|
115
|
+
Uploader = new(:uploader)
|
116
|
+
|
117
|
+
# CarrierWave-specific fields
|
118
|
+
Model = new(:model)
|
119
|
+
MountPoint = new(:mount_point)
|
120
|
+
|
121
|
+
# SQL-specific fields
|
122
|
+
Sql = new(:sql)
|
123
|
+
Name = new(:name)
|
124
|
+
RowCount = new(:row_count)
|
125
|
+
# Use Adapter for both AMS and SQL adapter name
|
126
|
+
BindParams = new(:bind_params)
|
127
|
+
DatabaseName = new(:db_name)
|
128
|
+
ConnectionPoolSize = new(:pool_size)
|
129
|
+
ActiveConnections = new(:active_count)
|
130
|
+
OperationType = new(:op_type)
|
131
|
+
TableNames = new(:table_names)
|
132
|
+
|
133
|
+
# ActiveModelSerializers fields
|
134
|
+
Serializer = new(:serializer)
|
135
|
+
Adapter = new(:adapter)
|
136
|
+
ResourceClass = new(:resource_class)
|
137
|
+
|
138
|
+
# Ahoy-specific fields
|
139
|
+
AhoyEvent = new(:ahoy_event)
|
140
|
+
Properties = new(:properties)
|
141
|
+
|
142
|
+
# Puma / server lifecycle fields
|
143
|
+
Mode = new(:mode)
|
144
|
+
PumaVersion = new(:puma_version)
|
145
|
+
PumaCodename = new(:puma_codename)
|
146
|
+
RubyVersion = new(:ruby_version)
|
147
|
+
MinThreads = new(:min_threads)
|
148
|
+
MaxThreads = new(:max_threads)
|
149
|
+
Environment = new(:environment)
|
150
|
+
ListeningAddresses = new(:listening_addresses)
|
151
|
+
Address = new(:addr)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -7,8 +7,9 @@ module LogStruct
|
|
7
7
|
enums do
|
8
8
|
# Error sources
|
9
9
|
TypeChecking = new(:type_checking) # For type checking errors (Sorbet)
|
10
|
-
LogStruct = new(:logstruct) # Errors from LogStruct itself
|
11
10
|
Security = new(:security) # Security-related events
|
11
|
+
# Errors from LogStruct. (Cannot use LogStruct here because it confuses tapioca.)
|
12
|
+
Internal = new(:logstruct)
|
12
13
|
|
13
14
|
# Application sources
|
14
15
|
Rails = new(:rails) # For request-related logs/errors
|
@@ -21,6 +22,8 @@ module LogStruct
|
|
21
22
|
Shrine = new(:shrine)
|
22
23
|
CarrierWave = new(:carrierwave)
|
23
24
|
Sidekiq = new(:sidekiq)
|
25
|
+
Dotenv = new(:dotenv)
|
26
|
+
Puma = new(:puma)
|
24
27
|
end
|
25
28
|
end
|
26
29
|
end
|