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 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
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: double_quotes
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
@@ -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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
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,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ LogCleaner::Engine.routes.draw do
4
+ root to: "dashboard#index"
5
+ 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
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Gem Version
4
+ module LogCleaner
5
+ VERSION = "0.1.0"
6
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ module LogCleaner
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []