require 'json' module Logster module Middleware class Viewer PATH_INFO = "PATH_INFO".freeze SCRIPT_NAME = "SCRIPT_NAME".freeze REQUEST_METHOD = "REQUEST_METHOD".freeze def initialize(app) @app = app @logs_path = Logster.config.subdirectory || "/logs" @path_regex = Regexp.new("^(#{@logs_path}$)|^(#{@logs_path}(/.*))$") @store = Logster.store or raise ArgumentError.new("store") @assets_path = File.expand_path("../../../../assets", __FILE__) @fileserver = Rack::File.new(@assets_path) end def call(env) path = env[PATH_INFO] script_name = env[SCRIPT_NAME] if script_name && script_name.length > 0 path = script_name + path end if resource = resolve_path(path) # easier to debug, call per line unless Logster.config.authorize_callback.call(env) return @app.call(env) end if resource =~ /\.js$|\.handlebars$|\.css$/ env[PATH_INFO] = resource @fileserver.call(env) elsif resource.start_with?("/messages.json") serve_messages(Rack::Request.new(env)) elsif resource =~ /\/(un)?protect\/([0-9a-f]+)$/ off = $1 == "un" key = $2 message = Logster.store.get(key) unless message return [404, {}, ["Message not found"]] end if off if Logster.store.unprotect(key) return [301, {"Location" => "#{@logs_path}/show/#{key}?protected=false"}, []] else return [500, {}, ["Failed"]] end else if Logster.store.protect(key) return [301, {"Location" => "#{@logs_path}/show/#{key}?protected=true"}, []] else return [500, {}, ["Failed"]] end end elsif resource =~ /\/show\/([0-9a-f]+)(\.json)?$/ key = $1 json = $2 == ".json" message = Logster.store.get(key) unless message return [404, {}, ["Message not found"]] end if json [200, {"Content-Type" => "application/json; charset=utf-8"}, [message.to_json]] else preload = preload_json({"/show/#{key}.json" => message}) [200, {"Content-Type" => "text/html; charset=utf-8"}, [body(preload)]] end elsif resource == "/" [200, {"Content-Type" => "text/html; charset=utf-8"}, [body(preload_json)]] else [404, {}, ["Not found"]] end else @app.call(env) end end protected def serve_messages(req) opts = { before: req["before"], after: req["after"] } if(filter = req["filter"]) filter = filter.split("_").map{|s| s.to_i} opts[:severity] = filter end if search = req["search"] search = (parse_regex(search) || search) if req["regex_search"] == "true" opts[:search] = search end payload = { messages: @store.latest(opts), total: @store.count } json = JSON.generate(payload) [200, {"Content-Type" => "application/json"}, [json]] end def parse_regex(string) if string =~ /\/(.+)\/(.*)/ s = $1 flags = Regexp::IGNORECASE if $2 && $2.include?("i") Regexp.new(s, flags) rescue nil end end def resolve_path(path) if path =~ @path_regex $3 || "/" end end def preload_json(extra={}) values = {} values.merge!(extra) end def css(name, attrs={}) attrs = attrs.map do |k,v| "#{k}='#{v}'" end.join(" ") "" end def script(prod, dev=nil) name = ENV['DEBUG_JS'] == "1" && dev ? dev : prod "" end def component(name) ember_template("components/#{name}", "components/" << name) end def handlebars(name) ember_template("templates/#{name}", name) end def ember_template(location, name) val = File.read("#{@assets_path}/javascript/#{location}.handlebars") < Ember.TEMPLATES[#{name.inspect}] = Ember.Handlebars.compile(#{val.inspect}); JS end def body(preload) < #{css("app.css")} #{script("external/moment.min.js")} #{script("external/jquery.min.js")} #{script("external/handlebars.min.js")} #{script("external/lodash.min.js")} #{script("external/ember.min.js", "external/ember.js")} #{handlebars("application")} #{handlebars("index")} #{handlebars("message")} #{handlebars("show")} #{component("message-info")} #{component("tabbed-section")} #{component("tab-contents")} #{component("tab-link")} #{script("app.js")} HTML end end end end