Skip to content

Commit e3fd177

Browse files
committed
code review and fixes
1 parent 61d07ba commit e3fd177

File tree

20 files changed

+1647
-4441
lines changed

20 files changed

+1647
-4441
lines changed

.cspell/project-words.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,4 @@ TESTOPTS
5353
docspring
5454
shiki
5555
lightningcss
56+
klass

DEVELOPMENT.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,10 @@ And you always need to check for any third-party gems that are not part of Rails
9797

9898
- Use Sorbet type annotations for all methods
9999
- Ensure all files have the appropriate `# typed:` annotation
100-
- **NEVER use `T.unsafe` calls**. Instead, properly type your code or use appropriate type assertions with `T.let` or `T.cast` when absolutely necessary.
100+
- Prefer precise types and `T.let`/`T.cast` over `T.unsafe`. If a Sorbet limitation requires it (e.g., splat with callbacks, serializing unknown external objects), a minimal, localized `T.unsafe` may be used with a brief comment explaining why.
101101
- `T.untyped` is generally ok for Hash values when they come from unknown sources.
102-
- When dealing with external libraries, create proper type signatures or use extension methods rather than resorting to `T.unsafe`.
103-
- **NEVER use `class.name`** anywhere - this is a Sorbet quirk that hides the `name` method from all base classes. Prefer just using Classes themselves as the type. `"#{class}"` will automatically call `.to_s`. Similarly, `to_json` will automatically call `.to_s` - but you can call `.to_s` manually if you really need it.
102+
- When dealing with external libraries, create proper type signatures or use extension methods; keep `T.unsafe` usage small and justified.
103+
- Avoid `class.name` where possible due to Sorbet quirks; prefer string interpolation (`"#{klass}"`) or use the class as a value.
104104

105105
### Testing
106106

Gemfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ GEM
213213
public_suffix (6.0.1)
214214
racc (1.8.1)
215215
rack (3.1.11)
216-
rack-session (2.1.0)
216+
rack-session (2.1.1)
217217
base64 (>= 0.1.0)
218218
rack (>= 3.0.0)
219219
rack-test (2.2.0)

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# LogStruct
22

3-
Adds JSON structured logging to any Rails app. Simply add the gem to your Gemfile and add an initializer to configure it. Now your Rails app prints beautiful JSON logs to STDOUT. They're easy to search and filter, you can turn them into metrics and alerts, and they're great for building dashboards in CloudWatch, Grafana, or Datadog.
3+
Adds JSON structured logging to any Rails app. Simply add the gem to your Gemfile and add an initializer to configure it. By default, your Rails app prints JSON logs to STDOUT (or to the configured destination when `RAILS_LOG_TO_STDOUT` is set). They're easy to search and filter, you can turn them into metrics and alerts, and they're great for building dashboards in CloudWatch, Grafana, or Datadog.
44

55
We support all your other favorite gems too, like Sidekiq, Sentry, and Shrine. (And if not, please open a PR!)
66

@@ -14,7 +14,7 @@ We support all your other favorite gems too, like Sidekiq, Sentry, and Shrine. (
1414
- Error handling and reporting
1515
- Metadata collection for rich context
1616
- Lograge integration for structured request logging
17-
- Sensitive data scrubbing with Logstop (vendored fork)
17+
- Sensitive data scrubbing for strings (inspired by the Logstop gem)
1818
- Host authorization logging for security violations
1919
- Rack middleware for enhanced error logging
2020
- ActionMailer delivery callbacks for Rails 7.0.x (backported from Rails 7.1)
@@ -46,7 +46,7 @@ LogStruct is designed to be highly opinionated and work out of the box with mini
4646

4747
Please see the [documentation](https://logstruct.com/docs/configuration/) for configuration options.
4848

49-
### Important Note on Integration
49+
### Important Notes on Integration
5050

5151
Once initialized, the gem automatically includes its modules into the appropriate base classes:
5252

@@ -55,6 +55,8 @@ Once initialized, the gem automatically includes its modules into the appropriat
5555
- We configure `Lograge` for request logging
5656
- A Rack middleware is inserted to catch and log errors, including security violations (IP spoofing, CSRF, blocked hosts, etc.)
5757
- Structured logging is set up for ActiveJob, Sidekiq, Shrine, etc.
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+
- 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.
5860

5961
## Documentation
6062

lib/log_struct/concerns/configuration.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,14 @@ def set_enabled_from_rails_env!
4242
# Set enabled based on current Rails environment and the LOGSTRUCT_ENABLED env var.
4343
# Precedence:
4444
# 1. Check if LOGSTRUCT_ENABLED env var is defined
45-
# 2. Check if current Rails environment is in enabled_environments
46-
# 3. Default to whatever is set in config.enabled (which defaults to true)
45+
# - Sets enabled=true only when value is "true"
46+
# - Sets enabled=false when value is "false" (or any non-"true")
47+
# 2. Otherwise, check if current Rails environment is in enabled_environments
48+
# 3. Otherwise, leave as config.enabled (defaults to true)
4749

4850
# Then check if LOGSTRUCT_ENABLED env var is set
4951
config.enabled = if ENV["LOGSTRUCT_ENABLED"]
50-
# Only override if env var is "true"
52+
# Override to true only if env var is "true"
5153
ENV["LOGSTRUCT_ENABLED"] == "true"
5254
else
5355
config.enabled_environments.include?(::Rails.env.to_sym)

lib/log_struct/formatter.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,8 @@ def process_values(arg, recursion_depth: 0)
6161

6262
# Process each key-value pair
6363
arg.each do |key, value|
64-
# Only filter keys in nested structures (recursion_depth >= 1)
65-
# Check if this key should be filtered
66-
result[key] = if recursion_depth >= 1 && ParamFilters.should_filter_key?(key)
64+
# Check if this key should be filtered at any depth
65+
result[key] = if ParamFilters.should_filter_key?(key)
6766
# Filter the value
6867
{_filtered: ParamFilters.summarize_json_attribute(key, value)}
6968
else

lib/log_struct/log/sql.rb

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,19 @@ def serialize(strict = true)
105105
hash = serialize_common(strict)
106106
merge_additional_data_fields(hash)
107107

108-
# Add SQL-specific fields
109-
hash[:message] = message
110-
hash[:sql] = sql
111-
hash[:name] = name
112-
hash[:duration] = duration
113-
hash[:row_count] = row_count
114-
hash[:connection_adapter] = connection_adapter
115-
hash[:bind_params] = bind_params
116-
hash[:database_name] = database_name
117-
hash[:connection_pool_size] = connection_pool_size
118-
hash[:active_connections] = active_connections
119-
hash[:operation_type] = operation_type
120-
hash[:table_names] = table_names
108+
# Add SQL-specific fields using LOG_KEYS mapping for consistency
109+
hash[LOG_KEYS.fetch(:message)] = message
110+
hash[LOG_KEYS.fetch(:sql)] = sql
111+
hash[LOG_KEYS.fetch(:name)] = name
112+
hash[LOG_KEYS.fetch(:duration)] = duration
113+
hash[LOG_KEYS.fetch(:row_count)] = row_count
114+
hash[LOG_KEYS.fetch(:connection_adapter)] = connection_adapter
115+
hash[LOG_KEYS.fetch(:bind_params)] = bind_params
116+
hash[LOG_KEYS.fetch(:database_name)] = database_name
117+
hash[LOG_KEYS.fetch(:connection_pool_size)] = connection_pool_size
118+
hash[LOG_KEYS.fetch(:active_connections)] = active_connections
119+
hash[LOG_KEYS.fetch(:operation_type)] = operation_type
120+
hash[LOG_KEYS.fetch(:table_names)] = table_names
121121

122122
hash
123123
end

lib/log_struct/log_keys.rb

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,19 @@ module LogStruct
8484

8585
# CarrierWave-specific fields
8686
model: :model,
87-
mount_point: :mount_point
87+
mount_point: :mount_point,
88+
89+
# SQL-specific fields
90+
sql: :sql,
91+
name: :name,
92+
row_count: :row_count,
93+
connection_adapter: :connection_adapter,
94+
bind_params: :bind_params,
95+
database_name: :database_name,
96+
connection_pool_size: :connection_pool_size,
97+
active_connections: :active_connections,
98+
operation_type: :operation_type,
99+
table_names: :table_names
88100
}.freeze,
89101
T::Hash[Symbol, Symbol])
90102
end

lib/log_struct/semantic_logger/logger.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def tagged(*tags, &block)
9696
if tag_array.empty?
9797
super(&block)
9898
else
99-
super(T.unsafe(tag_array), &block)
99+
super(*T.unsafe(tag_array), &block)
100100
end
101101
end
102102

@@ -113,9 +113,11 @@ def clear_tags!
113113
::SemanticLogger.pop_tags(count) if count > 0
114114
end
115115

116-
sig { params(tags: T.untyped).void }
116+
sig { params(tags: T.untyped).returns(T::Array[T.untyped]) }
117117
def push_tags(*tags)
118-
tags.flatten.each { |tag| ::SemanticLogger.push_tags(tag) }
118+
flat = tags.flatten.compact
119+
flat.each { |tag| ::SemanticLogger.push_tags(tag) }
120+
flat
119121
end
120122

121123
sig { params(count: Integer).void }

lib/log_struct/semantic_logger/setup.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ def self.add_appenders(app)
137137
)
138138
end
139139

140-
# Add file appender if configured
141-
if app.config.paths["log"].first && !ENV["RAILS_LOG_TO_STDOUT"]
140+
# Add file appender if configured and not already logging to STDOUT/StringIO
141+
if app.config.paths["log"].first && io != $stdout && !io.is_a?(StringIO)
142142
::SemanticLogger.add_appender(
143143
file_name: app.config.paths["log"].first,
144144
formatter: LogStruct::SemanticLogger::Formatter.new,
@@ -155,6 +155,7 @@ def self.determine_output(app)
155155
# Use StringIO for tests to avoid cluttering test output
156156
StringIO.new
157157
else
158+
# Prefer file logging when not explicitly configured for STDOUT
158159
$stdout
159160
end
160161
end

0 commit comments

Comments
 (0)