philiprehberger-log_filter 0.1.2
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/CHANGELOG.md +22 -0
- data/LICENSE +21 -0
- data/README.md +117 -0
- data/lib/philiprehberger/log_filter/filter.rb +79 -0
- data/lib/philiprehberger/log_filter/presets.rb +30 -0
- data/lib/philiprehberger/log_filter/version.rb +7 -0
- data/lib/philiprehberger/log_filter/wrapper.rb +66 -0
- data/lib/philiprehberger/log_filter.rb +28 -0
- metadata +56 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3b4641d0484bf50c2cc4b567eae1af24be89009b8e85c74b9e603f10ec835f63
|
|
4
|
+
data.tar.gz: d79d7ff53b0fe31b17ead3ea34d05006c856408ee7c88bf7c6eedf6f4bca808e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 84988453f7ab48620e52056c05f41bae19a7963a8e008aef5e5e9320d92c5d5b7afb1697afeecfb6597c4a094f3090fcf84f7e3950e2459369d76f018576a098
|
|
7
|
+
data.tar.gz: 39aef2aa4d1f08094a5f77a91535ceadb09d6ff506b068ee4fc56b4dc9ae48840f6fb6fb0e973d03216fce40fd424d53cc8b1e91b501235f145e4eb030b51c2c
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this gem will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.2] - 2026-03-13
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- Fix RuboCop ExtraSpacing offense in gemspec metadata
|
|
14
|
+
|
|
15
|
+
## [0.1.0] - 2026-03-13
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Initial release
|
|
19
|
+
- `Filter` class with `#drop`, `#drop_if`, and `#replace` rules
|
|
20
|
+
- `Wrapper` class for wrapping Ruby Logger with filter support
|
|
21
|
+
- `Presets` module with `health_check`, `assets`, and `bots` factory methods
|
|
22
|
+
- Convenience methods on `Philiprehberger::LogFilter` module
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 philiprehberger
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# philiprehberger-log_filter
|
|
2
|
+
|
|
3
|
+
[](https://github.com/philiprehberger/rb-log-filter/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/philiprehberger-log_filter)
|
|
5
|
+
|
|
6
|
+
Pattern-based log filtering with drop, replace, and preset rules.
|
|
7
|
+
|
|
8
|
+
## Requirements
|
|
9
|
+
|
|
10
|
+
- Ruby >= 3.1
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Add to your Gemfile:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
gem "philiprehberger-log_filter"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then run:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
bundle install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or install directly:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
gem install philiprehberger-log_filter
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
require "philiprehberger/log_filter"
|
|
36
|
+
|
|
37
|
+
# Build a custom filter chain
|
|
38
|
+
filter = Philiprehberger::LogFilter::Filter.new
|
|
39
|
+
.drop(/health_?check/i)
|
|
40
|
+
.drop(/DEBUG/)
|
|
41
|
+
.replace(/password=\S+/, "password=[REDACTED]")
|
|
42
|
+
|
|
43
|
+
filter.apply("GET /healthcheck 200") # => nil (dropped)
|
|
44
|
+
filter.apply("DEBUG some noise") # => nil (dropped)
|
|
45
|
+
filter.apply("login password=abc123") # => "login password=[REDACTED]"
|
|
46
|
+
filter.apply("GET /api/users 200") # => "GET /api/users 200"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Wrapping a Logger
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
require "logger"
|
|
53
|
+
require "philiprehberger/log_filter"
|
|
54
|
+
|
|
55
|
+
logger = Logger.new($stdout)
|
|
56
|
+
filter = Philiprehberger::LogFilter::Filter.new
|
|
57
|
+
.drop(/healthcheck/i)
|
|
58
|
+
.replace(/token=\S+/, "token=[REDACTED]")
|
|
59
|
+
|
|
60
|
+
filtered_logger = Philiprehberger::LogFilter.wrap(logger, filter)
|
|
61
|
+
|
|
62
|
+
filtered_logger.info("GET /healthcheck 200") # silently dropped
|
|
63
|
+
filtered_logger.info("auth token=secret123") # logs "auth token=[REDACTED]"
|
|
64
|
+
filtered_logger.info("GET /api/users 200") # logs normally
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Using Presets
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# Drop health-check noise
|
|
71
|
+
filter = Philiprehberger::LogFilter.health_check_filter
|
|
72
|
+
filtered_logger = Philiprehberger::LogFilter.wrap(logger, filter)
|
|
73
|
+
|
|
74
|
+
# Drop static asset requests
|
|
75
|
+
filter = Philiprehberger::LogFilter.asset_filter
|
|
76
|
+
|
|
77
|
+
# Drop bot/crawler traffic
|
|
78
|
+
filter = Philiprehberger::LogFilter.bot_filter
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Block-Based Drop Rules
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
filter = Philiprehberger::LogFilter::Filter.new
|
|
85
|
+
.drop_if { |msg| msg.length > 1000 } # drop excessively long messages
|
|
86
|
+
.drop_if { |msg| msg.count("\n") > 10 } # drop multi-line spam
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## API
|
|
90
|
+
|
|
91
|
+
| Class / Method | Description |
|
|
92
|
+
|----------------|-------------|
|
|
93
|
+
| `Filter.new` | Create a new empty filter chain |
|
|
94
|
+
| `Filter#drop(pattern)` | Add a regex drop rule; returns self |
|
|
95
|
+
| `Filter#drop_if(&block)` | Add a block-based drop rule; returns self |
|
|
96
|
+
| `Filter#replace(pattern, replacement)` | Add a replacement rule; returns self |
|
|
97
|
+
| `Filter#apply(message)` | Run all rules; returns transformed string or nil |
|
|
98
|
+
| `Wrapper.new(logger, filter)` | Wrap a Logger with a filter |
|
|
99
|
+
| `Presets.health_check` | Filter dropping health-check paths |
|
|
100
|
+
| `Presets.assets` | Filter dropping static-asset requests |
|
|
101
|
+
| `Presets.bots` | Filter dropping bot/crawler traffic |
|
|
102
|
+
| `LogFilter.wrap(logger, filter)` | Convenience wrapper constructor |
|
|
103
|
+
| `LogFilter.health_check_filter` | Shortcut for `Presets.health_check` |
|
|
104
|
+
| `LogFilter.asset_filter` | Shortcut for `Presets.assets` |
|
|
105
|
+
| `LogFilter.bot_filter` | Shortcut for `Presets.bots` |
|
|
106
|
+
|
|
107
|
+
## Development
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
bundle install
|
|
111
|
+
bundle exec rspec # Run tests
|
|
112
|
+
bundle exec rubocop # Check code style
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
MIT
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module LogFilter
|
|
5
|
+
# Chain of rules that can drop or transform log messages.
|
|
6
|
+
#
|
|
7
|
+
# Rules are evaluated in the order they were added. A drop rule
|
|
8
|
+
# short-circuits and returns +nil+. A replace rule mutates the
|
|
9
|
+
# message string before passing it to the next rule.
|
|
10
|
+
class Filter
|
|
11
|
+
# @return [Array<Hash>] the ordered list of rules
|
|
12
|
+
attr_reader :rules
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@rules = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Add a pattern-based drop rule. Messages matching +pattern+ are suppressed.
|
|
19
|
+
#
|
|
20
|
+
# @param pattern [Regexp] the pattern to match against
|
|
21
|
+
# @return [self] for chaining
|
|
22
|
+
def drop(pattern)
|
|
23
|
+
@rules << { type: :drop_pattern, pattern: pattern }
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Add a block-based drop rule. Messages for which the block returns
|
|
28
|
+
# a truthy value are suppressed.
|
|
29
|
+
#
|
|
30
|
+
# @yield [message] evaluates whether the message should be dropped
|
|
31
|
+
# @yieldparam message [String]
|
|
32
|
+
# @yieldreturn [Boolean]
|
|
33
|
+
# @return [self] for chaining
|
|
34
|
+
def drop_if(&block)
|
|
35
|
+
@rules << { type: :drop_block, block: block }
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Add a replacement rule. Occurrences of +pattern+ in the message
|
|
40
|
+
# are replaced with +replacement+.
|
|
41
|
+
#
|
|
42
|
+
# @param pattern [Regexp] the pattern to match
|
|
43
|
+
# @param replacement [String] the replacement string
|
|
44
|
+
# @return [self] for chaining
|
|
45
|
+
def replace(pattern, replacement)
|
|
46
|
+
@rules << { type: :replace, pattern: pattern, replacement: replacement }
|
|
47
|
+
self
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Run all rules against +message+ in order.
|
|
51
|
+
#
|
|
52
|
+
# @param message [String] the log message to filter
|
|
53
|
+
# @return [String, nil] the transformed message, or +nil+ if dropped
|
|
54
|
+
def apply(message)
|
|
55
|
+
result = message.dup
|
|
56
|
+
|
|
57
|
+
@rules.each do |rule|
|
|
58
|
+
result = apply_rule(rule, result)
|
|
59
|
+
return nil if result.nil?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
result
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
# @param rule [Hash] a single rule hash
|
|
68
|
+
# @param message [String] the current message
|
|
69
|
+
# @return [String, nil]
|
|
70
|
+
def apply_rule(rule, message)
|
|
71
|
+
case rule[:type]
|
|
72
|
+
when :drop_pattern then message.match?(rule[:pattern]) ? nil : message
|
|
73
|
+
when :drop_block then rule[:block].call(message) ? nil : message
|
|
74
|
+
when :replace then message.gsub(rule[:pattern], rule[:replacement])
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module LogFilter
|
|
5
|
+
# Factory methods that return pre-configured {Filter} instances for
|
|
6
|
+
# common log-noise scenarios.
|
|
7
|
+
module Presets
|
|
8
|
+
# Filter that drops health-check request log lines.
|
|
9
|
+
#
|
|
10
|
+
# @return [Filter] a filter suppressing health-check paths
|
|
11
|
+
def self.health_check
|
|
12
|
+
Filter.new.drop(%r{health_?check|/health|/ping|/ready|/alive}i)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Filter that drops static-asset request log lines.
|
|
16
|
+
#
|
|
17
|
+
# @return [Filter] a filter suppressing asset paths
|
|
18
|
+
def self.assets
|
|
19
|
+
Filter.new.drop(/\.(css|js|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|map)\b/i)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Filter that drops bot/crawler request log lines.
|
|
23
|
+
#
|
|
24
|
+
# @return [Filter] a filter suppressing bot user-agents
|
|
25
|
+
def self.bots
|
|
26
|
+
Filter.new.drop(/bot|crawler|spider|slurp|googlebot|bingbot/i)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module LogFilter
|
|
5
|
+
# Wraps a Ruby Logger (or any object responding to the standard log
|
|
6
|
+
# level methods) and applies a {Filter} to every message before
|
|
7
|
+
# forwarding.
|
|
8
|
+
class Wrapper
|
|
9
|
+
LOG_LEVELS = %i[debug info warn error fatal].freeze
|
|
10
|
+
|
|
11
|
+
# @param logger [Logger] the underlying logger to delegate to
|
|
12
|
+
# @param filter [Filter] the filter to apply to messages
|
|
13
|
+
def initialize(logger, filter)
|
|
14
|
+
@logger = logger
|
|
15
|
+
@filter = filter
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
LOG_LEVELS.each do |level|
|
|
19
|
+
# @param message [String, nil] the log message
|
|
20
|
+
# @param args [Array] additional positional arguments
|
|
21
|
+
# @return [void]
|
|
22
|
+
define_method(level) do |message = nil, *args, &block|
|
|
23
|
+
message = block&.call if message.nil? && block
|
|
24
|
+
return if message.nil?
|
|
25
|
+
|
|
26
|
+
filtered = @filter.apply(message.to_s)
|
|
27
|
+
return if filtered.nil?
|
|
28
|
+
|
|
29
|
+
@logger.public_send(level, filtered, *args)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Integer] the current log level
|
|
34
|
+
def level
|
|
35
|
+
@logger.level
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param new_level [Integer, Symbol] the new log level
|
|
39
|
+
# @return [void]
|
|
40
|
+
def level=(new_level)
|
|
41
|
+
@logger.level = new_level
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Close the underlying logger.
|
|
45
|
+
#
|
|
46
|
+
# @return [void]
|
|
47
|
+
def close
|
|
48
|
+
@logger.close
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Delegate unknown methods to the underlying logger.
|
|
52
|
+
def method_missing(method_name, ...)
|
|
53
|
+
if @logger.respond_to?(method_name)
|
|
54
|
+
@logger.public_send(method_name, ...)
|
|
55
|
+
else
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
62
|
+
@logger.respond_to?(method_name, include_private) || super
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "log_filter/version"
|
|
4
|
+
require_relative "log_filter/filter"
|
|
5
|
+
require_relative "log_filter/wrapper"
|
|
6
|
+
require_relative "log_filter/presets"
|
|
7
|
+
|
|
8
|
+
module Philiprehberger
|
|
9
|
+
module LogFilter
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
def self.health_check_filter
|
|
13
|
+
Presets.health_check
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.asset_filter
|
|
17
|
+
Presets.assets
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.bot_filter
|
|
21
|
+
Presets.bots
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.wrap(logger, filter)
|
|
25
|
+
Wrapper.new(logger, filter)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: philiprehberger-log_filter
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Philip Rehberger
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-13 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Pattern-based log filtering — drop or transform log lines matching rules.
|
|
14
|
+
Includes preset filters for health checks, static assets, and bot traffic.
|
|
15
|
+
email:
|
|
16
|
+
- me@philiprehberger.com
|
|
17
|
+
executables: []
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- CHANGELOG.md
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- lib/philiprehberger/log_filter.rb
|
|
25
|
+
- lib/philiprehberger/log_filter/filter.rb
|
|
26
|
+
- lib/philiprehberger/log_filter/presets.rb
|
|
27
|
+
- lib/philiprehberger/log_filter/version.rb
|
|
28
|
+
- lib/philiprehberger/log_filter/wrapper.rb
|
|
29
|
+
homepage: https://github.com/philiprehberger/rb-log-filter
|
|
30
|
+
licenses:
|
|
31
|
+
- MIT
|
|
32
|
+
metadata:
|
|
33
|
+
homepage_uri: https://github.com/philiprehberger/rb-log-filter
|
|
34
|
+
source_code_uri: https://github.com/philiprehberger/rb-log-filter
|
|
35
|
+
changelog_uri: https://github.com/philiprehberger/rb-log-filter/blob/main/CHANGELOG.md
|
|
36
|
+
rubygems_mfa_required: 'true'
|
|
37
|
+
post_install_message:
|
|
38
|
+
rdoc_options: []
|
|
39
|
+
require_paths:
|
|
40
|
+
- lib
|
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - ">="
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: 3.1.0
|
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
|
+
requirements:
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: '0'
|
|
51
|
+
requirements: []
|
|
52
|
+
rubygems_version: 3.5.22
|
|
53
|
+
signing_key:
|
|
54
|
+
specification_version: 4
|
|
55
|
+
summary: Pattern-based log filtering with drop, replace, and preset rules
|
|
56
|
+
test_files: []
|