Class: Pinglish

Inherits:
Object
  • Object
show all
Defined in:
lib/pinglish.rb,
lib/pinglish/check.rb

Overview

This Rack middleware provides a “/_ping” endpoint for configurable system health checks. It’s intended to be consumed by machines.

Defined Under Namespace

Classes: Check, TooLong

Constant Summary collapse

HEADERS =

The HTTP headers sent for every response.

{
  "Content-Type" => "application/json; charset=UTF-8"
}

Instance Method Summary collapse

Constructor Details

#initialize(app, options = nil) {|_self| ... } ⇒ Pinglish

Create a new instance of the middleware wrapping ‘app`, with an optional `:path` (default: `“/_ping”`), `:max` timeout in seconds (default: `29`), and behavior `block`.

Yields:

  • (_self)

Yield Parameters:

  • _self (Pinglish)

    the object that the method was called on



25
26
27
28
29
30
31
32
33
34
# File 'lib/pinglish.rb', line 25

def initialize(app, options = nil, &block)
  options ||= {}

  @app    = app
  @checks = {}
  @max    = options[:max] || 29 # seconds
  @path   = options[:path] || "/_ping"

  yield self if block_given?
end

Instance Method Details

#call(env) ⇒ Object

Exercise the middleware, immediately delegating to the wrapped app unless the request path matches ‘path`.



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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/pinglish.rb', line 39

def call(env)
  request = Rack::Request.new env

  return @app.call env unless request.path_info == @path

  begin
    timeout @max do
      results  = {}

      @checks.values.each do |check|
        begin
          timeout check.timeout do
            results[check.name] = check.call
          end
        rescue StandardError => e
          results[check.name] = e
        end
      end

      failed      = results.values.any? { |v| failure? v }
      http_status = failed ? 503 : 200
      text_status = failed ? "failures" : "ok"

      data = {
        :now    => Time.now.to_i.to_s,
        :status => text_status
      }

      results.each do |name, value|

        # The unnnamed/default check doesn't contribute data.
        next if name.nil?

        if failure? value

          # If a check fails its name is added to a `failures` array.
          # If the check failed because it timed out, its name is
          # added to a `timeouts` array instead.

          key = timeout?(value) ? :timeouts : :failures
          (data[key] ||= []) << name

        elsif value

          # If the check passed and returned a value, the stringified
          # version of the value is returned under the `name` key.

          data[name] = value.to_s
        end
      end

      [http_status, HEADERS, [JSON.generate(data)]]
    end

  rescue Exception => ex

    # Something catastrophic happened. We can't even run the checks
    # and render a JSON response. Fall back on a pre-rendered string
    # and interpolate the current epoch time.

    now = Time.now.to_i.to_s
    [500, HEADERS, ['{"status":"failures","now":"' + now + '"}']]
  end
end

#check(name = nil, options = nil, &block) ⇒ Object

Add a new check with optional ‘name`. A `:timeout` option can be specified in seconds for checks that might take longer than the one second default. A previously added check with the same name will be replaced.



109
110
111
# File 'lib/pinglish.rb', line 109

def check(name = nil, options = nil, &block)
  @checks[name] = Check.new(name, options, &block)
end

#failure?(value) ⇒ Boolean

Does ‘value` represent a check failure? This default implementation returns `true` for any value that is an Exception or false. Subclasses can override this method for different behavior.

Returns:

  • (Boolean)


117
118
119
# File 'lib/pinglish.rb', line 117

def failure?(value)
  value.is_a?(Exception) || value == false
end

#timeout(seconds, &block) ⇒ Object

Raise Pinglish::TooLong after ‘seconds` has elapsed. This default implementation uses Ruby’s built-in Timeout class. Subclasses can override this method for different behavior, but any new implementation must raise Pinglish::TooLong when the timeout is exceeded or override ‘timeout?` appropriately.



127
128
129
# File 'lib/pinglish.rb', line 127

def timeout(seconds, &block)
  Timeout.timeout seconds, Pinglish::TooLong, &block
end

#timeout?(value) ⇒ Boolean

Does ‘value` represent a check timeout? Returns `true` for any value that is an instance of Pinglish::TooLong.

Returns:

  • (Boolean)


134
135
136
# File 'lib/pinglish.rb', line 134

def timeout?(value)
  value.is_a? Pinglish::TooLong
end