activesupport-json_logging 1.2.2 → 1.3.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 +20 -0
- data/README.md +69 -13
- data/lib/activesupport/json_logging/railtie.rb +11 -0
- data/lib/json_logging/event_subscriber.rb +150 -0
- data/lib/json_logging/json_logger.rb +1 -3
- data/lib/json_logging/json_logger_extension.rb +2 -2
- data/lib/json_logging/line_encoder.rb +0 -8
- data/lib/json_logging/message_parser.rb +2 -0
- data/lib/json_logging/sanitizer.rb +27 -36
- data/lib/json_logging/sanitizer_hash_limit.rb +42 -0
- data/lib/json_logging/structured_hash_json_encoder.rb +1 -36
- data/lib/json_logging/structured_hash_sanitizer.rb +7 -16
- data/lib/json_logging/version.rb +1 -1
- data/lib/json_logging.rb +1 -0
- data/sig/json_logging.rbs +5 -0
- metadata +24 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8860a79aadc768a5173400d8c56041edde0fcd66b9e3f2f50e4918956c31b451
|
|
4
|
+
data.tar.gz: 07420fcc1f8aa91271ebf0484a338b921dba8fd41c16e2e50adf789a9eaaed33
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df9baad44f3b33dce3e18b795691fd6e7d8d620930826d2d9e423808b266b294b6ab3355619df9a2a406d51c7c0d3338be984599871579fbc67b69c5fc198a42
|
|
7
|
+
data.tar.gz: c93486338ce6906fb91bd96b55732a67525883971d7b7551690b34a4280337482160c096459b63623f0c2e629cb41b6af028fa008b274fc756137e5f21fc9958
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 1.3.1 (2026-09-04)
|
|
6
|
+
|
|
7
|
+
- Copy large structured hashes before Rails parameter filtering so caller fields stay intact
|
|
8
|
+
- Keep original key names on the large-hash encoder path when a Rails parameter filter is present
|
|
9
|
+
- Isolate nested hashes in `with_context` from later caller mutations
|
|
10
|
+
- Match fallback sensitive-key detection to Rails default filter substrings including email, otp, ssn, token, and credentials
|
|
11
|
+
- Rebuild the compiled Rails parameter filter after in-place `filter_parameters` changes
|
|
12
|
+
- Treat JSON object and array strings longer than 10,000 characters as truncated text
|
|
13
|
+
- Replace home-directory prefixes in log strings and backtraces with a tilde
|
|
14
|
+
- Skip EventSubscriber writes when the logger has INFO disabled; keep the Rails event timestamp as an integer
|
|
15
|
+
- Keep overflow sensitive keys as filtered after the 50-key hash cap without copying their values
|
|
16
|
+
- Require json 2.21.2 or newer
|
|
17
|
+
|
|
18
|
+
## 1.3.0 (2026-07-25)
|
|
19
|
+
|
|
20
|
+
- BREAKING: Require Ruby 3.2+ and Rails 7+ (drop Ruby 2.7–3.1 and Rails 6 support)
|
|
21
|
+
- Add opt-in `JsonLogging::EventSubscriber` for Rails 8.1 `ActiveSupport::EventReporter` / `Rails.event` structured events
|
|
22
|
+
|
|
3
23
|
## 1.2.3 (2026-07-13)
|
|
4
24
|
|
|
5
25
|
- Reduce per-line work for plain string, hash, tagged, and context-scoped log messages on common shapes
|
data/README.md
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
# activesupport-json_logging
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/activesupport-json_logging) [](https://github.com/amkisko/activesupport-json_logging.rb/actions/workflows/test.yml) [](https://codecov.io/
|
|
3
|
+
[](https://badge.fury.io/rb/activesupport-json_logging) [](https://github.com/amkisko/activesupport-json_logging.rb/actions/workflows/test.yml) [](https://app.codecov.io/github/amkisko/activesupport-json_logging.rb) [](https://sonarcloud.io/project/overview?id=amkisko_activesupport-json_logging.rb)
|
|
4
4
|
|
|
5
5
|
Structured JSON logging for Rails and ActiveSupport with a safe, single-line formatter.
|
|
6
6
|
No dependencies beyond Rails and Activesupport.
|
|
7
|
-
Supports
|
|
8
|
-
|
|
9
|
-
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
10
|
-
|
|
11
|
-
<a href="https://www.kiskolabs.com">
|
|
12
|
-
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
13
|
-
</a>
|
|
14
|
-
|
|
7
|
+
Supports Ruby 3.2+ and Rails 7–8.
|
|
15
8
|
|
|
16
9
|
## Installation
|
|
17
10
|
|
|
@@ -46,6 +39,7 @@ end
|
|
|
46
39
|
- Native `tagged` method support - use it just like Rails' tagged logger
|
|
47
40
|
- Service-specific tagged loggers - create loggers with permanent tags using `logger.tagged("service")` without a block
|
|
48
41
|
- Full compatibility with `ActiveSupport::BroadcastLogger` (Rails 7.1+)
|
|
42
|
+
- Opt-in `JsonLogging::EventSubscriber` for Rails 8.1 `ActiveSupport::EventReporter` / `Rails.event`
|
|
49
43
|
- Automatic Rails integration via Railtie (auto-requires the gem in Rails apps)
|
|
50
44
|
|
|
51
45
|
## Basic usage
|
|
@@ -132,6 +126,37 @@ sidekiq_logger.info("Job enqueued") # Tagged with "sidekiq"
|
|
|
132
126
|
api_logger.info("Request received") # Tagged with "api"
|
|
133
127
|
```
|
|
134
128
|
|
|
129
|
+
### Rails 8.1 Event Reporter
|
|
130
|
+
|
|
131
|
+
Rails 8.1 adds `Rails.event` (`ActiveSupport::EventReporter`) for structured events that complement `Rails.logger`. Setting `config.logger` to a `JsonLogging` logger does not subscribe you to events. Register a subscriber:
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
# config/initializers/json_logging.rb
|
|
135
|
+
subscriber = JsonLogging::EventSubscriber.new(logger: -> { Rails.logger })
|
|
136
|
+
Rails.event.subscribe(subscriber)
|
|
137
|
+
|
|
138
|
+
# Optional: only a subset of events
|
|
139
|
+
Rails.event.subscribe(subscriber) { |event| event[:name].start_with?("app.") }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Pass either `logger:` or `io:`, not both. A callable `logger:` is resolved on each emit (the Railtie opt-in uses this so a later `Rails.logger=` swap is picked up).
|
|
143
|
+
|
|
144
|
+
Or enable the Railtie opt-in (off by default):
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# config/application.rb or an environment file
|
|
148
|
+
config.json_logging.subscribe_event_reporter = true
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
The subscriber writes one JSON line per event and keeps the Rails event shape (`name`, `payload`, `tags`, `context`, `timestamp`, `source_location`). It writes through `Logger#<<` so the logger formatter does not wrap the event again. It skips that write when the logger reports that INFO is disabled (`logger.level` above INFO). An IO destination always writes. `timestamp` stays the Rails event integer (nanoseconds), not the ISO8601 string used by `JsonLoggerExtension`. Payload and tag objects that implement `#serialize` are encoded via that method. Encode and write failures never raise from `#emit`.
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
Rails.event.set_context(request_id: "abc123")
|
|
155
|
+
Rails.event.tagged("billing") do
|
|
156
|
+
Rails.event.notify("invoice.created", invoice_id: 3)
|
|
157
|
+
end
|
|
158
|
+
```
|
|
159
|
+
|
|
135
160
|
### BroadcastLogger integration
|
|
136
161
|
|
|
137
162
|
`ActiveSupport::BroadcastLogger` (Rails 7.1+) allows writing logs to multiple destinations simultaneously. `JsonLogging` works seamlessly with `BroadcastLogger`:
|
|
@@ -460,6 +485,16 @@ formatter.call("INFO", Time.now, nil, "message") # Output includes both tags
|
|
|
460
485
|
|
|
461
486
|
**Note:** When used with a logger (via `JsonLogging.new`), the logger uses `FormatterWithTags` which automatically includes tags from the logger's tagged context. Use `Formatter` directly only when you need a standalone formatter without a logger instance.
|
|
462
487
|
|
|
488
|
+
### JsonLogging::EventSubscriber
|
|
489
|
+
|
|
490
|
+
Rails 8.1+ subscriber for `ActiveSupport::EventReporter`. See [Rails 8.1 Event Reporter](#rails-81-event-reporter).
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
subscriber = JsonLogging::EventSubscriber.new(logger: -> { Rails.logger })
|
|
494
|
+
# or: JsonLogging::EventSubscriber.new(io: $stdout)
|
|
495
|
+
Rails.event.subscribe(subscriber)
|
|
496
|
+
```
|
|
497
|
+
|
|
463
498
|
### JsonLogging.with_context
|
|
464
499
|
|
|
465
500
|
Add thread-local context that appears in all log entries within the block:
|
|
@@ -554,7 +589,7 @@ This is useful for:
|
|
|
554
589
|
- Temporary verbose logging in background jobs
|
|
555
590
|
- Per-request log level changes
|
|
556
591
|
|
|
557
|
-
**Note:** `local_level` is available in Rails 7.1+. In Rails
|
|
592
|
+
**Note:** `local_level` is available in Rails 7.1+. In Rails 7.0, only the global `level` is available.
|
|
558
593
|
|
|
559
594
|
#### Standard Logger Methods
|
|
560
595
|
|
|
@@ -581,9 +616,9 @@ logger.reopen # Reopen the logger (if supported by logdev)
|
|
|
581
616
|
## Security & privacy
|
|
582
617
|
|
|
583
618
|
- **Rails ParameterFilter integration**: Automatically uses `Rails.application.config.filter_parameters` to filter sensitive data (passwords, tokens, etc.). This includes encrypted attributes automatically. See [Rails parameter filtering guide](https://thoughtbot.com/blog/parameter-filtering).
|
|
584
|
-
- **Input sanitization**: Removes control characters, truncates long strings, and limits structure depth/size:
|
|
619
|
+
- **Input sanitization**: Removes control characters, truncates long strings, redacts home-directory prefixes to `~`, and limits structure depth/size:
|
|
585
620
|
- Maximum string length: 10,000 characters (truncated with `...[truncated]` suffix)
|
|
586
|
-
- Maximum context hash size: 50 keys (additional keys are truncated)
|
|
621
|
+
- Maximum context hash size: 50 keys (additional keys are truncated; overflow keys that match sensitive patterns or `filter_parameters` still appear as `[FILTERED]`)
|
|
587
622
|
- Maximum nesting depth: 10 levels (deeper structures return `{"error" => "max_depth_exceeded"}`)
|
|
588
623
|
- Maximum backtrace lines: 20 lines per exception
|
|
589
624
|
- **Single-line JSON**: Emits single-line JSON to avoid log injection via newlines
|
|
@@ -597,7 +632,7 @@ This gem automatically uses Rails' `config.filter_parameters` when available. Co
|
|
|
597
632
|
|
|
598
633
|
```ruby
|
|
599
634
|
Rails.application.config.filter_parameters += [
|
|
600
|
-
:passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
|
|
635
|
+
:passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
|
|
601
636
|
]
|
|
602
637
|
```
|
|
603
638
|
|
|
@@ -652,7 +687,28 @@ gem build activesupport-json_logging.gemspec
|
|
|
652
687
|
gem push activesupport-json_logging-*.gem
|
|
653
688
|
```
|
|
654
689
|
|
|
690
|
+
## Links
|
|
691
|
+
|
|
692
|
+
- [GitHub](https://github.com/amkisko/activesupport-json_logging.rb)
|
|
693
|
+
- [GitLab](https://gitlab.com/amkisko/activesupport-json_logging.rb)
|
|
694
|
+
- [RubyGems](https://rubygems.org/gems/activesupport-json_logging)
|
|
695
|
+
- [Versions Atom](https://rubygems.org/gems/activesupport-json_logging/versions.atom) (feed id `9801944970071`)
|
|
696
|
+
- [libraries.io](https://libraries.io/rubygems/activesupport-json_logging)
|
|
697
|
+
- [Deps.dev](https://deps.dev/rubygems/activesupport-json_logging)
|
|
698
|
+
- [SonarCloud](https://sonarcloud.io/project/overview?id=amkisko_activesupport-json_logging.rb)
|
|
699
|
+
- [Snyk](https://snyk.io/test/github/amkisko/activesupport-json_logging.rb)
|
|
700
|
+
- [Codecov](https://app.codecov.io/github/amkisko/activesupport-json_logging.rb)
|
|
701
|
+
- [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/amkisko/activesupport-json_logging.rb)
|
|
702
|
+
|
|
655
703
|
## License
|
|
656
704
|
|
|
657
705
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
658
706
|
|
|
707
|
+
## Sponsors
|
|
708
|
+
|
|
709
|
+
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
710
|
+
|
|
711
|
+
<a href="https://www.kiskolabs.com">
|
|
712
|
+
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
713
|
+
</a>
|
|
714
|
+
|
|
@@ -2,7 +2,18 @@ require "json_logging"
|
|
|
2
2
|
|
|
3
3
|
module JsonLogging
|
|
4
4
|
class Railtie < ::Rails::Railtie
|
|
5
|
+
config.json_logging = ActiveSupport::OrderedOptions.new
|
|
6
|
+
config.json_logging.subscribe_event_reporter = false
|
|
7
|
+
|
|
5
8
|
# This Railtie ensures json_logging is automatically required when Rails loads
|
|
6
9
|
# Without this, users would need to manually require "json_logging" in initializers
|
|
10
|
+
|
|
11
|
+
initializer "json_logging.subscribe_event_reporter", after: :load_config_initializers do |app|
|
|
12
|
+
next unless app.config.json_logging.subscribe_event_reporter
|
|
13
|
+
next unless defined?(ActiveSupport::EventReporter)
|
|
14
|
+
next unless Rails.respond_to?(:event)
|
|
15
|
+
|
|
16
|
+
Rails.event.subscribe(JsonLogging::EventSubscriber.new(logger: -> { Rails.logger }))
|
|
17
|
+
end
|
|
7
18
|
end
|
|
8
19
|
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
module JsonLogging
|
|
2
|
+
# Opt-in subscriber for ActiveSupport::EventReporter (Rails 8.1+).
|
|
3
|
+
# Preserves the Rails event hash shape as a single JSON line.
|
|
4
|
+
class EventSubscriber
|
|
5
|
+
def initialize(logger: nil, io: nil)
|
|
6
|
+
if !logger.nil? && !io.nil?
|
|
7
|
+
raise ArgumentError, "JsonLogging::EventSubscriber accepts only one of logger: or io:"
|
|
8
|
+
end
|
|
9
|
+
if logger.nil? && io.nil?
|
|
10
|
+
raise ArgumentError, "JsonLogging::EventSubscriber requires logger: or io:"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
@logger = logger
|
|
14
|
+
@io = io
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def emit(event)
|
|
18
|
+
line = begin
|
|
19
|
+
encode_event(event)
|
|
20
|
+
rescue => error
|
|
21
|
+
encode_fallback(event, error)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
write_line(line)
|
|
25
|
+
rescue => error
|
|
26
|
+
report_write_error(error)
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def encode_event(event)
|
|
33
|
+
payload = {
|
|
34
|
+
"name" => Helpers.safe_string(event_field(event, :name)),
|
|
35
|
+
"payload" => serialize_payload(event_field(event, :payload)),
|
|
36
|
+
"tags" => serialize_tags(event_field(event, :tags)),
|
|
37
|
+
"context" => hash_or_empty(event_field(event, :context)),
|
|
38
|
+
"timestamp" => event_field(event, :timestamp)
|
|
39
|
+
}
|
|
40
|
+
source_location = event_field(event, :source_location)
|
|
41
|
+
payload["source_location"] = hash_or_empty(source_location) if source_location
|
|
42
|
+
|
|
43
|
+
LineEncoder.to_json_line(Sanitizer.sanitize_hash(payload))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def encode_fallback(event, error)
|
|
47
|
+
event_name = begin
|
|
48
|
+
Helpers.safe_string(event && event_field(event, :name))
|
|
49
|
+
rescue
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
LineEncoder.to_json_line(
|
|
54
|
+
Sanitizer.sanitize_hash(
|
|
55
|
+
{
|
|
56
|
+
"name" => "json_logging.event_encode_error",
|
|
57
|
+
"event_name" => event_name,
|
|
58
|
+
"error" => {
|
|
59
|
+
"class" => error.class.name,
|
|
60
|
+
"message" => Helpers.safe_string(error.message)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def event_field(event, key)
|
|
68
|
+
return nil unless event.is_a?(Hash)
|
|
69
|
+
|
|
70
|
+
if event.key?(key)
|
|
71
|
+
event[key]
|
|
72
|
+
elsif event.key?(key.to_s)
|
|
73
|
+
event[key.to_s]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def serialize_payload(payload)
|
|
78
|
+
case payload
|
|
79
|
+
when Hash
|
|
80
|
+
payload
|
|
81
|
+
when nil
|
|
82
|
+
{}
|
|
83
|
+
else
|
|
84
|
+
serialize_object(payload)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def serialize_tags(tags)
|
|
89
|
+
return {} unless tags.is_a?(Hash)
|
|
90
|
+
|
|
91
|
+
tags.transform_values { |value| serialize_tag_value(value) }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def serialize_tag_value(value)
|
|
95
|
+
case value
|
|
96
|
+
when Hash, TrueClass, FalseClass, Numeric, NilClass, String
|
|
97
|
+
value
|
|
98
|
+
else
|
|
99
|
+
serialize_object(value)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def serialize_object(object)
|
|
104
|
+
if object.respond_to?(:serialize)
|
|
105
|
+
serialized = object.serialize
|
|
106
|
+
serialized.is_a?(Hash) ? serialized : {"value" => serialized}
|
|
107
|
+
elsif object.respond_to?(:to_h)
|
|
108
|
+
object.to_h
|
|
109
|
+
else
|
|
110
|
+
{
|
|
111
|
+
"class" => object.class.name,
|
|
112
|
+
"value" => Helpers.safe_string(object)
|
|
113
|
+
}
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def hash_or_empty(value)
|
|
118
|
+
value.is_a?(Hash) ? value : {}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def write_line(line)
|
|
122
|
+
if @io
|
|
123
|
+
@io.write(line)
|
|
124
|
+
return
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
logger = resolve_logger
|
|
128
|
+
return if logger.respond_to?(:info?) && !logger.info?
|
|
129
|
+
|
|
130
|
+
logger << line
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def resolve_logger
|
|
134
|
+
logger = @logger
|
|
135
|
+
# Proc responds to << (composition); Logger responds to add.
|
|
136
|
+
return logger.call if logger.respond_to?(:call) && !logger.respond_to?(:add)
|
|
137
|
+
|
|
138
|
+
logger
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def report_write_error(error)
|
|
142
|
+
return unless defined?(ActiveSupport) && ActiveSupport.respond_to?(:error_reporter)
|
|
143
|
+
return if ActiveSupport.error_reporter.nil?
|
|
144
|
+
|
|
145
|
+
ActiveSupport.error_reporter.report(error, handled: true, severity: :error)
|
|
146
|
+
rescue
|
|
147
|
+
nil
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -8,18 +8,16 @@ module JsonLogging
|
|
|
8
8
|
|
|
9
9
|
def initialize(*args, **kwargs)
|
|
10
10
|
# Initialize with minimal args to avoid ActiveSupport::Logger threading issues
|
|
11
|
-
# Rails 7+ supports kwargs, Rails 6 uses positional args only
|
|
12
11
|
logdev = args.first || $stdout
|
|
13
12
|
shift_age = args[1] || 0
|
|
14
13
|
shift_size = args[2]
|
|
15
14
|
|
|
16
|
-
# Handle both positional and keyword arguments for Rails
|
|
15
|
+
# Handle both positional and keyword arguments for Rails 7–8 compatibility
|
|
17
16
|
if kwargs.empty? && shift_size
|
|
18
17
|
super(logdev, shift_age, shift_size)
|
|
19
18
|
elsif kwargs.empty?
|
|
20
19
|
super(logdev, shift_age)
|
|
21
20
|
else
|
|
22
|
-
# Rails 7+ may pass kwargs - delegate to parent
|
|
23
21
|
super
|
|
24
22
|
end
|
|
25
23
|
|
|
@@ -95,7 +95,7 @@ module JsonLogging
|
|
|
95
95
|
|
|
96
96
|
def current_tags
|
|
97
97
|
# Use IsolatedExecutionState (Rails 7.1+) for better thread/Fiber safety
|
|
98
|
-
# Falls back to Thread.current for Rails
|
|
98
|
+
# Falls back to Thread.current for Rails 7.0
|
|
99
99
|
if defined?(ActiveSupport::IsolatedExecutionState)
|
|
100
100
|
ActiveSupport::IsolatedExecutionState[tags_key] ||= []
|
|
101
101
|
else
|
|
@@ -105,7 +105,7 @@ module JsonLogging
|
|
|
105
105
|
|
|
106
106
|
def set_tags(new_tags)
|
|
107
107
|
# Use IsolatedExecutionState (Rails 7.1+) for better thread/Fiber safety
|
|
108
|
-
# Falls back to Thread.current for Rails
|
|
108
|
+
# Falls back to Thread.current for Rails 7.0
|
|
109
109
|
if defined?(ActiveSupport::IsolatedExecutionState)
|
|
110
110
|
ActiveSupport::IsolatedExecutionState[tags_key] = new_tags
|
|
111
111
|
else
|
|
@@ -223,14 +223,6 @@ module JsonLogging
|
|
|
223
223
|
"#{JSON.generate(payload)}\n"
|
|
224
224
|
end
|
|
225
225
|
|
|
226
|
-
def build_sanitized_hash_payload(message:, severity:, timestamp:)
|
|
227
|
-
hash = message.is_a?(Hash) ? message : message.to_hash
|
|
228
|
-
payload = Sanitizer.sanitize_hash(hash)
|
|
229
|
-
payload["severity"] = severity
|
|
230
|
-
payload["timestamp"] = timestamp
|
|
231
|
-
payload
|
|
232
|
-
end
|
|
233
|
-
|
|
234
226
|
def deep_stringify_structure(obj)
|
|
235
227
|
case obj
|
|
236
228
|
when Hash
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require_relative "structured_hash_sanitizer"
|
|
2
|
+
require_relative "sanitizer_hash_limit"
|
|
2
3
|
|
|
3
4
|
module JsonLogging
|
|
4
5
|
module Sanitizer
|
|
@@ -18,7 +19,7 @@ module JsonLogging
|
|
|
18
19
|
MAX_BACKTRACE_LINES = 20
|
|
19
20
|
|
|
20
21
|
# Common sensitive key patterns (case insensitive) - fallback when Rails ParameterFilter not available
|
|
21
|
-
SENSITIVE_KEY_PATTERNS =
|
|
22
|
+
SENSITIVE_KEY_PATTERNS = /(passw|pwd|email|secret|token|_key|crypt|salt|certificate|otp|ssn|cvv|cvc|credential)/i
|
|
22
23
|
|
|
23
24
|
@parameter_filter = nil
|
|
24
25
|
@parameter_filter_config = nil
|
|
@@ -38,7 +39,7 @@ module JsonLogging
|
|
|
38
39
|
return @parameter_filter
|
|
39
40
|
end
|
|
40
41
|
|
|
41
|
-
@parameter_filter_config = filter_params
|
|
42
|
+
@parameter_filter_config = filter_params.dup.freeze
|
|
42
43
|
@parameter_filter_requires_full_tree_walk = parameter_filter_requires_full_tree_walk?(filter_params)
|
|
43
44
|
@parameter_filter = ActiveSupport::ParameterFilter.new(filter_params)
|
|
44
45
|
rescue
|
|
@@ -76,12 +77,14 @@ module JsonLogging
|
|
|
76
77
|
# Sanitize a string by removing/escaping control characters and truncating
|
|
77
78
|
def sanitize_string(str)
|
|
78
79
|
return str unless str.is_a?(String)
|
|
79
|
-
return str if str.length <= MAX_STRING_LENGTH && !str.match?(CONTROL_CHARS)
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
home_prefix = home_directory_prefix
|
|
82
|
+
has_home_directory = home_prefix && str.include?(home_prefix)
|
|
83
|
+
return str if str.length <= MAX_STRING_LENGTH && !str.match?(CONTROL_CHARS) && !has_home_directory
|
|
84
|
+
|
|
82
85
|
sanitized = str.gsub(CONTROL_CHARS, "")
|
|
86
|
+
sanitized = redact_home_directory(sanitized, home_prefix) if has_home_directory
|
|
83
87
|
|
|
84
|
-
# Truncate if too long
|
|
85
88
|
if sanitized.length > MAX_STRING_LENGTH
|
|
86
89
|
sanitized = sanitized[0, MAX_STRING_LENGTH] + "...[truncated]"
|
|
87
90
|
end
|
|
@@ -91,12 +94,8 @@ module JsonLogging
|
|
|
91
94
|
"<sanitization_error>"
|
|
92
95
|
end
|
|
93
96
|
|
|
94
|
-
# Sanitize a hash, removing sensitive keys and limiting size/depth
|
|
95
|
-
# Uses Rails ParameterFilter when available, falls back to pattern matching
|
|
96
97
|
def sanitize_hash(hash, depth: 0)
|
|
97
98
|
return hash unless hash.is_a?(Hash)
|
|
98
|
-
|
|
99
|
-
# Prevent excessive nesting
|
|
100
99
|
return {"error" => "max_depth_exceeded"} if depth > MAX_DEPTH
|
|
101
100
|
|
|
102
101
|
limited_hash = limited_hash_for_sanitization(hash)
|
|
@@ -109,13 +108,7 @@ module JsonLogging
|
|
|
109
108
|
end
|
|
110
109
|
|
|
111
110
|
def limited_hash_for_sanitization(hash)
|
|
112
|
-
|
|
113
|
-
truncated = hash.first(MAX_CONTEXT_SIZE).to_h
|
|
114
|
-
truncated["_truncated"] = true
|
|
115
|
-
truncated
|
|
116
|
-
else
|
|
117
|
-
hash
|
|
118
|
-
end
|
|
111
|
+
SanitizerHashLimit.apply(hash, filter_config: rails_parameter_filter && @parameter_filter_config)
|
|
119
112
|
end
|
|
120
113
|
|
|
121
114
|
def fast_path_sanitized_hash(hash, depth: 0)
|
|
@@ -126,38 +119,26 @@ module JsonLogging
|
|
|
126
119
|
end
|
|
127
120
|
|
|
128
121
|
def sanitize_hash_with_filtering(limited_hash, depth: 0)
|
|
129
|
-
# Use Rails ParameterFilter if available (handles encrypted attributes automatically)
|
|
130
122
|
filter = rails_parameter_filter
|
|
131
123
|
if filter
|
|
132
|
-
# ParameterFilter
|
|
133
|
-
# This includes encrypted attributes automatically
|
|
134
|
-
# Create a deep copy since filter modifies in place (Rails 6+)
|
|
124
|
+
# ParameterFilter mutates in place; copy first
|
|
135
125
|
filtered = limited_hash.respond_to?(:deep_dup) ? limited_hash.deep_dup : limited_hash.dup
|
|
136
126
|
filtered = filter.filter(filtered)
|
|
137
|
-
|
|
138
|
-
# Then sanitize values (strings, control chars, etc.) preserving filtered structure
|
|
139
127
|
filtered.each_with_object({}) do |(key, value), result|
|
|
140
128
|
result[key.to_s] = sanitize_value(value, depth: depth + 1)
|
|
141
129
|
end
|
|
142
|
-
|
|
143
130
|
else
|
|
144
|
-
# Fallback: use pattern matching for sensitive keys
|
|
145
131
|
limited_hash.each_with_object({}) do |(key, value), result|
|
|
146
132
|
key_string = key.to_s
|
|
147
|
-
|
|
148
|
-
# Skip sensitive keys
|
|
149
133
|
if SENSITIVE_KEY_PATTERNS.match?(key_string)
|
|
150
134
|
result[sensitive_filtered_key_name(key_string)] = "[FILTERED]"
|
|
151
135
|
next
|
|
152
136
|
end
|
|
153
|
-
|
|
154
137
|
result[key_string] = sanitize_value(value, depth: depth + 1)
|
|
155
138
|
end
|
|
156
139
|
end
|
|
157
140
|
end
|
|
158
141
|
|
|
159
|
-
# Sanitize a value (handles strings, hashes, arrays, etc.)
|
|
160
|
-
# Preserves numeric, boolean, and nil types
|
|
161
142
|
def sanitize_value(value, depth: 0)
|
|
162
143
|
case value
|
|
163
144
|
when String
|
|
@@ -165,24 +146,20 @@ module JsonLogging
|
|
|
165
146
|
when Hash
|
|
166
147
|
sanitize_hash(value, depth: depth)
|
|
167
148
|
when Array
|
|
168
|
-
# Limit array size
|
|
169
149
|
sanitized = value.first(MAX_CONTEXT_SIZE).map { |v| sanitize_value(v, depth: depth + 1) }
|
|
170
150
|
sanitized << "[truncated]" if value.size > MAX_CONTEXT_SIZE
|
|
171
151
|
sanitized
|
|
172
152
|
when Exception
|
|
173
153
|
sanitize_exception(value)
|
|
174
154
|
when Numeric, TrueClass, FalseClass, NilClass
|
|
175
|
-
# Preserve numeric, boolean, and nil types
|
|
176
155
|
value
|
|
177
156
|
else
|
|
178
|
-
# For other types, convert to string and sanitize
|
|
179
157
|
sanitize_string(value.to_s)
|
|
180
158
|
end
|
|
181
159
|
rescue
|
|
182
160
|
"<unprintable>"
|
|
183
161
|
end
|
|
184
162
|
|
|
185
|
-
# Sanitize exception, including backtrace
|
|
186
163
|
def sanitize_exception(ex)
|
|
187
164
|
{
|
|
188
165
|
"error" => {
|
|
@@ -195,11 +172,9 @@ module JsonLogging
|
|
|
195
172
|
{"error" => {"class" => "Exception", "message" => "<sanitization_failed>"}}
|
|
196
173
|
end
|
|
197
174
|
|
|
198
|
-
# Sanitize backtrace - truncate and remove sensitive paths
|
|
199
175
|
def sanitize_backtrace(backtrace)
|
|
200
176
|
return [] unless backtrace.is_a?(Array)
|
|
201
177
|
|
|
202
|
-
# Take first MAX_BACKTRACE_LINES, sanitize each
|
|
203
178
|
backtrace.first(MAX_BACKTRACE_LINES).map do |line|
|
|
204
179
|
sanitize_string(line.to_s)
|
|
205
180
|
end
|
|
@@ -207,7 +182,6 @@ module JsonLogging
|
|
|
207
182
|
[]
|
|
208
183
|
end
|
|
209
184
|
|
|
210
|
-
# Check if a key looks sensitive
|
|
211
185
|
def sensitive_key?(key)
|
|
212
186
|
SENSITIVE_KEY_PATTERNS.match?(key.to_s)
|
|
213
187
|
end
|
|
@@ -255,5 +229,22 @@ module JsonLogging
|
|
|
255
229
|
def sensitive_filtered_key_name(key_string)
|
|
256
230
|
key_string.gsub(/(?<!^)(?=[A-Z])/, "_").downcase + "_filtered"
|
|
257
231
|
end
|
|
232
|
+
|
|
233
|
+
def home_directory_prefix
|
|
234
|
+
prefix = ENV["HOME"]
|
|
235
|
+
prefix = Dir.home if prefix.blank?
|
|
236
|
+
prefix = prefix.to_s.chomp("/").chomp("\\")
|
|
237
|
+
return if prefix.empty?
|
|
238
|
+
|
|
239
|
+
prefix
|
|
240
|
+
rescue ArgumentError, RuntimeError
|
|
241
|
+
nil
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def redact_home_directory(str, prefix)
|
|
245
|
+
return str unless prefix
|
|
246
|
+
|
|
247
|
+
str.gsub(/#{Regexp.escape(prefix)}(?=\/|\\|\z)/, "~")
|
|
248
|
+
end
|
|
258
249
|
end
|
|
259
250
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module JsonLogging
|
|
2
|
+
module SanitizerHashLimit
|
|
3
|
+
FILTERED = "[FILTERED]"
|
|
4
|
+
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def apply(hash, filter_config:)
|
|
8
|
+
return hash if hash.size <= Sanitizer::MAX_CONTEXT_SIZE
|
|
9
|
+
|
|
10
|
+
truncated = {}
|
|
11
|
+
kept = 0
|
|
12
|
+
extra_filtered = 0
|
|
13
|
+
hash.each do |key, value|
|
|
14
|
+
if kept < Sanitizer::MAX_CONTEXT_SIZE
|
|
15
|
+
truncated[key] = value
|
|
16
|
+
kept += 1
|
|
17
|
+
elsif extra_filtered < Sanitizer::MAX_CONTEXT_SIZE && key_requires_filtering?(key, filter_config)
|
|
18
|
+
truncated[key] = FILTERED
|
|
19
|
+
extra_filtered += 1
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
truncated["_truncated"] = true
|
|
23
|
+
truncated
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def key_requires_filtering?(key, filter_config)
|
|
27
|
+
return true if Sanitizer.sensitive_key?(key)
|
|
28
|
+
|
|
29
|
+
Array(filter_config).any? do |filter|
|
|
30
|
+
next false if filter.is_a?(Proc)
|
|
31
|
+
|
|
32
|
+
key_string = key.to_s
|
|
33
|
+
if filter.is_a?(Regexp)
|
|
34
|
+
filter.match?(key_string)
|
|
35
|
+
else
|
|
36
|
+
token = filter.to_s
|
|
37
|
+
!token.empty? && !token.include?(".") && key_string.match?(/#{Regexp.escape(token)}/i)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -6,35 +6,6 @@ module JsonLogging
|
|
|
6
6
|
|
|
7
7
|
module_function
|
|
8
8
|
|
|
9
|
-
def eligible?(hash)
|
|
10
|
-
return false if Sanitizer.primitive_log_hash?(hash)
|
|
11
|
-
return false unless Sanitizer::StructuredHash.structured_log_hash?(hash)
|
|
12
|
-
return false unless large_structured_hash?(hash)
|
|
13
|
-
|
|
14
|
-
filter = Sanitizer.rails_parameter_filter
|
|
15
|
-
return true unless filter
|
|
16
|
-
|
|
17
|
-
!Sanitizer.rails_parameter_filter_requires_full_tree_walk?
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def large_structured_hash?(hash, leaf_threshold: LEAF_THRESHOLD)
|
|
21
|
-
leaf_count = 0
|
|
22
|
-
walker = lambda do |value|
|
|
23
|
-
case value
|
|
24
|
-
when Hash
|
|
25
|
-
value.each_value { |entry| walker.call(entry) }
|
|
26
|
-
when Array
|
|
27
|
-
value.each { |entry| walker.call(entry) }
|
|
28
|
-
else
|
|
29
|
-
leaf_count += 1
|
|
30
|
-
throw(:enough, true) if leaf_count > leaf_threshold
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
catch(:enough) { walker.call(hash) }
|
|
35
|
-
leaf_count > leaf_threshold
|
|
36
|
-
end
|
|
37
|
-
|
|
38
9
|
def try_encode_line(hash, severity:, timestamp:, field_overrides: {})
|
|
39
10
|
tree = prepared_tree(hash, field_overrides: field_overrides)
|
|
40
11
|
return nil unless tree
|
|
@@ -88,13 +59,7 @@ module JsonLogging
|
|
|
88
59
|
return jsonable.tree unless filter
|
|
89
60
|
return jsonable.tree if Sanitizer.rails_parameter_filter_requires_full_tree_walk?
|
|
90
61
|
|
|
91
|
-
|
|
92
|
-
jsonable.tree
|
|
93
|
-
else
|
|
94
|
-
Sanitizer::StructuredHash.stringify_keys_copy(jsonable.tree)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
filter.filter(tree)
|
|
62
|
+
filter.filter(Sanitizer::StructuredHash.stringify(jsonable.tree))
|
|
98
63
|
end
|
|
99
64
|
|
|
100
65
|
def append_json_line(tree, severity:, timestamp:, field_overrides:)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module JsonLogging
|
|
2
2
|
module Sanitizer
|
|
3
3
|
module StructuredHash
|
|
4
|
-
JsonableResult = Struct.new(:tree, :owned, :leaf_count
|
|
4
|
+
JsonableResult = Struct.new(:tree, :owned, :leaf_count)
|
|
5
5
|
|
|
6
6
|
module_function
|
|
7
7
|
|
|
@@ -44,11 +44,7 @@ module JsonLogging
|
|
|
44
44
|
|
|
45
45
|
def sanitize_without_filter(hash, depth: 0)
|
|
46
46
|
jsonable = jsonable_tree(hash, depth: depth)
|
|
47
|
-
|
|
48
|
-
stringify(jsonable.tree)
|
|
49
|
-
else
|
|
50
|
-
jsonable.tree.dup
|
|
51
|
-
end
|
|
47
|
+
stringify(jsonable.tree)
|
|
52
48
|
end
|
|
53
49
|
|
|
54
50
|
def jsonable_tree(hash, depth: 0, seen: nil, parent_key: nil, omit_keys: nil, count_leaves: false, leaf_counter: nil)
|
|
@@ -99,14 +95,14 @@ module JsonLogging
|
|
|
99
95
|
when String
|
|
100
96
|
leaf_counter[0] += 1 if leaf_counter
|
|
101
97
|
sanitized = Sanitizer.sanitize_string(value)
|
|
102
|
-
JsonableResult.new(tree: sanitized, owned: sanitized != value)
|
|
98
|
+
JsonableResult.new(tree: sanitized, owned: sanitized != value, leaf_count: nil)
|
|
103
99
|
when Hash
|
|
104
100
|
jsonable_tree(value, depth: depth + 1, seen: seen, parent_key: parent_key, leaf_counter: leaf_counter)
|
|
105
101
|
when Array
|
|
106
102
|
jsonable_array(value, depth: depth, seen: seen, parent_key: parent_key, leaf_counter: leaf_counter)
|
|
107
103
|
else
|
|
108
104
|
leaf_counter[0] += 1 if leaf_counter
|
|
109
|
-
JsonableResult.new(tree: value, owned: false)
|
|
105
|
+
JsonableResult.new(tree: value, owned: false, leaf_count: nil)
|
|
110
106
|
end
|
|
111
107
|
end
|
|
112
108
|
|
|
@@ -123,7 +119,7 @@ module JsonLogging
|
|
|
123
119
|
result[index] = child.tree
|
|
124
120
|
end
|
|
125
121
|
|
|
126
|
-
JsonableResult.new(tree: owned ? result : array, owned: owned)
|
|
122
|
+
JsonableResult.new(tree: owned ? result : array, owned: owned, leaf_count: nil)
|
|
127
123
|
end
|
|
128
124
|
|
|
129
125
|
def process_jsonable_entry(key, value, depth:, seen:, parent_key:, omit_keys:, leaf_counter:)
|
|
@@ -133,7 +129,8 @@ module JsonLogging
|
|
|
133
129
|
return
|
|
134
130
|
end
|
|
135
131
|
|
|
136
|
-
|
|
132
|
+
# ParameterFilter keeps original key names; fallback rename would hide them from it.
|
|
133
|
+
if Sanitizer.rails_parameter_filter.nil? && Sanitizer::SENSITIVE_KEY_PATTERNS.match?(key_string)
|
|
137
134
|
return [Sanitizer.sensitive_filtered_key_name(key_string), "[FILTERED]", true]
|
|
138
135
|
end
|
|
139
136
|
|
|
@@ -195,12 +192,6 @@ module JsonLogging
|
|
|
195
192
|
value
|
|
196
193
|
end
|
|
197
194
|
end
|
|
198
|
-
|
|
199
|
-
def stringify_keys_copy(hash)
|
|
200
|
-
return hash if hash.keys.all? { |key| key.is_a?(String) }
|
|
201
|
-
|
|
202
|
-
hash.transform_keys(&:to_s)
|
|
203
|
-
end
|
|
204
195
|
end
|
|
205
196
|
end
|
|
206
197
|
end
|
data/lib/json_logging/version.rb
CHANGED
data/lib/json_logging.rb
CHANGED
|
@@ -12,6 +12,7 @@ require_relative "json_logging/formatter"
|
|
|
12
12
|
require_relative "json_logging/formatter_with_tags"
|
|
13
13
|
require_relative "json_logging/json_logger_extension"
|
|
14
14
|
require_relative "json_logging/json_logger"
|
|
15
|
+
require_relative "json_logging/event_subscriber"
|
|
15
16
|
|
|
16
17
|
module JsonLogging
|
|
17
18
|
THREAD_CONTEXT_KEY = :__json_logging_context
|
data/sig/json_logging.rbs
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activesupport-json_logging
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -15,7 +15,7 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '7.0'
|
|
19
19
|
- - "<"
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
21
|
version: '9.0'
|
|
@@ -25,7 +25,7 @@ dependencies:
|
|
|
25
25
|
requirements:
|
|
26
26
|
- - ">="
|
|
27
27
|
- !ruby/object:Gem::Version
|
|
28
|
-
version: '
|
|
28
|
+
version: '7.0'
|
|
29
29
|
- - "<"
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: '9.0'
|
|
@@ -35,7 +35,7 @@ dependencies:
|
|
|
35
35
|
requirements:
|
|
36
36
|
- - ">="
|
|
37
37
|
- !ruby/object:Gem::Version
|
|
38
|
-
version: '
|
|
38
|
+
version: '7.0'
|
|
39
39
|
- - "<"
|
|
40
40
|
- !ruby/object:Gem::Version
|
|
41
41
|
version: '9.0'
|
|
@@ -45,10 +45,24 @@ dependencies:
|
|
|
45
45
|
requirements:
|
|
46
46
|
- - ">="
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
|
-
version: '
|
|
48
|
+
version: '7.0'
|
|
49
49
|
- - "<"
|
|
50
50
|
- !ruby/object:Gem::Version
|
|
51
51
|
version: '9.0'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: json
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: 2.21.2
|
|
59
|
+
type: :runtime
|
|
60
|
+
prerelease: false
|
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - ">="
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: 2.21.2
|
|
52
66
|
- !ruby/object:Gem::Dependency
|
|
53
67
|
name: rspec
|
|
54
68
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -77,20 +91,6 @@ dependencies:
|
|
|
77
91
|
- - ">="
|
|
78
92
|
- !ruby/object:Gem::Version
|
|
79
93
|
version: 2.2.0
|
|
80
|
-
- !ruby/object:Gem::Dependency
|
|
81
|
-
name: webmock
|
|
82
|
-
requirement: !ruby/object:Gem::Requirement
|
|
83
|
-
requirements:
|
|
84
|
-
- - "~>"
|
|
85
|
-
- !ruby/object:Gem::Version
|
|
86
|
-
version: '3'
|
|
87
|
-
type: :development
|
|
88
|
-
prerelease: false
|
|
89
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
90
|
-
requirements:
|
|
91
|
-
- - "~>"
|
|
92
|
-
- !ruby/object:Gem::Version
|
|
93
|
-
version: '3'
|
|
94
94
|
- !ruby/object:Gem::Dependency
|
|
95
95
|
name: rake
|
|
96
96
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -316,7 +316,7 @@ dependencies:
|
|
|
316
316
|
- !ruby/object:Gem::Version
|
|
317
317
|
version: '0.2'
|
|
318
318
|
description: Lightweight JSON logger and formatter integrating with Rails/ActiveSupport.
|
|
319
|
-
No extra deps beyond Rails/Activesupport. Compatible with Rails
|
|
319
|
+
No extra deps beyond Rails/Activesupport. Compatible with Rails 7–8.
|
|
320
320
|
email:
|
|
321
321
|
- contact@kiskolabs.com
|
|
322
322
|
executables: []
|
|
@@ -329,6 +329,7 @@ files:
|
|
|
329
329
|
- lib/activesupport/json_logging.rb
|
|
330
330
|
- lib/activesupport/json_logging/railtie.rb
|
|
331
331
|
- lib/json_logging.rb
|
|
332
|
+
- lib/json_logging/event_subscriber.rb
|
|
332
333
|
- lib/json_logging/formatter.rb
|
|
333
334
|
- lib/json_logging/formatter_with_tags.rb
|
|
334
335
|
- lib/json_logging/helpers.rb
|
|
@@ -338,6 +339,7 @@ files:
|
|
|
338
339
|
- lib/json_logging/message_parser.rb
|
|
339
340
|
- lib/json_logging/payload_builder.rb
|
|
340
341
|
- lib/json_logging/sanitizer.rb
|
|
342
|
+
- lib/json_logging/sanitizer_hash_limit.rb
|
|
341
343
|
- lib/json_logging/severity.rb
|
|
342
344
|
- lib/json_logging/structured_hash_json_encoder.rb
|
|
343
345
|
- lib/json_logging/structured_hash_sanitizer.rb
|
|
@@ -357,14 +359,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
357
359
|
requirements:
|
|
358
360
|
- - ">="
|
|
359
361
|
- !ruby/object:Gem::Version
|
|
360
|
-
version: '2
|
|
362
|
+
version: '3.2'
|
|
361
363
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
362
364
|
requirements:
|
|
363
365
|
- - ">="
|
|
364
366
|
- !ruby/object:Gem::Version
|
|
365
367
|
version: '0'
|
|
366
368
|
requirements: []
|
|
367
|
-
rubygems_version: 4.0.
|
|
369
|
+
rubygems_version: 4.0.6
|
|
368
370
|
specification_version: 4
|
|
369
371
|
summary: Structured JSON logging for Rails/ActiveSupport with safe, single-line entries.
|
|
370
372
|
test_files: []
|