class Logster::RedisRateLimiter

Constants

BUCKETS
PREFIX

Attributes

callback[R]
duration[R]

Public Class Methods

clear_all(redis, redis_prefix = nil) click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 10
def self.clear_all(redis, redis_prefix = nil)
  prefix = key_prefix(redis_prefix)

  redis.eval "
  local keys = redis.call('keys', '*#{prefix}*')
  if (table.getn(keys) > 0) then
    redis.call('del', unpack(keys))
  end
  "
end
new(redis, severities, limit, duration, redis_prefix = nil, callback = nil) click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 21
def initialize(redis, severities, limit, duration, redis_prefix = nil, callback = nil)
  @severities = severities
  @limit = limit
  @duration = duration
  @callback = callback
  @redis_prefix = redis_prefix
  @redis = redis
  @bucket_range = @duration / BUCKETS
  @mget_keys = (0..(BUCKETS - 1)).map { |i| "#{key}:#{i}" }
end

Private Class Methods

key_prefix(redis_prefix) click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 83
def self.key_prefix(redis_prefix)
  if redis_prefix
    "#{redis_prefix.call}:#{PREFIX}"
  else
    PREFIX
  end

end

Public Instance Methods

callback_key() click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 77
def callback_key
  "#{key}:callback_triggered"
end
check(severity) click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 36
    def check(severity)
      return unless @severities.include?(severity)
      time = Time.now.to_i
      num = bucket_number(time)
      redis_key = "#{key}:#{num}"

      current_rate = @redis.eval <<-LUA
        local bucket_number = #{num}
        local bucket_count = redis.call("INCR", "#{redis_key}")

        if bucket_count == 1 then
          redis.call("EXPIRE", "#{redis_key}", "#{bucket_expiry(time)}")
          redis.call("DEL", "#{callback_key}")
        end

        local function retrieve_rate ()
          local sum = 0
          local values = redis.call("MGET", #{mget_keys(num)})
          for index, value in ipairs(values) do
            if value ~= false then sum = sum + value end
          end
          return sum
        end

        return (retrieve_rate() + bucket_count)
      LUA

      if !@redis.get(callback_key) && (current_rate >= @limit)
        @callback.call(current_rate) if @callback
        @redis.set(callback_key, 1)
      end

      current_rate
    end
key() click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 71
def key
  # "_LOGSTER_RATE_LIMIT:012:20:30"
  # Triggers callback when log levels of :debug, :info and :warn occurs 20 times within 30 secs
  "#{key_prefix}:#{@severities.join("")}:#{@limit}:#{@duration}"
end
retrieve_rate() click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 32
def retrieve_rate
  @redis.mget(@mget_keys).reduce(0) { |sum, value| sum + value.to_i }
end

Private Instance Methods

bucket_expiry(time) click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 106
def bucket_expiry(time)
  @duration - ((time % @duration) % @bucket_range)
end
bucket_number(time) click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 102
def bucket_number(time)
  (time % @duration) / @bucket_range
end
key_prefix() click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 92
def key_prefix
  self.class.key_prefix(@redis_prefix)
end
mget_keys(bucket_num) click to toggle source
# File lib/logster/redis_rate_limiter.rb, line 96
def mget_keys(bucket_num)
  keys = @mget_keys.dup
  keys.delete_at(bucket_num)
  keys.map { |key| "'#{key}'" }.join(', ')
end