Class: Philiprehberger::LogFilter::Filter
- Inherits:
-
Object
- Object
- Philiprehberger::LogFilter::Filter
- Defined in:
- lib/philiprehberger/log_filter/filter.rb
Overview
Chain of rules that can drop or transform log messages.
Rules are evaluated in the order they were added. A drop rule short-circuits and returns nil. A replace rule mutates the message string before passing it to the next rule.
Direct Known Subclasses
Instance Attribute Summary collapse
-
#rules ⇒ Array<Hash>
readonly
The ordered list of rules.
Instance Method Summary collapse
-
#apply(message) ⇒ String?
Run all rules against
messagein order. -
#chain(other) ⇒ Filter
Compose this filter with
otherinto a new filter. -
#describe_rules ⇒ Array<Hash{Symbol=>Object}>
Return a human-readable description of every rule in the chain, in declaration order.
-
#drop(pattern) ⇒ self
Add a pattern-based drop rule.
-
#drop_field(key) ⇒ self
Add a rule to remove a field from JSON log messages.
-
#drop_if {|message| ... } ⇒ self
Add a block-based drop rule.
-
#explain(message) ⇒ Hash
Run
messagethrough the chain WITHOUT mutating stats and WITHOUT invoking anytap_eachblocks. -
#initialize ⇒ Filter
constructor
A new instance of Filter.
-
#mask_field(key, with: '***') ⇒ self
Add a rule to mask a field value in JSON log messages.
-
#replace(pattern, replacement) ⇒ self
Add a replacement rule.
-
#reset_stats! ⇒ void
Reset all statistics counters to zero.
-
#sample(pattern, rate:) ⇒ self
Add a sampling rule.
-
#stats ⇒ Hash
Return current filter statistics.
-
#tap_each {|message| ... } ⇒ self
Add a side-effecting inspection rule.
-
#truncate(max_length, suffix: '…') ⇒ self
Add a rule that caps outgoing messages at
max_lengthcharacters, appendingsuffixwhen truncation occurred.
Constructor Details
#initialize ⇒ Filter
Returns a new instance of Filter.
17 18 19 20 21 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 17 def initialize @rules = [] @mutex = Mutex.new @stats = { dropped: 0, passed: 0, replaced: 0, sampled: 0 } end |
Instance Attribute Details
#rules ⇒ Array<Hash> (readonly)
Returns the ordered list of rules.
15 16 17 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 15 def rules @rules end |
Instance Method Details
#apply(message) ⇒ String?
Run all rules against message in order.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 142 def apply() result = .dup @rules.each do |rule| result = apply_rule(rule, result) if result.nil? increment_stat(:dropped) return nil end end increment_stat(:passed) result end |
#chain(other) ⇒ Filter
Compose this filter with other into a new filter.
The returned filter’s apply runs self.apply first and passes the result through other.apply. If the first filter drops the message (returns nil), the second filter is not invoked and the chained apply returns nil. Composition is associative, so a.chain(b).chain© behaves transitively.
The chained filter tracks its own stats independently of the two source filters; each source filter continues to track its own counters when invoked.
221 222 223 224 225 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 221 def chain(other) raise ArgumentError, 'other must be a Philiprehberger::LogFilter::Filter' unless other.is_a?(Filter) ChainedFilter.new(self, other) end |
#describe_rules ⇒ Array<Hash{Symbol=>Object}>
Return a human-readable description of every rule in the chain, in declaration order. Useful for debugging, logging, or rendering the filter configuration in an admin UI.
Does not mutate any state and does not invoke any rule blocks.
166 167 168 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 166 def describe_rules @rules.map { |r| { type: r[:type], description: describe_rule(r) } } end |
#drop(pattern) ⇒ self
Add a pattern-based drop rule. Messages matching pattern are suppressed.
27 28 29 30 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 27 def drop(pattern) @rules << { type: :drop_pattern, pattern: pattern } self end |
#drop_field(key) ⇒ self
Add a rule to remove a field from JSON log messages. Non-JSON messages pass through unmodified.
73 74 75 76 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 73 def drop_field(key) @rules << { type: :drop_field, key: key.to_s } self end |
#drop_if {|message| ... } ⇒ self
Add a block-based drop rule. Messages for which the block returns a truthy value are suppressed.
39 40 41 42 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 39 def drop_if(&block) @rules << { type: :drop_block, block: block } self end |
#explain(message) ⇒ Hash
Run message through the chain WITHOUT mutating stats and WITHOUT invoking any tap_each blocks. Returns a trace describing what each rule did to the message, plus the final transformed value (or nil if the chain would have dropped it).
Intended for debugging filter configurations. Because :sample rules are stochastic, this method treats a sample rule as a deterministic sampled_in when its pattern matches (it shows the path the message would take if the sample passed). This makes explain deterministic and side-effect-free.
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 189 def explain() decisions = [] result = .dup @rules.each_with_index do |rule, idx| decision, new_result = explain_rule(rule, result, idx) decisions << decision if decision[:action] == :dropped return { result: nil, decisions: decisions } end result = new_result end { result: result, decisions: decisions } end |
#mask_field(key, with: '***') ⇒ self
Add a rule to mask a field value in JSON log messages. Non-JSON messages pass through unmodified.
84 85 86 87 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 84 def mask_field(key, with: '***') @rules << { type: :mask_field, key: key.to_s, mask: with } self end |
#replace(pattern, replacement) ⇒ self
Add a replacement rule. Occurrences of pattern in the message are replaced with replacement.
50 51 52 53 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 50 def replace(pattern, replacement) @rules << { type: :replace, pattern: pattern, replacement: replacement } self end |
#reset_stats! ⇒ void
This method returns an undefined value.
Reset all statistics counters to zero.
132 133 134 135 136 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 132 def reset_stats! @mutex.synchronize do @stats = { dropped: 0, passed: 0, replaced: 0, sampled: 0 } end end |
#sample(pattern, rate:) ⇒ self
Add a sampling rule. Only pass through rate fraction of messages matching pattern. Non-matching messages pass through unaffected.
61 62 63 64 65 66 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 61 def sample(pattern, rate:) raise ArgumentError, 'rate must be between 0.0 and 1.0' unless rate.is_a?(Numeric) && rate >= 0.0 && rate <= 1.0 @rules << { type: :sample, pattern: pattern, rate: rate.to_f } self end |
#stats ⇒ Hash
Return current filter statistics.
125 126 127 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 125 def stats @mutex.synchronize { @stats.dup } end |
#tap_each {|message| ... } ⇒ self
Add a side-effecting inspection rule. The block is called with every message that reaches this stage of the pipeline (after any previous transforms applied). The message is then passed through unchanged —the block’s return value is ignored. Exceptions raised inside the block are not swallowed.
Useful for counting, metrics emission, or attaching to a debugger without altering the filter output.
101 102 103 104 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 101 def tap_each(&block) @rules << { type: :tap, block: block } self end |
#truncate(max_length, suffix: '…') ⇒ self
Add a rule that caps outgoing messages at max_length characters, appending suffix when truncation occurred. Messages shorter than or equal to max_length pass through unchanged. Never drops a message, only transforms it.
115 116 117 118 119 120 |
# File 'lib/philiprehberger/log_filter/filter.rb', line 115 def truncate(max_length, suffix: '…') raise ArgumentError, 'max_length must be a positive Integer' unless max_length.is_a?(Integer) && max_length.positive? @rules << { type: :truncate, max_length: max_length, suffix: suffix } self end |