philiprehberger-log_filter 0.2.1 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e2ec105d85156aebf2097c6c73be344c4b9247897bb6fb32b9c90e87c4f7ab6e
4
- data.tar.gz: 6c5bcfde597acdfb0627c8c246ca7cda3319ec7d82c077292db1deed05c6921a
3
+ metadata.gz: d87e6f862d6fe0d39c516f49c18c2aecf3917d28fbfb9a074a0f9a06ce627c33
4
+ data.tar.gz: c3e42d136400a6a0c54f8ff7b342a9340c0b34171e368718236c1043858a32bf
5
5
  SHA512:
6
- metadata.gz: f91e15c938b5b48ce92d9d1563046f2493774d4e23e3a09b091dcd7febaeef0492daae13061c6b50c3277b90bef2192b09ec3bbdc63ebf163426863b6952ba34
7
- data.tar.gz: 03dad2354f01da5eb2a0339c25731b82b6d7c1e5b2557d092ac17c1952148d9d44c5dd85b75621a31aea1ee86ad542918a4e2e26fefdae8be4e468a6c442abb4
6
+ metadata.gz: 884ed562f6e2f290dcb92689273bc9785db1a7523b3da0c139e70e10c774e9558fc0840e8d1664ffacd48a2706a2b037cf0c4ac7cb9d71cbfdc76e9c03db8253
7
+ data.tar.gz: da68ea62830a8e198194bd13daaaf9a4604fcec21284597f0b21e2de13bb32d2f1e493b349a2a360e5e07368814c2794d8f24055e025dccaaffa882aaa3d1b65
data/CHANGELOG.md CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-04-17
11
+
12
+ ### Added
13
+ - `Filter#chain(other)` composes two filters into a new filter that pipes events through both in order (drops short-circuit)
14
+
10
15
  ## [0.2.1] - 2026-03-31
11
16
 
12
17
  ### Changed
data/README.md CHANGED
@@ -114,6 +114,30 @@ filter.apply('{"user":"alice","password":"secret","ssn":"123-45-6789"}')
114
114
  filter.apply("plain text log line") # => "plain text log line"
115
115
  ```
116
116
 
117
+ ### Chaining filters
118
+
119
+ Compose two filters into a new filter whose `apply` pipes each event through
120
+ the first filter and then through the second. If the first filter drops the
121
+ event (returns `nil`), the second is skipped. Composition is associative, so
122
+ `a.chain(b).chain(c)` works as expected.
123
+
124
+ ```ruby
125
+ require "philiprehberger/log_filter"
126
+
127
+ redact = Philiprehberger::LogFilter::Filter.new
128
+ .replace(/password=\S+/, "password=[REDACTED]")
129
+
130
+ drop_debug = Philiprehberger::LogFilter::Filter.new
131
+ .drop(/DEBUG/)
132
+
133
+ pipeline = drop_debug.chain(redact)
134
+
135
+ pipeline.apply("DEBUG noise") # => nil (dropped by first filter)
136
+ pipeline.apply("login password=abc123") # => "login password=[REDACTED]"
137
+ ```
138
+
139
+ The chained filter tracks its own `stats` independently of the source filters.
140
+
117
141
  ### Filter Statistics
118
142
 
119
143
  ```ruby
@@ -144,6 +168,7 @@ filter.stats # => { dropped: 0, passed: 0, replaced: 0, sampled: 0 }
144
168
  | `Filter#drop_field(key)` | Remove a field from JSON log messages; returns self |
145
169
  | `Filter#mask_field(key, with:)` | Mask a field value in JSON log messages; returns self |
146
170
  | `Filter#apply(message)` | Run all rules; returns transformed string or nil |
171
+ | `Filter#chain(other)` | Compose with another filter; returns a new filter piping events through both |
147
172
  | `Filter#stats` | Return counters: dropped, passed, replaced, sampled |
148
173
  | `Filter#reset_stats!` | Zero all statistics counters |
149
174
  | `Wrapper.new(logger, filter)` | Wrap a Logger with a filter |
@@ -121,6 +121,27 @@ module Philiprehberger
121
121
  result
122
122
  end
123
123
 
124
+ # Compose this filter with +other+ into a new filter.
125
+ #
126
+ # The returned filter's +apply+ runs +self.apply+ first and passes the
127
+ # result through +other.apply+. If the first filter drops the message
128
+ # (returns +nil+), the second filter is not invoked and the chained
129
+ # apply returns +nil+. Composition is associative, so
130
+ # +a.chain(b).chain(c)+ behaves transitively.
131
+ #
132
+ # The chained filter tracks its own +stats+ independently of the two
133
+ # source filters; each source filter continues to track its own
134
+ # counters when invoked.
135
+ #
136
+ # @param other [Filter] the filter to run after this one
137
+ # @return [Filter] a new filter composing +self+ and +other+
138
+ # @raise [ArgumentError] if +other+ is not a {Filter}
139
+ def chain(other)
140
+ raise ArgumentError, 'other must be a Philiprehberger::LogFilter::Filter' unless other.is_a?(Filter)
141
+
142
+ ChainedFilter.new(self, other)
143
+ end
144
+
124
145
  private
125
146
 
126
147
  # @param stat [Symbol] the stat key to increment
@@ -205,5 +226,42 @@ module Philiprehberger
205
226
  nil
206
227
  end
207
228
  end
229
+
230
+ # A filter produced by {Filter#chain} that pipes events through two
231
+ # source filters in order. Tracks its own stats independently of the
232
+ # inputs. Short-circuits when the first filter drops the event.
233
+ #
234
+ # @api private
235
+ class ChainedFilter < Filter
236
+ # @param first [Filter] the filter to run first
237
+ # @param second [Filter] the filter to run on +first+'s output
238
+ def initialize(first, second)
239
+ super()
240
+ @first = first
241
+ @second = second
242
+ end
243
+
244
+ # Run +first.apply+ then +second.apply+. If the first returns +nil+,
245
+ # the second is skipped and +nil+ is returned.
246
+ #
247
+ # @param message [String] the log message to filter
248
+ # @return [String, nil] the transformed message, or +nil+ if dropped
249
+ def apply(message)
250
+ intermediate = @first.apply(message)
251
+ if intermediate.nil?
252
+ increment_stat(:dropped)
253
+ return nil
254
+ end
255
+
256
+ result = @second.apply(intermediate)
257
+ if result.nil?
258
+ increment_stat(:dropped)
259
+ return nil
260
+ end
261
+
262
+ increment_stat(:passed)
263
+ result
264
+ end
265
+ end
208
266
  end
209
267
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module LogFilter
5
- VERSION = '0.2.1'
5
+ VERSION = '0.3.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-log_filter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Rehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-31 00:00:00.000000000 Z
11
+ date: 2026-04-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Pattern-based log filtering — drop or transform log lines matching rules.
14
14
  Includes preset filters for health checks, static assets, and bot traffic.
@@ -26,11 +26,11 @@ files:
26
26
  - lib/philiprehberger/log_filter/presets.rb
27
27
  - lib/philiprehberger/log_filter/version.rb
28
28
  - lib/philiprehberger/log_filter/wrapper.rb
29
- homepage: https://github.com/philiprehberger/rb-log-filter
29
+ homepage: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-log_filter
30
30
  licenses:
31
31
  - MIT
32
32
  metadata:
33
- homepage_uri: https://github.com/philiprehberger/rb-log-filter
33
+ homepage_uri: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-log_filter
34
34
  source_code_uri: https://github.com/philiprehberger/rb-log-filter
35
35
  changelog_uri: https://github.com/philiprehberger/rb-log-filter/blob/main/CHANGELOG.md
36
36
  bug_tracker_uri: https://github.com/philiprehberger/rb-log-filter/issues