logstruct 0.0.2 → 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.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -22
  3. data/README.md +25 -2
  4. data/lib/log_struct/boot_buffer.rb +28 -0
  5. data/lib/log_struct/builders/active_job.rb +84 -0
  6. data/lib/log_struct/concerns/configuration.rb +126 -13
  7. data/lib/log_struct/concerns/error_handling.rb +3 -7
  8. data/lib/log_struct/concerns/logging.rb +5 -5
  9. data/lib/log_struct/config_struct/filters.rb +18 -0
  10. data/lib/log_struct/config_struct/integrations.rb +16 -12
  11. data/lib/log_struct/configuration.rb +13 -0
  12. data/lib/log_struct/enums/event.rb +13 -0
  13. data/lib/log_struct/enums/log_field.rb +154 -0
  14. data/lib/log_struct/enums/source.rb +4 -1
  15. data/lib/log_struct/formatter.rb +29 -17
  16. data/lib/log_struct/integrations/action_mailer/error_handling.rb +3 -11
  17. data/lib/log_struct/integrations/action_mailer/event_logging.rb +22 -12
  18. data/lib/log_struct/integrations/active_job/log_subscriber.rb +52 -48
  19. data/lib/log_struct/integrations/active_model_serializers.rb +49 -0
  20. data/lib/log_struct/integrations/active_record.rb +35 -5
  21. data/lib/log_struct/integrations/active_storage.rb +59 -20
  22. data/lib/log_struct/integrations/ahoy.rb +54 -0
  23. data/lib/log_struct/integrations/carrierwave.rb +13 -16
  24. data/lib/log_struct/integrations/dotenv.rb +278 -0
  25. data/lib/log_struct/integrations/good_job/log_subscriber.rb +86 -136
  26. data/lib/log_struct/integrations/good_job/logger.rb +8 -10
  27. data/lib/log_struct/integrations/good_job.rb +5 -7
  28. data/lib/log_struct/integrations/host_authorization.rb +25 -4
  29. data/lib/log_struct/integrations/lograge.rb +20 -14
  30. data/lib/log_struct/integrations/puma.rb +482 -0
  31. data/lib/log_struct/integrations/rack_error_handler/middleware.rb +11 -18
  32. data/lib/log_struct/integrations/shrine.rb +44 -19
  33. data/lib/log_struct/integrations/sorbet.rb +48 -0
  34. data/lib/log_struct/integrations.rb +25 -0
  35. data/lib/log_struct/log/action_mailer/delivered.rb +99 -0
  36. data/lib/log_struct/log/action_mailer/delivery.rb +99 -0
  37. data/lib/log_struct/log/action_mailer.rb +30 -45
  38. data/lib/log_struct/log/active_job/enqueue.rb +125 -0
  39. data/lib/log_struct/log/active_job/finish.rb +130 -0
  40. data/lib/log_struct/log/active_job/schedule.rb +125 -0
  41. data/lib/log_struct/log/active_job/start.rb +130 -0
  42. data/lib/log_struct/log/active_job.rb +41 -54
  43. data/lib/log_struct/log/active_model_serializers.rb +94 -0
  44. data/lib/log_struct/log/active_storage/delete.rb +87 -0
  45. data/lib/log_struct/log/active_storage/download.rb +103 -0
  46. data/lib/log_struct/log/active_storage/exist.rb +93 -0
  47. data/lib/log_struct/log/active_storage/metadata.rb +93 -0
  48. data/lib/log_struct/log/active_storage/stream.rb +93 -0
  49. data/lib/log_struct/log/active_storage/upload.rb +118 -0
  50. data/lib/log_struct/log/active_storage/url.rb +93 -0
  51. data/lib/log_struct/log/active_storage.rb +32 -68
  52. data/lib/log_struct/log/ahoy.rb +88 -0
  53. data/lib/log_struct/log/carrierwave/delete.rb +115 -0
  54. data/lib/log_struct/log/carrierwave/download.rb +131 -0
  55. data/lib/log_struct/log/carrierwave/upload.rb +141 -0
  56. data/lib/log_struct/log/carrierwave.rb +37 -72
  57. data/lib/log_struct/log/dotenv/load.rb +76 -0
  58. data/lib/log_struct/log/dotenv/restore.rb +76 -0
  59. data/lib/log_struct/log/dotenv/save.rb +76 -0
  60. data/lib/log_struct/log/dotenv/update.rb +76 -0
  61. data/lib/log_struct/log/dotenv.rb +12 -0
  62. data/lib/log_struct/log/error.rb +58 -46
  63. data/lib/log_struct/log/good_job/enqueue.rb +126 -0
  64. data/lib/log_struct/log/good_job/error.rb +151 -0
  65. data/lib/log_struct/log/good_job/finish.rb +136 -0
  66. data/lib/log_struct/log/good_job/log.rb +131 -0
  67. data/lib/log_struct/log/good_job/schedule.rb +136 -0
  68. data/lib/log_struct/log/good_job/start.rb +136 -0
  69. data/lib/log_struct/log/good_job.rb +40 -141
  70. data/lib/log_struct/log/interfaces/additional_data_field.rb +1 -17
  71. data/lib/log_struct/log/interfaces/common_fields.rb +1 -39
  72. data/lib/log_struct/log/interfaces/public_common_fields.rb +4 -0
  73. data/lib/log_struct/log/interfaces/request_fields.rb +1 -33
  74. data/lib/log_struct/log/plain.rb +59 -34
  75. data/lib/log_struct/log/puma/shutdown.rb +80 -0
  76. data/lib/log_struct/log/puma/start.rb +120 -0
  77. data/lib/log_struct/log/puma.rb +10 -0
  78. data/lib/log_struct/log/request.rb +132 -48
  79. data/lib/log_struct/log/security/blocked_host.rb +141 -0
  80. data/lib/log_struct/log/security/csrf_violation.rb +131 -0
  81. data/lib/log_struct/log/security/ip_spoof.rb +141 -0
  82. data/lib/log_struct/log/security.rb +40 -70
  83. data/lib/log_struct/log/shared/add_request_fields.rb +1 -26
  84. data/lib/log_struct/log/shared/merge_additional_data_fields.rb +1 -25
  85. data/lib/log_struct/log/shared/serialize_common.rb +1 -33
  86. data/lib/log_struct/log/shared/serialize_common_public.rb +44 -0
  87. data/lib/log_struct/log/shrine/delete.rb +85 -0
  88. data/lib/log_struct/log/shrine/download.rb +90 -0
  89. data/lib/log_struct/log/shrine/exist.rb +90 -0
  90. data/lib/log_struct/log/shrine/metadata.rb +90 -0
  91. data/lib/log_struct/log/shrine/upload.rb +105 -0
  92. data/lib/log_struct/log/shrine.rb +10 -67
  93. data/lib/log_struct/log/sidekiq.rb +65 -26
  94. data/lib/log_struct/log/sql.rb +113 -106
  95. data/lib/log_struct/log.rb +31 -32
  96. data/lib/log_struct/multi_error_reporter.rb +80 -22
  97. data/lib/log_struct/param_filters.rb +50 -7
  98. data/lib/log_struct/rails_boot_banner_silencer.rb +123 -0
  99. data/lib/log_struct/railtie.rb +71 -0
  100. data/lib/log_struct/semantic_logger/formatter.rb +4 -2
  101. data/lib/log_struct/semantic_logger/setup.rb +34 -18
  102. data/lib/log_struct/shared/interfaces/additional_data_field.rb +22 -0
  103. data/lib/log_struct/shared/interfaces/common_fields.rb +39 -0
  104. data/lib/log_struct/shared/interfaces/public_common_fields.rb +29 -0
  105. data/lib/log_struct/shared/interfaces/request_fields.rb +39 -0
  106. data/lib/log_struct/shared/shared/add_request_fields.rb +28 -0
  107. data/lib/log_struct/shared/shared/merge_additional_data_fields.rb +27 -0
  108. data/lib/log_struct/shared/shared/serialize_common.rb +58 -0
  109. data/lib/log_struct/version.rb +1 -1
  110. data/lib/log_struct.rb +22 -4
  111. data/logstruct.gemspec +3 -0
  112. metadata +108 -5
  113. data/lib/log_struct/log/interfaces/message_field.rb +0 -20
  114. data/lib/log_struct/log_keys.rb +0 -102
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22a9091af167d668b3470dfc55ea0d02de4809b66dd51db5531fe60e138c5452
4
- data.tar.gz: 75f1e277bc6dafa3666ebd1be7e1c64c5e783065520fe5261eb734c3011ade3f
3
+ metadata.gz: 0b926657fc690c72031cf72973642c3654264b3ea348f2212a625788b62d1ade
4
+ data.tar.gz: 650b11e3b8b3e975feee474994ae9420b68da2eb933942f28cc495649d71f225
5
5
  SHA512:
6
- metadata.gz: 6202a87818cb4ba803df9b5a516291fd7513ad58c0d94e2a42bd5f341f9bc3f5e04e777bdaa82f843d09b78a4ddbd823f87e64777ff91f9d4b07606deb554291
7
- data.tar.gz: 886387655858e495eb9cb36f0d4d727f17719161771b7bdd52b67328cea78c975af3e40d8689e66e9b1cfacc8e2339867286303245770ed2680d37890d5d8e08
6
+ metadata.gz: 2e0339abeafbea5bf239799dcf1ca843f57aae004e356e407a18909d9c0b6e4d1302c897f358be7bf539b5e0534efd032319a0b3055ff29e169c1d3f22557193
7
+ data.tar.gz: de385bc25a32f189044fbf1749e4511c1f9411d37a9af8628fc655e9706daba1a6b94b9482de756ef12557cc497deffa6f49a39ab9b434b3be6e25c7cb402214
data/CHANGELOG.md CHANGED
@@ -5,28 +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.0.1] - 2025-03-04
8
+ ## [0.1.1] - 2025-09-29
9
9
 
10
- ### Added
10
+ Added dotenv-rails integration. Many other fixes and improvements.
11
11
 
12
- - Pushed empty gem to RubyGems to secure the name
12
+ ## [0.1.0] - 2025-09-07
13
13
 
14
- ## [0.0.2-rc1] - 2025-09-05
15
-
16
- ### Added
17
-
18
- - Unified GitHub Actions workflow to release RubyGem and sync/tag the Terraform provider; dry-run support for safe validation
19
- - Provider catalog export script and automated provider CI (build/vet/test)
20
- - Terraform docs page with quickstart, recipes, and provider README link
21
- - API design doc (typed vs. untyped) planned; initial philosophy captured
22
- - Coverage threshold gate (>= 80%) in CI; additional tests to lift coverage
23
- - Release helper: scripts/create_release_tag.sh (creates annotated tag from version.rb)
24
-
25
- ### Changed
26
-
27
- - ActionMailer callbacks patched for Rails 7.0; event logging + metadata collection tested
28
- - Provider CloudWatch filter builder refactored for testability; key lookups aligned with exported catalog (event/source)
29
-
30
- ### Fixed
31
-
32
- - Pre-commit hooks reporting; cspell dictionary updated; TypeScript import fixes in site
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,10 +58,33 @@ 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.)
64
77
 
78
+ ### Custom Typed Logs
79
+
80
+ In addition to the built‑in, strictly typed log structures (Request, Error, SQL, etc.), you can define your own app‑specific typed logs while still using the public `LogStruct.info/error/...` methods.
81
+
82
+ - Compose the public interfaces: include `LogStruct::Log::Interfaces::PublicCommonFields` and the helpers `SerializeCommonPublic` + `MergeAdditionalDataFields` in your `T::Struct`.
83
+ - Fix your `source` to a constant (e.g., return the string `"payments"`), and restrict `event` with a `T::Enum` (e.g., `processed|failed|refunded`).
84
+ - The `LogStruct.info` signature accepts either the internal `CommonFields` (for built‑ins) or your public custom type, so you keep type safety at the call site.
85
+
86
+ See the docs page for a complete example: [Sorbet Types → Custom Typed Logs](https://logstruct.com/docs/sorbet-types#custom-log-classes).
87
+
65
88
  ## License
66
89
 
67
90
  This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
@@ -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 "false" (or any non-"true")
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"] == "true"
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
- # Convert all Rails filter parameters to symbols and merge with our filter keys
77
- converted_params = rails_filter_params.map do |param|
78
- param.respond_to?(:to_sym) ? param.to_sym : param
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
- # Add Rails filter parameters to our filter keys
82
- config.filters.filter_keys += converted_params
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
- # Ensure no duplicates
85
- config.filters.filter_keys.uniq!
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
- # Clear Rails filter parameters since we've incorporated them
88
- ::Rails.application.config.filter_parameters.clear
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::LogStruct
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::Error.from_exception(
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
 
@@ -11,31 +11,31 @@ module LogStruct
11
11
  extend T::Sig
12
12
 
13
13
  # Log a log struct at debug level
14
- sig { params(log: Log::Interfaces::CommonFields).void }
14
+ sig { params(log: T.any(Log::Interfaces::CommonFields, Log::Interfaces::PublicCommonFields)).void }
15
15
  def debug(log)
16
16
  Rails.logger.debug(log)
17
17
  end
18
18
 
19
19
  # Log a log struct at info level
20
- sig { params(log: Log::Interfaces::CommonFields).void }
20
+ sig { params(log: T.any(Log::Interfaces::CommonFields, Log::Interfaces::PublicCommonFields)).void }
21
21
  def info(log)
22
22
  Rails.logger.info(log)
23
23
  end
24
24
 
25
25
  # Log a log struct at warn level
26
- sig { params(log: Log::Interfaces::CommonFields).void }
26
+ sig { params(log: T.any(Log::Interfaces::CommonFields, Log::Interfaces::PublicCommonFields)).void }
27
27
  def warn(log)
28
28
  Rails.logger.warn(log)
29
29
  end
30
30
 
31
31
  # Log a log struct at error level
32
- sig { params(log: Log::Interfaces::CommonFields).void }
32
+ sig { params(log: T.any(Log::Interfaces::CommonFields, Log::Interfaces::PublicCommonFields)).void }
33
33
  def error(log)
34
34
  Rails.logger.error(log)
35
35
  end
36
36
 
37
37
  # Log a log struct at fatal level
38
- sig { params(log: Log::Interfaces::CommonFields).void }
38
+ sig { params(log: T.any(Log::Interfaces::CommonFields, Log::Interfaces::PublicCommonFields)).void }
39
39
  def fatal(log)
40
40
  Rails.logger.fatal(log)
41
41
  end
@@ -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
@@ -84,6 +72,22 @@ module LogStruct
84
72
  # Include bind parameters in SQL logs (disable in production for security)
85
73
  # Default: true in development/test, false in production
86
74
  prop :sql_log_bind_params, T::Boolean, factory: -> { !defined?(::Rails) || !::Rails.respond_to?(:env) || !::Rails.env.production? }
75
+
76
+ # Enable Ahoy (analytics events) integration
77
+ # Default: true (safe no-op unless Ahoy is defined)
78
+ prop :enable_ahoy, T::Boolean, default: true
79
+
80
+ # Enable ActiveModelSerializers integration
81
+ # Default: true (safe no-op unless ActiveModelSerializers is defined)
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
87
91
  end
88
92
  end
89
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)