lib/statusz.rb in statusz-0.0.4 vs lib/statusz.rb in statusz-0.1.0.pre

- old
+ new

@@ -1,11 +1,14 @@ require "cgi" require "erb" require "time" require "json" +require "rack" +# Statusz is a tool for displaying deploy-time and runtime server information. module Statusz + # @private FIELD_TO_SCRAPING_PROC = { "git directory" => Proc.new { |commit| `git rev-parse --show-toplevel`.strip.rpartition("/").last }, "latest commit" => Proc.new { |commit| `git log --pretty=%H #{commit} -n 1`.strip }, "containing branches" => Proc.new do |commit| `git branch --contains #{commit}`.strip.gsub("* ", "").gsub("\n", ", ") @@ -16,10 +19,18 @@ "#{`git config --get user.name`.strip} <#{`git config --get user.email`.strip}>" end, "all commits" => Proc.new { |commit| `git log --pretty=%H #{commit}`.strip } } + # Write out a statusz file. This should be done at deployment time. + # + # @param [String] filename the output filename. + # @param [Hash] options the options for the output. + # @option options [String] :commit The git commit for which to to output deploy information (default: HEAD). + # @option options [Symbol] :format The output format (one of `:html`, `:text`, or `:json`). Default: `:html + # @option options [Array] :fields The fields to include in the output. Default: all fields. + # @option options [Hash] :extra_fields A hash of extra key/value pairs to include in the output. def self.write_file(filename = "./statusz.html", options = {}) options[:commit] ||= "HEAD" options[:format] ||= :html raise "Bad format: #{options[:format]}" unless [:html, :text, :json].include? options[:format] options[:fields] ||= FIELD_TO_SCRAPING_PROC.keys @@ -34,22 +45,92 @@ options[:fields].each do |field| results[field] = FIELD_TO_SCRAPING_PROC[field].call(options[:commit]) end extra_fields.each { |field, value| results[field.to_s] = value.to_s } - case options[:format] + File.open(filename, "w") { |file| file.puts(render(results, options[:format])) } + end + + # @private + def self.render(fields, format) + case format when :text - output = results.map { |name, value| "#{name}:\n#{value}" }.join("\n\n") + fields.map { |name, value| "#{name}:\n#{value}" }.join("\n\n") when :json - output = results.to_json + fields.to_json when :html - html_values = results.reduce({}) do |hash, field_and_value| - field, value = field_and_value + html_values = fields.reduce({}) do |h, (field, value)| pair = (field == "all commits") ? { field => value.split("\n") } : { field => CGI.escapeHTML(value) } - hash.merge pair + h.merge pair end - output = ERB.new(File.read(File.join(File.dirname(__FILE__), "statusz.erb"))).result(binding) + ERB.new(File.read(File.join(File.dirname(__FILE__), "statusz.erb"))).result(binding) end + end - File.open(filename, "w") { |file| file.puts output } + # @private + def self.load_json_info(filename) + fields = {} + unless filename.nil? + unless File.file? filename + raise "No such file: #{filename}." + end + begin + fields = JSON.parse(File.read(filename)) + rescue StandardError => error + raise "Error reading json file #{filename}: #{error.message}." + end + unless fields.is_a? Hash + raise "Error: malformed statusz json file: #{filename}." + end + end + fields + end + + # If you wrote out a json file at deploy time, you can use this at runtime to turn the json file into any of + # statusz's supported formats (html, json, text) and add additional runtime values. + # + # @param [String] filename the json statusz file written at deploy time. If `filename` is `nil`, then + # statusz will output an html file containing only the fields in `extra_fields`. + # @param [Symbol] format then output format (one of `:html`, `:json`, `:text`). Defaults to `:html`. + # @param [Hash] extra_fields the extra key/value pairs to include in the output. + def self.render_from_json(filename = "./statusz.json", format = :html, extra_fields = {}) + raise "Bad format: #{format}" unless [:html, :text, :json].include? format + fields = load_json_info(filename) + fields.merge! extra_fields + render(fields, format) + end + + # A Rack server that can serve statusz. + class Server + # Set up the Statusz::Server Rack app. + # + # @param [String] filename the json statusz file written at deploy time. If `filename` is `nil`, then + # statusz will output an html file containing only the fields in `extra_fields`. + # @param [Hash] extra_fields extra key/value pairs to include in the output. + def initialize(filename = "./statusz.json", extra_fields = {}) + @filename = filename + @extra_fields = extra_fields + end + + # The usual Rack app call method. + def call(env) + headers = {} + path = Rack::Request.new(env).path + if path =~ /\.json$/ + headers["Content-Type"] = "application/json" + format = :json + elsif path =~ /\.txt$/ + headers["Content-Type"] = "text/plain" + format = :text + else + headers["Content-Type"] = "text/html" + format = :html + end + begin + body = Statusz.render_from_json(@filename, format, @extra_fields) + rescue StandardError => error + return [500, { "Content-Type" => "text/plain" }, ["Error with statusz:\n#{error.message}"]] + end + [200, headers, [body]] + end end end