activesupport-json_logging 1.0.0 → 1.2.0
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 +13 -38
- data/README.md +144 -22
- data/lib/activesupport/json_logging.rb +0 -1
- data/lib/json_logging/formatter_with_tags.rb +38 -8
- data/lib/json_logging/json_logger_extension.rb +29 -9
- data/lib/json_logging/payload_builder.rb +11 -3
- data/lib/json_logging/version.rb +1 -1
- metadata +71 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c99bef73cc6bf2627075656ee38a3e3b756ac61ef54f2edcc628814b317ac7bf
|
|
4
|
+
data.tar.gz: c4a0e1ecfd0ae8862db0167cc5507b8da4572e6a58ed4035ec7aa606c3154442
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3abb724226828b28519f25dc8edb138c3b4d7dc1a07bf2946b952e46bee4bef5d71149a44df4a52cb12e41f892ca4d2f7147bfdeb9dbd7ad0ca3c4ee3873dddc
|
|
7
|
+
data.tar.gz: 25b2c9f5ae0c8107e4f5c7385ff96fbb379cbfba851234be9800909978b14d1d81b5fab961a5d72bd1d4ede78c5d1d16eb5ab1534e6ed54d2074e1c21456395c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,44 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CHANGELOG
|
|
2
2
|
|
|
3
|
-
## 1.
|
|
3
|
+
## 1.2.0 (2025-11-07)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- Add support for service-specific tagged loggers: create loggers with permanent tags using `logger.tagged("service")` without a block
|
|
6
|
+
- Improve BroadcastLogger compatibility: service-specific loggers work seamlessly with `ActiveSupport::BroadcastLogger`
|
|
7
|
+
- Fix LocalTagStorage implementation to match Rails' TaggedLogging behavior: use `tag_stack` attribute accessor pattern for proper tag isolation
|
|
8
|
+
- Add comprehensive examples in README for service-specific loggers and BroadcastLogger integration
|
|
6
9
|
|
|
7
|
-
|
|
8
|
-
- feat: native tagged logging with `logger.tagged("TAG")` API compatible with Rails
|
|
9
|
-
- feat: thread-safe context via `JsonLogging.with_context` for per-thread fields
|
|
10
|
-
- feat: smart message parsing for hashes, JSON strings, and plain strings
|
|
11
|
-
- feat: inherit all ActiveSupport::Logger features (silence, local_level, etc.)
|
|
12
|
-
- feat: BroadcastLogger compatibility for Rails 7.1+ automatic wrapping
|
|
13
|
-
- feat: timestamp precision in microseconds (iso8601 with 6 decimals)
|
|
14
|
-
- feat: Rails ParameterFilter integration for automatic sensitive data filtering
|
|
15
|
-
- feat: input sanitization removing control characters and truncating long strings
|
|
16
|
-
- feat: sensitive key pattern matching fallback when ParameterFilter unavailable
|
|
17
|
-
- feat: depth and size limits for nested structures to prevent log bloat
|
|
18
|
-
- feat: single-line JSON output to prevent log injection via newlines
|
|
19
|
-
- feat: graceful error handling with fallback entries on serialization errors
|
|
20
|
-
- feat: Rails 6.0, 6.1, 7.0, 7.1, 7.2, 8.0 support
|
|
21
|
-
- feat: IsolatedExecutionState for thread/Fiber isolation (Rails 7.1+)
|
|
22
|
-
- feat: backward compatible fallback to Thread.current for Rails 6-7.0
|
|
23
|
-
- feat: kwargs support in logger initialization for Rails 7+
|
|
24
|
-
- perf: ~0.006ms per log entry overhead (250-400% vs plain text, typical for JSON)
|
|
25
|
-
- perf: memory efficient with ~3KB per entry and zero retained memory
|
|
26
|
-
- feat: performance benchmarks with memory profiling included
|
|
27
|
-
- test: 93.78% code coverage with comprehensive RSpec suite
|
|
28
|
-
- test: BroadcastLogger integration tests
|
|
29
|
-
- test: IsolatedExecutionState thread safety tests
|
|
30
|
-
- test: Appraisals configured for multi-version testing (Rails 6-8)
|
|
31
|
-
- test: GitHub Actions CI workflow
|
|
32
|
-
- docs: complete README with installation, usage, and API docs
|
|
33
|
-
- docs: Rails environment configuration examples (development, production, test)
|
|
34
|
-
- docs: Lograge integration with third-party logger configurations
|
|
35
|
-
- docs: Puma integration example
|
|
36
|
-
- docs: security best practices and ParameterFilter guide
|
|
37
|
-
- docs: inherited Rails logger features documentation
|
|
10
|
+
## 1.1.0 (2025-11-04)
|
|
38
11
|
|
|
39
|
-
|
|
12
|
+
- Move tags to root level of JSON payload instead of nested in context (breaking change: tags now at `payload["tags"]` instead of `payload["context"]["tags"]`)
|
|
13
|
+
- Filter system-controlled keys (severity, timestamp, message, tags, context) from user context to prevent conflicts
|
|
14
|
+
- Prevent nested context objects when user context includes a `context` key
|
|
15
|
+
- Fix pending spec for TimeWithZone objects by requiring ActiveSupport time extensions
|
|
40
16
|
|
|
41
|
-
##
|
|
17
|
+
## 1.0.0 (2025-10-31)
|
|
42
18
|
|
|
43
|
-
-
|
|
44
|
-
- test: basic RSpec test coverage
|
|
19
|
+
- Initial stable release
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# activesupport-json_logging
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/activesupport-json_logging) [](https://badge.fury.io/rb/activesupport-json_logging) [](https://github.com/amkisko/activesupport-json_logging.rb/actions/workflows/test.yml) [](https://codecov.io/gh/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.
|
|
@@ -8,6 +8,11 @@ Supports Rails versions from 6 to 8.
|
|
|
8
8
|
|
|
9
9
|
Sponsored by [Kisko Labs](https://www.kiskolabs.com).
|
|
10
10
|
|
|
11
|
+
<a href="https://www.kiskolabs.com">
|
|
12
|
+
<img src="kisko.svg" width="200" alt="Sponsored by Kisko Labs" />
|
|
13
|
+
</a>
|
|
14
|
+
|
|
15
|
+
|
|
11
16
|
## Installation
|
|
12
17
|
|
|
13
18
|
Add to your Gemfile:
|
|
@@ -30,22 +35,6 @@ Rails.application.configure do
|
|
|
30
35
|
end
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
### Development: Using from Local Repository
|
|
34
|
-
|
|
35
|
-
When developing the gem or testing changes in your application, you can point your Gemfile to a local path:
|
|
36
|
-
|
|
37
|
-
```ruby
|
|
38
|
-
# In your application's Gemfile
|
|
39
|
-
gem "activesupport-json_logging", path: "../activesupport-json_logging.rb"
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
Then run:
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
bundle install
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
**Note:** When using `path:` in your Gemfile, Bundler will use the local gem directly. Changes you make to the gem code will be immediately available in your application without needing to rebuild or reinstall the gem. This is ideal for development and testing.
|
|
49
38
|
|
|
50
39
|
## What you get
|
|
51
40
|
|
|
@@ -55,6 +44,8 @@ bundle install
|
|
|
55
44
|
- `JsonLogging.with_context` to attach contextual fields per-thread
|
|
56
45
|
- Smart message parsing (handles hashes, JSON strings, plain strings, and Exception objects)
|
|
57
46
|
- Native `tagged` method support - use it just like Rails' tagged logger
|
|
47
|
+
- Service-specific tagged loggers - create loggers with permanent tags using `logger.tagged("service")` without a block
|
|
48
|
+
- Full compatibility with `ActiveSupport::BroadcastLogger` (Rails 7.1+)
|
|
58
49
|
- Automatic Rails integration via Railtie (auto-requires the gem in Rails apps)
|
|
59
50
|
|
|
60
51
|
## Basic usage
|
|
@@ -78,6 +69,17 @@ logger.tagged("BCX").info("Stuff")
|
|
|
78
69
|
logger.tagged("BCX", "Jason").info("Stuff")
|
|
79
70
|
logger.tagged("BCX").tagged("Jason").info("Stuff")
|
|
80
71
|
|
|
72
|
+
# Create a service-specific logger with permanent tags
|
|
73
|
+
# All logs from this logger will include the "dotenv" tag
|
|
74
|
+
dotenv_logger = logger.tagged("dotenv")
|
|
75
|
+
dotenv_logger.info("Loading environment variables") # Includes "dotenv" tag
|
|
76
|
+
dotenv_logger.warn("Missing .env file") # Includes "dotenv" tag
|
|
77
|
+
|
|
78
|
+
# You can also create service loggers directly
|
|
79
|
+
base_logger = JsonLogging.logger($stdout)
|
|
80
|
+
service_logger = base_logger.tagged("my-service")
|
|
81
|
+
service_logger.info("Service started") # All logs tagged with "my-service"
|
|
82
|
+
|
|
81
83
|
# Add context
|
|
82
84
|
JsonLogging.with_context(user_id: 123) do
|
|
83
85
|
logger.warn({event: "slow_query", duration_ms: 250})
|
|
@@ -100,6 +102,84 @@ This gem does **not** automatically configure your Rails app. You set it up manu
|
|
|
100
102
|
- In Rails 7.1+, Rails automatically wraps your logger in `ActiveSupport::BroadcastLogger` to enable writing to multiple destinations (e.g., STDOUT and file simultaneously). This works seamlessly with our logger - your JSON logger will be wrapped and all method calls will delegate correctly. No special handling needed.
|
|
101
103
|
- In Rails 7.1+, tag storage uses `ActiveSupport::IsolatedExecutionState` for improved thread/Fiber safety.
|
|
102
104
|
|
|
105
|
+
### Service-specific loggers with tags
|
|
106
|
+
|
|
107
|
+
You can create loggers with permanent tags for specific services or components. This is useful when you want all logs from a particular service to be tagged consistently:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
# Create a logger for DotEnv service with "dotenv" tag
|
|
111
|
+
base_logger = JsonLogging.logger($stdout)
|
|
112
|
+
dotenv_logger = base_logger.tagged("dotenv")
|
|
113
|
+
|
|
114
|
+
# All logs from this logger will include the "dotenv" tag
|
|
115
|
+
dotenv_logger.info("Loading .env file")
|
|
116
|
+
dotenv_logger.warn("Missing .env.local file")
|
|
117
|
+
dotenv_logger.error("Invalid environment variable format")
|
|
118
|
+
|
|
119
|
+
# Example: Configure Dotenv::Rails to use tagged logger
|
|
120
|
+
if defined?(Dotenv::Rails)
|
|
121
|
+
Dotenv::Rails.logger = base_logger.tagged("dotenv")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Example: Create multiple service loggers
|
|
125
|
+
redis_logger = base_logger.tagged("redis")
|
|
126
|
+
sidekiq_logger = base_logger.tagged("sidekiq")
|
|
127
|
+
api_logger = base_logger.tagged("api")
|
|
128
|
+
|
|
129
|
+
# Each service logger maintains its tag across all log calls
|
|
130
|
+
redis_logger.info("Connected to Redis") # Tagged with "redis"
|
|
131
|
+
sidekiq_logger.info("Job enqueued") # Tagged with "sidekiq"
|
|
132
|
+
api_logger.info("Request received") # Tagged with "api"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### BroadcastLogger integration
|
|
136
|
+
|
|
137
|
+
`ActiveSupport::BroadcastLogger` (Rails 7.1+) allows writing logs to multiple destinations simultaneously. `JsonLogging` works seamlessly with `BroadcastLogger`:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
# Create JSON loggers for different destinations
|
|
141
|
+
stdout_logger = JsonLogging.logger($stdout)
|
|
142
|
+
file_logger = JsonLogging.logger(Rails.root.join("log", "production.log"))
|
|
143
|
+
|
|
144
|
+
# Wrap in BroadcastLogger to write to both destinations
|
|
145
|
+
broadcast_logger = ActiveSupport::BroadcastLogger.new(stdout_logger)
|
|
146
|
+
broadcast_logger.broadcast_to(file_logger)
|
|
147
|
+
|
|
148
|
+
# All logging methods work through BroadcastLogger
|
|
149
|
+
broadcast_logger.info("This goes to both STDOUT and file")
|
|
150
|
+
broadcast_logger.warn({event: "warning", message: "Something happened"})
|
|
151
|
+
|
|
152
|
+
# Tagged logging works through BroadcastLogger
|
|
153
|
+
broadcast_logger.tagged("REQUEST", request_id) do
|
|
154
|
+
broadcast_logger.info("Processing request") # Tagged logs go to both destinations
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Service-specific loggers work with BroadcastLogger
|
|
158
|
+
# Note: Create service logger from underlying logger, then wrap in BroadcastLogger
|
|
159
|
+
# (BroadcastLogger.tagged without block returns array due to delegation)
|
|
160
|
+
base_logger = JsonLogging.logger($stdout)
|
|
161
|
+
dotenv_logger = base_logger.tagged("dotenv")
|
|
162
|
+
dotenv_broadcast = ActiveSupport::BroadcastLogger.new(dotenv_logger)
|
|
163
|
+
dotenv_broadcast.broadcast_to(file_logger.tagged("dotenv")) # Tag second destination too
|
|
164
|
+
dotenv_broadcast.info("Environment loaded") # Tagged and broadcast to all destinations
|
|
165
|
+
|
|
166
|
+
# Rails 7.1+ automatically uses BroadcastLogger
|
|
167
|
+
# Your configuration can be simplified:
|
|
168
|
+
Rails.application.configure do
|
|
169
|
+
# Rails will automatically wrap this in BroadcastLogger
|
|
170
|
+
base_logger = ActiveSupport::Logger.new($stdout)
|
|
171
|
+
json_logger = JsonLogging.new(base_logger)
|
|
172
|
+
config.logger = json_logger # Rails wraps this in BroadcastLogger automatically
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Key points:**
|
|
177
|
+
- All logger methods (`info`, `warn`, `error`, etc.) work through `BroadcastLogger`
|
|
178
|
+
- Tagged logging (`tagged`) works correctly through `BroadcastLogger`
|
|
179
|
+
- Service-specific tagged loggers work with `BroadcastLogger`
|
|
180
|
+
- Each destination receives properly formatted JSON logs
|
|
181
|
+
- No special configuration needed - just wrap your `JsonLogging` logger in `BroadcastLogger`
|
|
182
|
+
|
|
103
183
|
### Basic setup
|
|
104
184
|
|
|
105
185
|
Create `config/initializers/json_logging.rb`:
|
|
@@ -329,10 +409,10 @@ logger.tagged("BCX").tagged("Jason").info("Stuff")
|
|
|
329
409
|
# Wrap a TaggedLogging logger - works perfectly
|
|
330
410
|
tagged_logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
|
|
331
411
|
json_logger = JsonLogging.new(tagged_logger)
|
|
332
|
-
json_logger.tagged("TEST") { json_logger.info("message") } # Tags appear in JSON
|
|
412
|
+
json_logger.tagged("TEST") { json_logger.info("message") } # Tags appear at root level in JSON
|
|
333
413
|
```
|
|
334
414
|
|
|
335
|
-
**Note:** If you wrap a `JsonLogging` logger with `ActiveSupport::TaggedLogging`, the TaggedLogging's text-based tags will appear as part of the message string in the JSON output, not as structured tags
|
|
415
|
+
**Note:** If you wrap a `JsonLogging` logger with `ActiveSupport::TaggedLogging`, the TaggedLogging's text-based tags will appear as part of the message string in the JSON output, not as structured tags at the root level. For best results, wrap loggers with `JsonLogging` last.
|
|
336
416
|
|
|
337
417
|
### JsonLogging::JsonLogger
|
|
338
418
|
|
|
@@ -359,7 +439,7 @@ formatter.call("INFO", Time.now, nil, "message")
|
|
|
359
439
|
|
|
360
440
|
# With tags (useful for Puma or other standalone use cases)
|
|
361
441
|
formatter = JsonLogging::Formatter.new(tags: ["Puma"])
|
|
362
|
-
formatter.call("INFO", Time.now, nil, "message") # Output includes "Puma" tag
|
|
442
|
+
formatter.call("INFO", Time.now, nil, "message") # Output includes "Puma" tag at root level
|
|
363
443
|
|
|
364
444
|
# Multiple tags
|
|
365
445
|
formatter = JsonLogging::Formatter.new(tags: ["Puma", "Worker"])
|
|
@@ -511,12 +591,13 @@ Rails.application.config.filter_parameters += [
|
|
|
511
591
|
|
|
512
592
|
The gem will automatically filter these from all log entries, including context data. Encrypted attributes (using Rails 7+ `encrypts`) are automatically filtered as well.
|
|
513
593
|
|
|
594
|
+
|
|
514
595
|
## Development
|
|
515
596
|
|
|
516
597
|
```bash
|
|
517
598
|
# Install dependencies
|
|
518
599
|
bundle install
|
|
519
|
-
bundle exec appraisal
|
|
600
|
+
bundle exec appraisal generate
|
|
520
601
|
|
|
521
602
|
# Run tests for current Rails version
|
|
522
603
|
bundle exec rspec
|
|
@@ -536,6 +617,47 @@ bundle exec appraisal rails-7.0 rspec
|
|
|
536
617
|
bundle exec standardrb --fix
|
|
537
618
|
```
|
|
538
619
|
|
|
620
|
+
### Development: Using from Local Repository
|
|
621
|
+
|
|
622
|
+
When developing the gem or testing changes in your application, you can point your Gemfile to a local path:
|
|
623
|
+
|
|
624
|
+
```ruby
|
|
625
|
+
# In your application's Gemfile
|
|
626
|
+
gem "activesupport-json_logging", path: "../activesupport-json_logging.rb"
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Then run:
|
|
630
|
+
|
|
631
|
+
```bash
|
|
632
|
+
bundle install
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
**Note:** When using `path:` in your Gemfile, Bundler will use the local gem directly. Changes you make to the gem code will be immediately available in your application without needing to rebuild or reinstall the gem. This is ideal for development and testing.
|
|
636
|
+
|
|
637
|
+
## Contributing
|
|
638
|
+
|
|
639
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/amkisko/activesupport-json_logging.rb
|
|
640
|
+
|
|
641
|
+
Contribution policy:
|
|
642
|
+
- New features are not necessarily added to the gem
|
|
643
|
+
- Pull request should have test coverage for affected parts
|
|
644
|
+
- Pull request should have changelog entry
|
|
645
|
+
|
|
646
|
+
Review policy:
|
|
647
|
+
- It might take up to 2 calendar weeks to review and merge critical fixes
|
|
648
|
+
- It might take up to 6 calendar months to review and merge pull request
|
|
649
|
+
- It might take up to 1 calendar year to review an issue
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
## Publishing
|
|
653
|
+
|
|
654
|
+
```sh
|
|
655
|
+
rm activesupport-json_logging-*.gem
|
|
656
|
+
gem build activesupport-json_logging.gemspec
|
|
657
|
+
gem push activesupport-json_logging-*.gem
|
|
658
|
+
```
|
|
659
|
+
|
|
539
660
|
## License
|
|
540
661
|
|
|
541
|
-
MIT
|
|
662
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
663
|
+
|
|
@@ -8,7 +8,13 @@ module JsonLogging
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def current_tags
|
|
11
|
-
|
|
11
|
+
# If LocalTagStorage is extended on this formatter, use its tag_stack
|
|
12
|
+
# This matches Rails' TaggedLogging behavior where tag_stack attribute shadows the method
|
|
13
|
+
if respond_to?(:tag_stack, true) && instance_variable_defined?(:@tag_stack)
|
|
14
|
+
tag_stack.tags
|
|
15
|
+
else
|
|
16
|
+
@logger.send(:current_tags)
|
|
17
|
+
end
|
|
12
18
|
end
|
|
13
19
|
|
|
14
20
|
def call(severity, timestamp, progname, msg)
|
|
@@ -22,18 +28,42 @@ module JsonLogging
|
|
|
22
28
|
build_fallback_output(severity, timestamp, msg, e)
|
|
23
29
|
end
|
|
24
30
|
|
|
31
|
+
def push_tags(*tags)
|
|
32
|
+
# If LocalTagStorage is present, use it; otherwise use logger's thread-local storage
|
|
33
|
+
if respond_to?(:tag_stack, true) && instance_variable_defined?(:@tag_stack)
|
|
34
|
+
tag_stack.push_tags(tags)
|
|
35
|
+
else
|
|
36
|
+
@logger.send(:push_tags, tags)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
25
40
|
# Support tagged blocks for formatter
|
|
26
41
|
def tagged(*tags)
|
|
27
42
|
if block_given?
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
43
|
+
# If LocalTagStorage is present, use it; otherwise use logger's thread-local storage
|
|
44
|
+
if respond_to?(:tag_stack, true) && instance_variable_defined?(:@tag_stack)
|
|
45
|
+
previous_count = tag_stack.tags.size
|
|
46
|
+
tag_stack.push_tags(tags)
|
|
47
|
+
begin
|
|
48
|
+
yield @logger
|
|
49
|
+
ensure
|
|
50
|
+
tag_stack.pop_tags(tag_stack.tags.size - previous_count)
|
|
51
|
+
end
|
|
52
|
+
else
|
|
53
|
+
previous = @logger.send(:current_tags).dup
|
|
54
|
+
@logger.send(:push_tags, tags)
|
|
55
|
+
begin
|
|
56
|
+
yield @logger
|
|
57
|
+
ensure
|
|
58
|
+
@logger.send(:set_tags, previous)
|
|
59
|
+
end
|
|
34
60
|
end
|
|
35
61
|
else
|
|
36
|
-
|
|
62
|
+
if respond_to?(:tag_stack, true) && instance_variable_defined?(:@tag_stack)
|
|
63
|
+
tag_stack.push_tags(tags)
|
|
64
|
+
else
|
|
65
|
+
@logger.send(:push_tags, tags)
|
|
66
|
+
end
|
|
37
67
|
self
|
|
38
68
|
end
|
|
39
69
|
end
|
|
@@ -75,7 +75,9 @@ module JsonLogging
|
|
|
75
75
|
# Return a new wrapped logger with tags applied (similar to TaggedLogging)
|
|
76
76
|
logger = JsonLogging.new(self)
|
|
77
77
|
# Extend formatter with LocalTagStorage to preserve current tags when creating nested loggers
|
|
78
|
+
# This matches Rails' TaggedLogging behavior
|
|
78
79
|
logger.formatter.extend(LocalTagStorage)
|
|
80
|
+
# Push tags through formatter (matches Rails delegation pattern)
|
|
79
81
|
logger.formatter.push_tags(*formatter.current_tags, *tags)
|
|
80
82
|
logger
|
|
81
83
|
end
|
|
@@ -132,7 +134,7 @@ module JsonLogging
|
|
|
132
134
|
payload = PayloadBuilder.merge_context(
|
|
133
135
|
payload,
|
|
134
136
|
additional_context: JsonLogging.additional_context.compact,
|
|
135
|
-
tags: current_tags
|
|
137
|
+
tags: formatter.current_tags
|
|
136
138
|
)
|
|
137
139
|
|
|
138
140
|
payload.compact
|
|
@@ -159,19 +161,37 @@ module JsonLogging
|
|
|
159
161
|
# Module for preserving current tags when creating nested tagged loggers
|
|
160
162
|
# Similar to ActiveSupport::TaggedLogging::LocalTagStorage
|
|
161
163
|
# When extended on a formatter, stores tags locally instead of using thread-local storage
|
|
164
|
+
# Uses tag_stack attribute accessor pattern to match Rails' TaggedLogging behavior
|
|
162
165
|
module LocalTagStorage
|
|
166
|
+
attr_accessor :tag_stack
|
|
167
|
+
|
|
163
168
|
def self.extended(base)
|
|
164
|
-
base.
|
|
169
|
+
base.tag_stack = LocalTagStack.new
|
|
165
170
|
end
|
|
166
171
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
end
|
|
172
|
+
# Simple tag stack implementation for local tag storage
|
|
173
|
+
# Similar to ActiveSupport::TaggedLogging::TagStack but simplified for JSON logging
|
|
174
|
+
class LocalTagStack
|
|
175
|
+
attr_reader :tags
|
|
172
176
|
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
def initialize
|
|
178
|
+
@tags = []
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def push_tags(tags)
|
|
182
|
+
flat = Array(tags).flatten.compact.map(&:to_s).reject(&:empty?)
|
|
183
|
+
return [] if flat.empty?
|
|
184
|
+
@tags.concat(flat)
|
|
185
|
+
flat
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def pop_tags(count = 1)
|
|
189
|
+
@tags.pop(count)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def clear
|
|
193
|
+
@tags.clear
|
|
194
|
+
end
|
|
175
195
|
end
|
|
176
196
|
end
|
|
177
197
|
end
|
|
@@ -29,14 +29,22 @@ module JsonLogging
|
|
|
29
29
|
additional_context || {}
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
# Filter out system-controlled keys from user context
|
|
33
|
+
# These keys should never be set by user context as they're controlled by the logger
|
|
34
|
+
system_controlled_keys = [:tags, "tags", :severity, "severity", :timestamp, "timestamp", :message, "message", :context, "context"]
|
|
35
|
+
user_context_filtered = sanitized_context.except(*system_controlled_keys)
|
|
36
|
+
|
|
37
|
+
# Also prevent overriding any existing payload keys (additional safety)
|
|
38
|
+
deduped_additional = user_context_filtered.reject { |k, _| payload.key?(k) }
|
|
33
39
|
merged_context = existing_context.merge(deduped_additional)
|
|
34
40
|
|
|
41
|
+
# Put tags at root level, separate from context
|
|
42
|
+
# Merge with existing tags from payload (e.g., when logging a hash with tags: [...] at root)
|
|
35
43
|
unless tags.empty?
|
|
36
|
-
existing_tags = Array(
|
|
44
|
+
existing_tags = Array(payload[:tags] || payload["tags"])
|
|
37
45
|
# Sanitize tag strings (remove control chars, truncate)
|
|
38
46
|
sanitized_tags = tags.map { |tag| Sanitizer.sanitize_string(tag.to_s) }
|
|
39
|
-
|
|
47
|
+
payload[:tags] = (existing_tags + sanitized_tags).uniq
|
|
40
48
|
end
|
|
41
49
|
|
|
42
50
|
payload[:context] = merged_context unless merged_context.empty?
|
data/lib/json_logging/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
7
|
+
- Andrei Makarov
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10
|
|
11
|
+
date: 2025-11-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -56,84 +56,140 @@ dependencies:
|
|
|
56
56
|
requirements:
|
|
57
57
|
- - "~>"
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
|
-
version: '3
|
|
59
|
+
version: '3'
|
|
60
60
|
type: :development
|
|
61
61
|
prerelease: false
|
|
62
62
|
version_requirements: !ruby/object:Gem::Requirement
|
|
63
63
|
requirements:
|
|
64
64
|
- - "~>"
|
|
65
65
|
- !ruby/object:Gem::Version
|
|
66
|
-
version: '3
|
|
66
|
+
version: '3'
|
|
67
|
+
- !ruby/object:Gem::Dependency
|
|
68
|
+
name: webmock
|
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - "~>"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '3'
|
|
74
|
+
type: :development
|
|
75
|
+
prerelease: false
|
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - "~>"
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '3'
|
|
67
81
|
- !ruby/object:Gem::Dependency
|
|
68
82
|
name: rake
|
|
69
83
|
requirement: !ruby/object:Gem::Requirement
|
|
70
84
|
requirements:
|
|
71
85
|
- - "~>"
|
|
72
86
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: '13
|
|
87
|
+
version: '13'
|
|
74
88
|
type: :development
|
|
75
89
|
prerelease: false
|
|
76
90
|
version_requirements: !ruby/object:Gem::Requirement
|
|
77
91
|
requirements:
|
|
78
92
|
- - "~>"
|
|
79
93
|
- !ruby/object:Gem::Version
|
|
80
|
-
version: '13
|
|
94
|
+
version: '13'
|
|
81
95
|
- !ruby/object:Gem::Dependency
|
|
82
96
|
name: simplecov
|
|
83
97
|
requirement: !ruby/object:Gem::Requirement
|
|
84
98
|
requirements:
|
|
85
99
|
- - "~>"
|
|
86
100
|
- !ruby/object:Gem::Version
|
|
87
|
-
version: '0.
|
|
101
|
+
version: '0.22'
|
|
102
|
+
type: :development
|
|
103
|
+
prerelease: false
|
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - "~>"
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '0.22'
|
|
109
|
+
- !ruby/object:Gem::Dependency
|
|
110
|
+
name: rspec_junit_formatter
|
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - "~>"
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: '0.6'
|
|
88
116
|
type: :development
|
|
89
117
|
prerelease: false
|
|
90
118
|
version_requirements: !ruby/object:Gem::Requirement
|
|
91
119
|
requirements:
|
|
92
120
|
- - "~>"
|
|
93
121
|
- !ruby/object:Gem::Version
|
|
94
|
-
version: '0.
|
|
122
|
+
version: '0.6'
|
|
123
|
+
- !ruby/object:Gem::Dependency
|
|
124
|
+
name: simplecov-cobertura
|
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
|
126
|
+
requirements:
|
|
127
|
+
- - "~>"
|
|
128
|
+
- !ruby/object:Gem::Version
|
|
129
|
+
version: '3'
|
|
130
|
+
type: :development
|
|
131
|
+
prerelease: false
|
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - "~>"
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '3'
|
|
95
137
|
- !ruby/object:Gem::Dependency
|
|
96
138
|
name: standard
|
|
97
139
|
requirement: !ruby/object:Gem::Requirement
|
|
98
140
|
requirements:
|
|
99
141
|
- - "~>"
|
|
100
142
|
- !ruby/object:Gem::Version
|
|
101
|
-
version: '1
|
|
143
|
+
version: '1'
|
|
102
144
|
type: :development
|
|
103
145
|
prerelease: false
|
|
104
146
|
version_requirements: !ruby/object:Gem::Requirement
|
|
105
147
|
requirements:
|
|
106
148
|
- - "~>"
|
|
107
149
|
- !ruby/object:Gem::Version
|
|
108
|
-
version: '1
|
|
150
|
+
version: '1'
|
|
109
151
|
- !ruby/object:Gem::Dependency
|
|
110
152
|
name: appraisal
|
|
111
153
|
requirement: !ruby/object:Gem::Requirement
|
|
112
154
|
requirements:
|
|
113
155
|
- - "~>"
|
|
114
156
|
- !ruby/object:Gem::Version
|
|
115
|
-
version: '2
|
|
157
|
+
version: '2'
|
|
116
158
|
type: :development
|
|
117
159
|
prerelease: false
|
|
118
160
|
version_requirements: !ruby/object:Gem::Requirement
|
|
119
161
|
requirements:
|
|
120
162
|
- - "~>"
|
|
121
163
|
- !ruby/object:Gem::Version
|
|
122
|
-
version: '2
|
|
164
|
+
version: '2'
|
|
123
165
|
- !ruby/object:Gem::Dependency
|
|
124
166
|
name: memory_profiler
|
|
125
167
|
requirement: !ruby/object:Gem::Requirement
|
|
126
168
|
requirements:
|
|
127
169
|
- - "~>"
|
|
128
170
|
- !ruby/object:Gem::Version
|
|
129
|
-
version: '1
|
|
171
|
+
version: '1'
|
|
172
|
+
type: :development
|
|
173
|
+
prerelease: false
|
|
174
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
175
|
+
requirements:
|
|
176
|
+
- - "~>"
|
|
177
|
+
- !ruby/object:Gem::Version
|
|
178
|
+
version: '1'
|
|
179
|
+
- !ruby/object:Gem::Dependency
|
|
180
|
+
name: rbs
|
|
181
|
+
requirement: !ruby/object:Gem::Requirement
|
|
182
|
+
requirements:
|
|
183
|
+
- - "~>"
|
|
184
|
+
- !ruby/object:Gem::Version
|
|
185
|
+
version: '3'
|
|
130
186
|
type: :development
|
|
131
187
|
prerelease: false
|
|
132
188
|
version_requirements: !ruby/object:Gem::Requirement
|
|
133
189
|
requirements:
|
|
134
190
|
- - "~>"
|
|
135
191
|
- !ruby/object:Gem::Version
|
|
136
|
-
version: '
|
|
192
|
+
version: '3'
|
|
137
193
|
description: Lightweight JSON logger and formatter integrating with Rails/ActiveSupport.
|
|
138
194
|
No extra deps beyond Rails/Activesupport. Compatible with Rails 6–8.
|
|
139
195
|
email:
|