Created
May 15, 2013 03:32
-
-
Save anonymous/5581474 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Camelog is a Ruby gem that helps Rails developers to browse easily in the application logs. | |
# It works as a live log file parser, so it's completely independent from Rails. | |
# Allow to choice what kind of request you want to see. | |
# It's in early development, so many things will change and many features are comming soon. | |
# Assets requests is also a GET, but Camelog calls it ASSET. | |
# By default it shows all requests ignoring ASSETS. | |
# require 'thread' allows me to use Queue.new. | |
# 'file-tail' is to "tail" the Rails log file. | |
# 'io/console' will be used to track user's keyboard. | |
# 'camelog/request' defines the request object used in this class. | |
require 'thread' | |
require 'file-tail' | |
require 'io/console' | |
require 'camelog/request' | |
# Should pass the application path in the initialization and then call run. | |
# Four loops will run in parallel using Threads. | |
# listen_keyboard, wait_command, stream_log and stream_parsed_log (main). | |
class Camelog | |
def initialize(app_path) | |
# Finds the application log path and set the default @types_to_show | |
# help and exit if the user used the help in ARGV. | |
@app_path = app_path | |
@pow_log_file = app_path + "log/development.log" | |
@types_to_show = ['GET', 'POST', 'PUT', 'DELETE'] | |
true | |
end | |
def run | |
# Set the default fundamental attributes. | |
# Then start the threads. | |
@cached_request = nil | |
@requests_queue = Queue.new | |
@keyboard_queue = Queue.new | |
@all_requests = Array.new | |
Thread.new { listen_keyboard } | |
Thread.new { wait_command } | |
Thread.new { stream_log } | |
stream_parsed_log | |
end | |
def listen_keyboard | |
# Watch the user's keyboard while running. | |
# Every character goes to @keyboard_queue | |
loop do | |
char = STDIN.getch | |
@keyboard_queue << char if char | |
end | |
end | |
def wait_command | |
# Consumes @keyboard_queue and try to find a valid Camelog command. | |
# When it finds a command, set the respective types in @types_to_show. | |
# Once has a command it also ensures that stream_parsed_log will show the right requests. | |
# And refresh_display after that. | |
# last_command is used to prevent that the same command runs twice. | |
last_command = nil | |
loop do | |
char = @keyboard_queue.shift | |
if char != last_command | |
case char | |
when 'q' | |
show "\n" + "Exit." | |
show "" | |
exit | |
when 'a' | |
@types_to_show = ['GET', 'POST', 'PUT', 'DELETE', 'ASSET'] | |
command = true | |
when 'g' | |
@types_to_show = ['GET'] | |
command = true | |
when 'p' | |
@types_to_show = ['POST'] | |
command = true | |
when 'u' | |
@types_to_show = ['PUT'] | |
command = true | |
when 'd' | |
@types_to_show = ['DELETE'] | |
command = true | |
when 's' | |
@types_to_show = ['ASSET'] | |
command = true | |
when '/' | |
@types_to_show = ['GET', 'POST', 'PUT', 'DELETE'] | |
command = true | |
when 'h' | |
help | |
last_command = char | |
end | |
refresh_display if command | |
last_command = char if command | |
command = false | |
end | |
end | |
end | |
def refresh_display | |
# This method is called when the user press a new command. | |
# It will refresh the console screen with the requests of the user's choice type. | |
# If no requests_to_show, refresh the screen with a waiting message. | |
requests_to_show = filter_to_show(@all_requests) | |
if requests_to_show.empty? | |
screen_jump | |
show 'No (' + current_request_option + ') requests types yet.' | |
show 'Watching...' | |
show '-------------------------------------------------' | |
show '' | |
else | |
output_text = '' | |
requests_to_show.each do |request| | |
output_text += request.text + "\n" | |
end | |
screen_jump | |
show 'Refreshed to see ' + current_request_option + ' requests.' | |
show '-------------------------------------------------' | |
show '' | |
show output_text | |
end | |
end | |
def current_request_option | |
# A trikcy way to get a descripton of what is the current view option. | |
if @types_to_show.count == 5 | |
'All' | |
elsif @types_to_show.count == 4 | |
'All ignoring ASSETS' | |
else | |
# Will return 'POST' or 'GET' or 'PUT' or ... | |
@types_to_show[0] | |
end | |
end | |
def filter_to_show(request_list) | |
# Recieves an array of Request objects. | |
# Outputs another array containing just the current @types_to_show requests. | |
output = Array.new | |
request_list.each do |request| | |
output << request if @types_to_show.include? request.type | |
end | |
output | |
end | |
def stream_log | |
# Stream inside the log file, line by line, sending each line to the parser method. | |
File.open(@pow_log_file) do |log| | |
log.extend(File::Tail) | |
log.interval = 10 | |
log.backward(10) | |
log.tail { |line| parse(line) } | |
end | |
end | |
def parse(line) | |
# It's recieves the sequential lines of the log file. | |
# Once it's passed in order, the parser knows how to separate each request. | |
# Each request goes into @requests_queue (for stream_parsed_log consume) and in @all_requests. | |
if line | |
# Looks for a cached request. | |
if @cached_request | |
# It there's a cached request, the follow lines should go inside it until the next blank line. | |
# line.length < 3 means that the line is blank. | |
if line.length < 3 | |
# Blank line, so the request is completed. | |
# Save it in @requests_queue and clear the cache. | |
@requests_queue << @cached_request | |
@all_requests << @cached_request | |
@cached_request = nil | |
else | |
# Append the line the the cached request text. | |
@cached_request.text += line | |
end | |
else | |
# If no cached request, it will match the line for a new one. | |
if line.include? '/assets/' | |
@cached_request = Request.new('ASSET', line) | |
elsif line.include? 'Started GET' | |
@cached_request = Request.new('GET', line) | |
elsif line.include? 'Started POST' | |
@cached_request = Request.new('POST', line) | |
elsif line.include? 'Started PUT' | |
@cached_request = Request.new('PUT', line) | |
elsif line.include? 'Started DELETE' | |
@cached_request = Request.new('DELETE', line) | |
end | |
end | |
end | |
end | |
def stream_parsed_log | |
# Consumes the @requests_queue and show the request. | |
# Unless the type don't belongs to @types_to_show. | |
show '' | |
loop do | |
request = @requests_queue.shift | |
if request | |
if show_request?(request) | |
show request.text + "\n" | |
end | |
end | |
end | |
end | |
def show_request?(request) | |
# Return true if the passed request type are present in @types_to_show | |
@types_to_show.include? request.type | |
end | |
def screen_jump | |
25.times do | |
puts '' | |
end | |
end | |
def show(str) | |
# Once has a STDIN.getch loop running (listen_keyboard) my console goes into raw mode. | |
# Print strings in raw mode is such a bad idea. | |
# So this method turns the raw mode off temporarily. | |
system "stty -raw echo" | |
puts str | |
system "stty raw -echo" | |
end | |
def help | |
system "stty -raw echo" | |
puts '' | |
puts 'Camelog usage:' | |
puts '$ camelog ~/my/railsapp' | |
puts '' | |
puts 'Camelog consider GET requests in /assets/ as ASSET type, instead of GET.' | |
puts 'As default it shows all requests, ignoring ASSETS.' | |
puts '' | |
puts 'Press one of these keys to navigate:' | |
puts 'q - Exit the program.' | |
puts 'a - Watch all types of requests.' | |
puts 'g - Watch only GET requests.' | |
puts 'p - Watch only POST requests.' | |
puts 'u - Watch only PUT requests.' | |
puts 'd - Watch only DELETE requests.' | |
puts 's - Watch only ASSET requests.' | |
puts '/ - Return to default mode. (Show all ignoring ASSETS)' | |
puts 'h - Show this message.' | |
puts '' | |
puts 'Enjoy.' | |
puts '' | |
system "stty raw -echo" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment