File: core.rb

package info (click to toggle)
ruby-safely-block 0.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 128 kB
  • sloc: ruby: 107; makefile: 3
file content (67 lines) | stat: -rw-r--r-- 2,138 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# stdlib
require "digest"

# modules
require_relative "services"
require_relative "version"

module Safely
  class << self
    attr_accessor :raise_envs, :tag, :report_exception_method, :throttle_counter
    attr_writer :env

    def report_exception(e, tag: nil, context: {})
      tag = Safely.tag if tag.nil?
      if tag && e.message
        e = e.dup # leave original exception unmodified
        message = e.message
        e.define_singleton_method(:message) do
          "[#{tag == true ? "safely" : tag}] #{message}"
        end
      end
      if report_exception_method.arity == 1
        report_exception_method.call(e)
      else
        report_exception_method.call(e, context)
      end
    end

    def env
      @env ||= ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
    end

    def throttled?(e, options)
      return false unless options
      key = "#{options[:key] || Digest::MD5.hexdigest([e.class.name, e.message, e.backtrace.join("\n")].join("/"))}/#{(Time.now.to_i / options[:period]) * options[:period]}"
      throttle_counter.clear if throttle_counter.size > 1000 # prevent from growing indefinitely
      (throttle_counter[key] += 1) > options[:limit]
    end
  end

  self.tag = true
  self.report_exception_method = DEFAULT_EXCEPTION_METHOD
  self.raise_envs = %w(development test)
  # not thread-safe, but we don't need to be exact
  self.throttle_counter = Hash.new(0)

  module Methods
    def safely(tag: nil, sample: nil, except: nil, only: nil, silence: nil, throttle: false, default: nil, context: {})
      yield
    rescue *Array(only || StandardError) => e
      raise e if Array(except).any? { |c| e.is_a?(c) }
      raise e if Safely.raise_envs.include?(Safely.env)
      if sample ? rand < 1.0 / sample : true
        begin
          unless Array(silence).any? { |c| e.is_a?(c) } || Safely.throttled?(e, throttle)
            Safely.report_exception(e, tag: tag, context: context)
          end
        rescue => e2
          $stderr.puts "FAIL-SAFE #{e2.class.name}: #{e2.message}"
        end
      end
      default
    end
    alias_method :yolo, :safely
  end
  extend Methods
end