Class: Philiprehberger::LogFilter::Filter

Inherits:
Object
  • Object
show all
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

ChainedFilter

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeFilter

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

#rulesArray<Hash> (readonly)

Returns the ordered list of rules.

Returns:

  • (Array<Hash>)

    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.

Parameters:

  • message (String)

    the log message to filter

Returns:

  • (String, nil)

    the transformed message, or nil if dropped



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(message)
  result = message.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.

Parameters:

  • other (Filter)

    the filter to run after this one

Returns:

  • (Filter)

    a new filter composing self and other

Raises:



172
173
174
175
176
# File 'lib/philiprehberger/log_filter/filter.rb', line 172

def chain(other)
  raise ArgumentError, 'other must be a Philiprehberger::LogFilter::Filter' unless other.is_a?(Filter)

  ChainedFilter.new(self, other)
end

#drop(pattern) ⇒ self

Add a pattern-based drop rule. Messages matching pattern are suppressed.

Parameters:

  • pattern (Regexp)

    the pattern to match against

Returns:

  • (self)

    for chaining



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.

Parameters:

  • key (String)

    the JSON field key to remove

Returns:

  • (self)

    for chaining



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.

Yields:

  • (message)

    evaluates whether the message should be dropped

Yield Parameters:

  • message (String)

Yield Returns:

  • (Boolean)

Returns:

  • (self)

    for chaining



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

#mask_field(key, with: '***') ⇒ self

Add a rule to mask a field value in JSON log messages. Non-JSON messages pass through unmodified.

Parameters:

  • key (String)

    the JSON field key to mask

  • with (String) (defaults to: '***')

    the mask replacement value

Returns:

  • (self)

    for chaining



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.

Parameters:

  • pattern (Regexp)

    the pattern to match

  • replacement (String)

    the replacement string

Returns:

  • (self)

    for chaining



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.

Parameters:

  • pattern (Regexp)

    the pattern to match

  • rate (Float)

    sampling rate between 0.0 and 1.0

Returns:

  • (self)

    for chaining

Raises:

  • (ArgumentError)


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

#statsHash

Return current filter statistics.

Returns:

  • (Hash)

    counters for :dropped, :passed, :replaced, :sampled



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.

Yields:

  • (message)

    receives the current message

Yield Parameters:

  • message (String)

Returns:

  • (self)

    for chaining



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.

Parameters:

  • max_length (Integer)

    the maximum length of the message in characters

  • suffix (String) (defaults to: '…')

    the string appended when truncation occurs

Returns:

  • (self)

    for chaining

Raises:

  • (ArgumentError)

    if max_length is not a positive Integer



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