log_cleaner 0.1.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 +7 -0
- data/.DS_Store +0 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +15 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +19 -0
- data/README.md +147 -0
- data/Rakefile +12 -0
- data/app/.DS_Store +0 -0
- data/lib/log_cleaner/.DS_Store +0 -0
- data/lib/log_cleaner/active_record_logger.rb +49 -0
- data/lib/log_cleaner/config/routes.rb +5 -0
- data/lib/log_cleaner/config.rb +33 -0
- data/lib/log_cleaner/engine.rb +36 -0
- data/lib/log_cleaner/logger.rb +114 -0
- data/lib/log_cleaner/request_logger.rb +150 -0
- data/lib/log_cleaner/request_middleware.rb +32 -0
- data/lib/log_cleaner/request_store.rb +29 -0
- data/lib/log_cleaner/version.rb +6 -0
- data/lib/log_cleaner.rb +45 -0
- data/sig/log_cleaner.rbs +4 -0
- metadata +67 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7d17979e59d30220cfa00c0855182bb6bfce4b8ddd7730b1eeec26cc51850154
|
|
4
|
+
data.tar.gz: e449a27cfd787627bd9dfce4c6c715392a1dc4fa27b7e98b7ba6d66386dacbcd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b4cffd66bf203dd0d9f7e93a89cca8e32fae55f3a8cd6a6241eb7d677c60a9c7d7d1114fe84103b72c6ca08eeacecb01fc7e01b0c286f43e37c6b9b13911e43c
|
|
7
|
+
data.tar.gz: f8ee1541a231494dd027b935e40b2657655514cc5154ac64a032fb40a49a0df86a40fdb1c545d070070a30434b51f6f716fc674eefb864a4775a4f8a1929a3de
|
data/.DS_Store
ADDED
|
Binary file
|
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.0] - 2026-01-18
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Structured JSON logging
|
|
7
|
+
- Automatic masking of sensitive fields (password, tokens, etc.)
|
|
8
|
+
- Request ID injection
|
|
9
|
+
- Request middleware support
|
|
10
|
+
- Controller request logging
|
|
11
|
+
- ActiveRecord SQL logging
|
|
12
|
+
- Configurable mask fields
|
|
13
|
+
|
|
14
|
+
### Security
|
|
15
|
+
- Prevents leaking sensitive data into logs
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
6
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
7
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
8
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
9
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
10
|
+
identity and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
13
|
+
diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
|
23
|
+
* Accepting responsibility and apologizing to those affected by our mistakes,
|
|
24
|
+
and learning from the experience
|
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the overall
|
|
26
|
+
community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or advances of
|
|
31
|
+
any kind
|
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
|
33
|
+
* Public or private harassment
|
|
34
|
+
* Publishing others' private information, such as a physical or email address,
|
|
35
|
+
without their explicit permission
|
|
36
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
37
|
+
professional setting
|
|
38
|
+
|
|
39
|
+
## Enforcement Responsibilities
|
|
40
|
+
|
|
41
|
+
Community leaders are responsible for clarifying and enforcing our standards of
|
|
42
|
+
acceptable behavior and will take appropriate and fair corrective action in
|
|
43
|
+
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
44
|
+
or harmful.
|
|
45
|
+
|
|
46
|
+
Community leaders have the right and responsibility to remove, edit, or reject
|
|
47
|
+
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
48
|
+
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
49
|
+
decisions when appropriate.
|
|
50
|
+
|
|
51
|
+
## Scope
|
|
52
|
+
|
|
53
|
+
This Code of Conduct applies within all community spaces, and also applies when
|
|
54
|
+
an individual is officially representing the community in public spaces.
|
|
55
|
+
Examples of representing our community include using an official email address,
|
|
56
|
+
posting via an official social media account, or acting as an appointed
|
|
57
|
+
representative at an online or offline event.
|
|
58
|
+
|
|
59
|
+
## Enforcement
|
|
60
|
+
|
|
61
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
62
|
+
reported to the community leaders responsible for enforcement at
|
|
63
|
+
[INSERT CONTACT METHOD].
|
|
64
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
65
|
+
|
|
66
|
+
All community leaders are obligated to respect the privacy and security of the
|
|
67
|
+
reporter of any incident.
|
|
68
|
+
|
|
69
|
+
## Enforcement Guidelines
|
|
70
|
+
|
|
71
|
+
Community leaders will follow these Community Impact Guidelines in determining
|
|
72
|
+
the consequences for any action they deem in violation of this Code of Conduct:
|
|
73
|
+
|
|
74
|
+
### 1. Correction
|
|
75
|
+
|
|
76
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
77
|
+
unprofessional or unwelcome in the community.
|
|
78
|
+
|
|
79
|
+
**Consequence**: A private, written warning from community leaders, providing
|
|
80
|
+
clarity around the nature of the violation and an explanation of why the
|
|
81
|
+
behavior was inappropriate. A public apology may be requested.
|
|
82
|
+
|
|
83
|
+
### 2. Warning
|
|
84
|
+
|
|
85
|
+
**Community Impact**: A violation through a single incident or series of
|
|
86
|
+
actions.
|
|
87
|
+
|
|
88
|
+
**Consequence**: A warning with consequences for continued behavior. No
|
|
89
|
+
interaction with the people involved, including unsolicited interaction with
|
|
90
|
+
those enforcing the Code of Conduct, for a specified period of time. This
|
|
91
|
+
includes avoiding interactions in community spaces as well as external channels
|
|
92
|
+
like social media. Violating these terms may lead to a temporary or permanent
|
|
93
|
+
ban.
|
|
94
|
+
|
|
95
|
+
### 3. Temporary Ban
|
|
96
|
+
|
|
97
|
+
**Community Impact**: A serious violation of community standards, including
|
|
98
|
+
sustained inappropriate behavior.
|
|
99
|
+
|
|
100
|
+
**Consequence**: A temporary ban from any sort of interaction or public
|
|
101
|
+
communication with the community for a specified period of time. No public or
|
|
102
|
+
private interaction with the people involved, including unsolicited interaction
|
|
103
|
+
with those enforcing the Code of Conduct, is allowed during this period.
|
|
104
|
+
Violating these terms may lead to a permanent ban.
|
|
105
|
+
|
|
106
|
+
### 4. Permanent Ban
|
|
107
|
+
|
|
108
|
+
**Community Impact**: Demonstrating a pattern of violation of community
|
|
109
|
+
standards, including sustained inappropriate behavior, harassment of an
|
|
110
|
+
individual, or aggression toward or disparagement of classes of individuals.
|
|
111
|
+
|
|
112
|
+
**Consequence**: A permanent ban from any sort of public interaction within the
|
|
113
|
+
community.
|
|
114
|
+
|
|
115
|
+
## Attribution
|
|
116
|
+
|
|
117
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
118
|
+
version 2.1, available at
|
|
119
|
+
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
|
120
|
+
|
|
121
|
+
Community Impact Guidelines were inspired by
|
|
122
|
+
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
|
123
|
+
|
|
124
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
|
125
|
+
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
|
126
|
+
[https://www.contributor-covenant.org/translations][translations].
|
|
127
|
+
|
|
128
|
+
[homepage]: https://www.contributor-covenant.org
|
|
129
|
+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
|
130
|
+
[Mozilla CoC]: https://github.com/mozilla/diversity
|
|
131
|
+
[FAQ]: https://www.contributor-covenant.org/faq
|
|
132
|
+
[translations]: https://www.contributor-covenant.org/translations
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
|
|
2
|
+
✅ This will also appear on RubyGems automatically once published.
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## 2️⃣ Create a Git Tag for your release
|
|
7
|
+
|
|
8
|
+
Before you can see tags with `git tag -l`, you must **create a tag**. Example for version `0.1.0`:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Stage and commit all changes (including README and version)
|
|
12
|
+
git add .
|
|
13
|
+
git commit -m "Release v0.1.0"
|
|
14
|
+
|
|
15
|
+
# Create a git tag
|
|
16
|
+
git tag v0.1.0
|
|
17
|
+
|
|
18
|
+
# Push commits and tags
|
|
19
|
+
git push origin main --tags
|
data/README.md
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# LogCleaner
|
|
2
|
+
|
|
3
|
+
LogCleaner is a Ruby gem for **structured JSON logging** with automatic **masking of sensitive fields** such as passwords, emails, and authentication tokens. It works with Rails controllers, middleware, and ActiveRecord models.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Structured JSON logs for Rails controllers and ActiveRecord
|
|
10
|
+
- Automatic masking of sensitive fields (`password`, `email`, `authenticity_token`, etc.)
|
|
11
|
+
- Middleware support for request IDs
|
|
12
|
+
- Configurable mask fields per environment
|
|
13
|
+
- Supports custom log messages
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
### 1. Add the Gem
|
|
20
|
+
|
|
21
|
+
Add `log_cleaner` to your Gemfile:
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
# Gemfile
|
|
25
|
+
gem 'log_cleaner'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then install the gem:
|
|
29
|
+
```
|
|
30
|
+
bundle install
|
|
31
|
+
```
|
|
32
|
+
## Create an Initializer
|
|
33
|
+
|
|
34
|
+
```Create a file for configuring masked fields:
|
|
35
|
+
touch config/initializers/log_cleaner.rb
|
|
36
|
+
```
|
|
37
|
+
Add the following content:
|
|
38
|
+
```
|
|
39
|
+
# config/initializers/log_cleaner.rb
|
|
40
|
+
LogCleaner.configure do |c|
|
|
41
|
+
if Rails.env.production?
|
|
42
|
+
# Mask sensitive fields in production
|
|
43
|
+
c.mask_fields = [:email, :password, :authenticity_token]
|
|
44
|
+
else
|
|
45
|
+
# Only mask password and authenticity_token in development
|
|
46
|
+
c.mask_fields = [:password, :authenticity_token]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
✅ This ensures sensitive data is masked automatically in logs.
|
|
52
|
+
|
|
53
|
+
## Include in ApplicationController
|
|
54
|
+
|
|
55
|
+
```Include LogCleaner::RequestLogger to clean logs for every request:
|
|
56
|
+
# app/controllers/application_controller.rb
|
|
57
|
+
class ApplicationController < ActionController::Base
|
|
58
|
+
include LogCleaner::RequestLogger
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Restart Rails Server
|
|
63
|
+
|
|
64
|
+
```After making changes, restart your Rails server:
|
|
65
|
+
rails server
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Verify Logs
|
|
69
|
+
|
|
70
|
+
Check your server logs (log/development.log or log/production.log) to see masked fields.
|
|
71
|
+
|
|
72
|
+
Example output:
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
**************************************************
|
|
76
|
+
{
|
|
77
|
+
"timestamp": "2026-01-17T18:03:37Z",
|
|
78
|
+
"level": "info",
|
|
79
|
+
"request_id": "req-97efe591",
|
|
80
|
+
"event": "controller_request",
|
|
81
|
+
"controller": "omniauth",
|
|
82
|
+
"action": "username_password_authenticate",
|
|
83
|
+
"status": 302,
|
|
84
|
+
"duration_ms": 492.15,
|
|
85
|
+
"params": {
|
|
86
|
+
"authenticity_token": "[FILTERED]",
|
|
87
|
+
"session": "[FILTERED]",
|
|
88
|
+
"commit": "Login",
|
|
89
|
+
"controller": "omniauth",
|
|
90
|
+
"action": "username_password_authenticate"
|
|
91
|
+
},
|
|
92
|
+
"request_body": {},
|
|
93
|
+
"url": "http://lms-in.yabx.local:3000/authenticate",
|
|
94
|
+
"method": "POST",
|
|
95
|
+
"ip": "127.0.0.1",
|
|
96
|
+
"user_id": 1
|
|
97
|
+
}
|
|
98
|
+
**************************************************
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Adding Custom Logs if you want then
|
|
102
|
+
|
|
103
|
+
You can log custom messages while keeping sensitive fields masked, Add this line for any action or any place:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
LogCleaner.log("Custom Info: User signup started", current_user: current_user.id)
|
|
107
|
+
```
|
|
108
|
+
Example log output:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
[INFO] Custom Info: User signup started {"current_user": 1}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Note: You can change the key or value according your own requirments.
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
## Contributing
|
|
118
|
+
|
|
119
|
+
Bug reports and pull requests are welcome on GitHub: [LogCleaner](https://github.com/shubham-chauhan-dev/log_cleaner)
|
|
120
|
+
|
|
121
|
+
1. Fork the repository
|
|
122
|
+
2. Create a branch (git checkout -b feature-name)
|
|
123
|
+
3. Make your changes
|
|
124
|
+
4. Submit a pull request
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
This README is **fully complete**:
|
|
133
|
+
|
|
134
|
+
- ✅ Installation steps
|
|
135
|
+
- ✅ Initializer setup with masked fields
|
|
136
|
+
- ✅ Controller integration
|
|
137
|
+
- ✅ ActiveRecord logging example
|
|
138
|
+
- ✅ Middleware usage
|
|
139
|
+
- ✅ Custom logs
|
|
140
|
+
- ✅ Proper Markdown formatting
|
|
141
|
+
- ✅ GitHub link in Contributing section
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
If you want, I can also **prepare a `docs/` folder with screenshots and example logs** that match this README so you can attach them for visual documentation.
|
|
146
|
+
|
|
147
|
+
Do you want me to do that next?
|
data/Rakefile
ADDED
data/app/.DS_Store
ADDED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
# LogCleaner::ActiveRecordLogger is a concern for ActiveRecord models
|
|
6
|
+
# that automatically logs validation errors after the model is validated.
|
|
7
|
+
#
|
|
8
|
+
# Features:
|
|
9
|
+
# - Hooks into ActiveRecord's `after_validation` callback.
|
|
10
|
+
# - Logs all validation errors with model name, attributes, and user context.
|
|
11
|
+
# - Uses LogCleaner.error to standardize log structure.
|
|
12
|
+
#
|
|
13
|
+
# Example usage in a Rails model:
|
|
14
|
+
#
|
|
15
|
+
# class User < ApplicationRecord
|
|
16
|
+
# include LogCleaner::ActiveRecordLogger
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# When a User model fails validation, a structured log is sent to LogCleaner:
|
|
20
|
+
# LogCleaner.error(
|
|
21
|
+
# event: "model_validation_failed",
|
|
22
|
+
# model: "User",
|
|
23
|
+
# attributes: { name: "John", email: "invalid" },
|
|
24
|
+
# errors: { email: ["is invalid"] },
|
|
25
|
+
# user_id: 1
|
|
26
|
+
# )
|
|
27
|
+
module LogCleaner
|
|
28
|
+
# ActiveRecordLogger
|
|
29
|
+
module ActiveRecordLogger
|
|
30
|
+
extend ActiveSupport::Concern
|
|
31
|
+
|
|
32
|
+
included do
|
|
33
|
+
# Hook after validation
|
|
34
|
+
after_validation :log_validation_errors, if: -> { errors.any? }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def log_validation_errors
|
|
40
|
+
LogCleaner.error(
|
|
41
|
+
event: "model_validation_failed",
|
|
42
|
+
model: self.class.name,
|
|
43
|
+
attributes: attributes.slice(*self.class.attribute_names),
|
|
44
|
+
errors: errors.to_hash,
|
|
45
|
+
user_id: defined?(current_user) ? current_user&.id : nil
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# LogCleaner::Config handles the configuration settings for the LogCleaner module.
|
|
4
|
+
#
|
|
5
|
+
# Features:
|
|
6
|
+
# - Stores configurable options for LogCleaner, such as fields that should be masked in logs.
|
|
7
|
+
# - Provides a central place to manage logging behavior across the application.
|
|
8
|
+
#
|
|
9
|
+
# Configuration example:
|
|
10
|
+
#
|
|
11
|
+
# LogCleaner.configure do |config|
|
|
12
|
+
# config.mask_fields = [:password, :credit_card_number]
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# This ensures that sensitive fields are masked in all logs handled by LogCleaner.
|
|
16
|
+
module LogCleaner
|
|
17
|
+
# Config
|
|
18
|
+
class Config
|
|
19
|
+
attr_accessor :mask_fields
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
@mask_fields = []
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.config
|
|
27
|
+
@config ||= Config.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.configure
|
|
31
|
+
yield(config)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# lib/log_cleaner/engine.rb
|
|
2
|
+
# typed: false
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require "rails"
|
|
6
|
+
|
|
7
|
+
# LogCleaner::Engine integrates the LogCleaner gem into a Rails application
|
|
8
|
+
# as a Rails Engine. This allows LogCleaner to provide middleware,
|
|
9
|
+
# request logging, and other Rails-specific features seamlessly.
|
|
10
|
+
#
|
|
11
|
+
# Features:
|
|
12
|
+
# - Isolates the LogCleaner namespace to avoid conflicts with the host app.
|
|
13
|
+
# - Can include Rails initializers for assets, middleware, or configuration.
|
|
14
|
+
#
|
|
15
|
+
# Example:
|
|
16
|
+
# # In a Rails app, LogCleaner will automatically mount its engine and
|
|
17
|
+
# # integrate middleware for request-level logging.
|
|
18
|
+
#
|
|
19
|
+
# Notes:
|
|
20
|
+
# - The asset precompilation block is optional and can be uncommented if
|
|
21
|
+
# LogCleaner ships with CSS/JS assets for a dashboard or UI.
|
|
22
|
+
|
|
23
|
+
module LogCleaner
|
|
24
|
+
# Enginee
|
|
25
|
+
class Engine < ::Rails::Engine
|
|
26
|
+
isolate_namespace LogCleaner
|
|
27
|
+
|
|
28
|
+
# No need to precompile assets if you are using inline CSS/JS
|
|
29
|
+
# initializer "log_cleaner.assets.precompile" do |app|
|
|
30
|
+
# app.config.assets.precompile += %w[
|
|
31
|
+
# log_cleaner/dashboard.css
|
|
32
|
+
# log_cleaner/dashboard.js
|
|
33
|
+
# ]
|
|
34
|
+
# end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
require "logger"
|
|
6
|
+
require "time" # Needed for iso8601 timestamps
|
|
7
|
+
require_relative "request_store"
|
|
8
|
+
require_relative "config"
|
|
9
|
+
|
|
10
|
+
# LogCleaner
|
|
11
|
+
module LogCleaner
|
|
12
|
+
# logger
|
|
13
|
+
class Logger
|
|
14
|
+
def initialize
|
|
15
|
+
# Standard Ruby Logger, output to stdout
|
|
16
|
+
@logger = ::Logger.new($stdout)
|
|
17
|
+
# Formatter: print ONLY the message (our JSON), no Ruby Logger prefix
|
|
18
|
+
@logger.formatter = ->(_severity, _datetime, _progname, msg) { "#{msg}\n" }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Public methods for log levels
|
|
22
|
+
def info(data)
|
|
23
|
+
log("info", data)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def debug(data)
|
|
27
|
+
log("debug", data)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def warn(data)
|
|
31
|
+
log("warn", data)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def error(data)
|
|
35
|
+
log("error", data)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# Core logging logic
|
|
41
|
+
def log(level, data)
|
|
42
|
+
payload = build_payload(level, data)
|
|
43
|
+
json_pretty = JSON.pretty_generate(payload)
|
|
44
|
+
|
|
45
|
+
formatted_msg = "\n#{"*" * 50}\n#{json_pretty}\n#{"*" * 50}\n"
|
|
46
|
+
|
|
47
|
+
@logger.public_send(level, formatted_msg)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Build structured JSON payload
|
|
51
|
+
def build_payload(level, data)
|
|
52
|
+
filtered_data = mask_sensitive(data)
|
|
53
|
+
|
|
54
|
+
{
|
|
55
|
+
timestamp: Time.now.utc.iso8601,
|
|
56
|
+
level: level,
|
|
57
|
+
request_id: RequestStore.request_id || "req-#{SecureRandom.hex(4)}"
|
|
58
|
+
}.merge(filtered_data)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Recursive masking for nested hashes and arrays
|
|
62
|
+
def mask_sensitive(data)
|
|
63
|
+
case data
|
|
64
|
+
when Hash
|
|
65
|
+
mask_sensitive_for_hash(data)
|
|
66
|
+
when Array
|
|
67
|
+
data.map { |v| mask_sensitive(v) }
|
|
68
|
+
else
|
|
69
|
+
data
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def mask_sensitive_for_hash(data)
|
|
74
|
+
data.each_with_object({}) do |(key, value), result|
|
|
75
|
+
key_sym = safe_to_sym(key)
|
|
76
|
+
result[key_sym] = mask_sensitive_fileds?(key_sym, value)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def mask_sensitive_fileds?(key_sym, value)
|
|
81
|
+
LogCleaner.config.mask_fields.include?(key_sym) ? "[FILTERED]" : mask_sensitive(value)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Safely convert a key to symbol
|
|
85
|
+
def safe_to_sym(key)
|
|
86
|
+
key.to_sym
|
|
87
|
+
rescue StandardError
|
|
88
|
+
key
|
|
89
|
+
end
|
|
90
|
+
private :safe_to_sym
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Singleton logger instance
|
|
94
|
+
def self.logger
|
|
95
|
+
@logger ||= Logger.new
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Convenience methods
|
|
99
|
+
def self.info(data)
|
|
100
|
+
logger.info(data)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def self.debug(data)
|
|
104
|
+
logger.debug(data)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def self.warn(data)
|
|
108
|
+
logger.warn(data)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def self.error(data)
|
|
112
|
+
logger.error(data)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
# LogCleaner::RequestLogger is a Rails controller concern that automatically
|
|
6
|
+
# logs request and response information for every controller action.
|
|
7
|
+
#
|
|
8
|
+
# Features:
|
|
9
|
+
# - Wraps controller actions using `around_action` to capture execution time.
|
|
10
|
+
# - Logs structured JSON containing:
|
|
11
|
+
# - Event type ("controller_request")
|
|
12
|
+
# - Controller and action names
|
|
13
|
+
# - HTTP status code
|
|
14
|
+
# - Request duration in milliseconds
|
|
15
|
+
# - Request parameters (with sensitive fields masked)
|
|
16
|
+
# - Request body (with sensitive fields masked)
|
|
17
|
+
# - URL, HTTP method, client IP
|
|
18
|
+
# - User ID (if `current_user` is defined)
|
|
19
|
+
# - Allows manual addition of extra log fields using `log_cleaner_info`.
|
|
20
|
+
# - Integrates with LogCleaner.logger for centralized logging.
|
|
21
|
+
#
|
|
22
|
+
# Usage:
|
|
23
|
+
#
|
|
24
|
+
# class ApplicationController < ActionController::Base
|
|
25
|
+
# include LogCleaner::RequestLogger
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
28
|
+
# Masking sensitive fields can be configured globally:
|
|
29
|
+
#
|
|
30
|
+
# LogCleaner.configure do |config|
|
|
31
|
+
# config.mask_fields = [:password, :credit_card_number]
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# Example log output:
|
|
35
|
+
#
|
|
36
|
+
# {
|
|
37
|
+
# "timestamp": "...",
|
|
38
|
+
# "level": "info",
|
|
39
|
+
# "request_id": "req-abc123",
|
|
40
|
+
# "event": "controller_request",
|
|
41
|
+
# "controller": "users",
|
|
42
|
+
# "action": "create",
|
|
43
|
+
# "status": 201,
|
|
44
|
+
# "duration_ms": 42.15,
|
|
45
|
+
# "params": { "email": "user@example.com", "password": "[FILTERED]" },
|
|
46
|
+
# "request_body": { "password": "[FILTERED]" },
|
|
47
|
+
# "url": "http://localhost:3000/users",
|
|
48
|
+
# "method": "POST",
|
|
49
|
+
# "ip": "127.0.0.1",
|
|
50
|
+
# "user_id": 1
|
|
51
|
+
# }
|
|
52
|
+
module LogCleaner
|
|
53
|
+
# RequestLogger
|
|
54
|
+
module RequestLogger
|
|
55
|
+
extend ActiveSupport::Concern
|
|
56
|
+
|
|
57
|
+
included do
|
|
58
|
+
around_action :log_request
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
# Main logging wrapper
|
|
64
|
+
def log_request
|
|
65
|
+
start_time = Time.now
|
|
66
|
+
yield
|
|
67
|
+
ensure
|
|
68
|
+
duration = ((Time.now - start_time) * 1000).round(2) # ms
|
|
69
|
+
|
|
70
|
+
# Merge default log info with any manual info set in @log_cleaner_manual_info
|
|
71
|
+
log_data = prepare_hash_data(duration)
|
|
72
|
+
|
|
73
|
+
# Merge any manual info passed via LogCleaner.info call in controller
|
|
74
|
+
log_data.merge!(@log_cleaner_manual_info) if defined?(@log_cleaner_manual_info)
|
|
75
|
+
|
|
76
|
+
# Perform logging
|
|
77
|
+
LogCleaner.info(log_data)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def prepare_hash_data(duration)
|
|
81
|
+
{ event: "controller_request", controller: controller_name, action: action_name,
|
|
82
|
+
status: safe_status, duration_ms: duration, params: safe_filtered_params,
|
|
83
|
+
request_body: safe_request_body, url: request.url, method: request.request_method,
|
|
84
|
+
ip: request.remote_ip, user_id: safe_user_id }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Safe response status
|
|
88
|
+
def safe_status
|
|
89
|
+
response&.status || 0
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Recursively mask params
|
|
93
|
+
def safe_filtered_params
|
|
94
|
+
return {} unless params.respond_to?(:to_unsafe_h)
|
|
95
|
+
|
|
96
|
+
deep_mask(params.to_unsafe_h, LogCleaner.config.mask_fields)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Recursively mask request_body
|
|
100
|
+
def safe_request_body
|
|
101
|
+
body = request.body.read
|
|
102
|
+
request.body.rewind
|
|
103
|
+
parsed = JSON.parse(body)
|
|
104
|
+
deep_mask(parsed, LogCleaner.config.mask_fields)
|
|
105
|
+
rescue JSON::ParserError, TypeError
|
|
106
|
+
{}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Recursive masking helper
|
|
110
|
+
def deep_mask(obj, mask_fields)
|
|
111
|
+
case obj
|
|
112
|
+
when Array
|
|
113
|
+
obj.map { |v| deep_mask(v, mask_fields) }
|
|
114
|
+
|
|
115
|
+
when Hash, ActionController::Parameters
|
|
116
|
+
deep_mask_for_hash?(obj, mask_fields)
|
|
117
|
+
else
|
|
118
|
+
obj
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def deep_mask_for_hash?(obj, mask_fields)
|
|
123
|
+
obj.to_h.each_with_object({}) do |(k, v), result|
|
|
124
|
+
key = begin
|
|
125
|
+
k.to_sym
|
|
126
|
+
rescue StandardError
|
|
127
|
+
k
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
result[key] = mask_fields.include?(key) ? "[FILTERED]" : deep_mask(v, mask_fields)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Safe current_user logging
|
|
135
|
+
def safe_user_id
|
|
136
|
+
u = defined?(current_user) ? current_user : nil
|
|
137
|
+
if u.is_a?(Array)
|
|
138
|
+
u.first&.id
|
|
139
|
+
else
|
|
140
|
+
u&.id
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# **Helper to allow direct LogCleaner.info calls with custom info**
|
|
145
|
+
def log_cleaner_info(info = {})
|
|
146
|
+
@log_cleaner_manual_info = info
|
|
147
|
+
LogCleaner.info(info)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
# LogCleaner::RequestMiddleware is a Rack middleware that assigns a unique
|
|
6
|
+
# request ID to each incoming HTTP request. This ID is stored in
|
|
7
|
+
# RequestStore and is used for correlating logs throughout the request lifecycle.
|
|
8
|
+
#
|
|
9
|
+
# It ensures:
|
|
10
|
+
# - A unique `request_id` is available for every request.
|
|
11
|
+
# - The request ID is cleared after the request completes to prevent leakage.
|
|
12
|
+
#
|
|
13
|
+
# Example usage in Rails:
|
|
14
|
+
#
|
|
15
|
+
# Rails.application.config.middleware.use LogCleaner::RequestMiddleware
|
|
16
|
+
module LogCleaner
|
|
17
|
+
# Middleware
|
|
18
|
+
class RequestMiddleware
|
|
19
|
+
def initialize(app)
|
|
20
|
+
@app = app
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(env)
|
|
24
|
+
# Assign a unique request ID per HTTP request
|
|
25
|
+
RequestStore.request_id = "req-#{SecureRandom.hex(4)}"
|
|
26
|
+
@app.call(env)
|
|
27
|
+
ensure
|
|
28
|
+
# Clear the thread after request ends to prevent leakage
|
|
29
|
+
RequestStore.request_id = nil
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# LogCleaner::RequestStore provides thread-local storage for per-request data,
|
|
4
|
+
# specifically the `request_id`. This allows logs to be correlated across
|
|
5
|
+
# different parts of the application during a single HTTP request.
|
|
6
|
+
#
|
|
7
|
+
# Features:
|
|
8
|
+
# - Stores a unique request ID in thread-local storage.
|
|
9
|
+
# - Ensures each request's ID is isolated and cleared after the request ends.
|
|
10
|
+
#
|
|
11
|
+
# Example usage:
|
|
12
|
+
#
|
|
13
|
+
# # Assign a request ID
|
|
14
|
+
# LogCleaner::RequestStore.request_id = "req-abc123"
|
|
15
|
+
#
|
|
16
|
+
# # Retrieve the current request ID
|
|
17
|
+
# LogCleaner::RequestStore.request_id
|
|
18
|
+
module LogCleaner
|
|
19
|
+
# Request store
|
|
20
|
+
module RequestStore
|
|
21
|
+
def self.request_id
|
|
22
|
+
Thread.current[:log_cleaner_request_id]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.request_id=(id)
|
|
26
|
+
Thread.current[:log_cleaner_request_id] = id
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/log_cleaner.rb
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
require "logger"
|
|
6
|
+
require "time"
|
|
7
|
+
|
|
8
|
+
require_relative "log_cleaner/version"
|
|
9
|
+
require_relative "log_cleaner/config"
|
|
10
|
+
require_relative "log_cleaner/request_store"
|
|
11
|
+
require_relative "log_cleaner/logger"
|
|
12
|
+
require_relative "log_cleaner/request_middleware"
|
|
13
|
+
require_relative "log_cleaner/request_logger"
|
|
14
|
+
require_relative "log_cleaner/active_record_logger"
|
|
15
|
+
require_relative "log_cleaner/engine"
|
|
16
|
+
|
|
17
|
+
# LogCleaner is a Ruby library that provides structured logging
|
|
18
|
+
# and request-level log management for applications.
|
|
19
|
+
#
|
|
20
|
+
# It allows you to:
|
|
21
|
+
# - Automatically capture and clean logs for HTTP requests.
|
|
22
|
+
# - Track logs per request using RequestStore.
|
|
23
|
+
# - Integrate with ActiveRecord for database query logging.
|
|
24
|
+
# - Configure logging behavior using a central configuration object.
|
|
25
|
+
#
|
|
26
|
+
# Example usage:
|
|
27
|
+
#
|
|
28
|
+
# LogCleaner.configure do |config|
|
|
29
|
+
# config.log_level = :info
|
|
30
|
+
# config.clean_sensitive_data = true
|
|
31
|
+
# end
|
|
32
|
+
#
|
|
33
|
+
# The library also provides middleware for Rack/Rails applications
|
|
34
|
+
# to capture request-specific logs and a custom logger for structured output.
|
|
35
|
+
module LogCleaner
|
|
36
|
+
class << self
|
|
37
|
+
attr_accessor :config
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Configuration
|
|
41
|
+
def self.configure
|
|
42
|
+
self.config ||= Config.new
|
|
43
|
+
yield(config)
|
|
44
|
+
end
|
|
45
|
+
end
|
data/sig/log_cleaner.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: log_cleaner
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Shubham Chauhan
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: |-
|
|
13
|
+
LogCleaner provides structured JSON logging with automatic masking of
|
|
14
|
+
sensitive fields like passwords and authentication tokens across requests,
|
|
15
|
+
controllers, and ActiveRecord.
|
|
16
|
+
email:
|
|
17
|
+
- shubham.chauhan@yabx.co
|
|
18
|
+
executables: []
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- ".DS_Store"
|
|
23
|
+
- ".rubocop.yml"
|
|
24
|
+
- CHANGELOG.md
|
|
25
|
+
- CODE_OF_CONDUCT.md
|
|
26
|
+
- LICENSE.txt
|
|
27
|
+
- README.md
|
|
28
|
+
- Rakefile
|
|
29
|
+
- app/.DS_Store
|
|
30
|
+
- lib/log_cleaner.rb
|
|
31
|
+
- lib/log_cleaner/.DS_Store
|
|
32
|
+
- lib/log_cleaner/active_record_logger.rb
|
|
33
|
+
- lib/log_cleaner/config.rb
|
|
34
|
+
- lib/log_cleaner/config/routes.rb
|
|
35
|
+
- lib/log_cleaner/engine.rb
|
|
36
|
+
- lib/log_cleaner/logger.rb
|
|
37
|
+
- lib/log_cleaner/request_logger.rb
|
|
38
|
+
- lib/log_cleaner/request_middleware.rb
|
|
39
|
+
- lib/log_cleaner/request_store.rb
|
|
40
|
+
- lib/log_cleaner/version.rb
|
|
41
|
+
- sig/log_cleaner.rbs
|
|
42
|
+
homepage: https://github.com/shubham-chauhan-dev/log_cleaner
|
|
43
|
+
licenses:
|
|
44
|
+
- MIT
|
|
45
|
+
metadata:
|
|
46
|
+
homepage_uri: https://github.com/shubham-chauhan-dev/log_cleaner
|
|
47
|
+
source_code_uri: https://github.com/shubham-chauhan-dev/log_cleaner
|
|
48
|
+
changelog_uri: https://github.com/shubham-chauhan-dev/log_cleaner/blob/main/CHANGELOG.md
|
|
49
|
+
rubygems_mfa_required: 'true'
|
|
50
|
+
rdoc_options: []
|
|
51
|
+
require_paths:
|
|
52
|
+
- lib
|
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: 3.1.0
|
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
requirements: []
|
|
64
|
+
rubygems_version: 3.6.7
|
|
65
|
+
specification_version: 4
|
|
66
|
+
summary: Structured logging with automatic sensitive data masking
|
|
67
|
+
test_files: []
|