Permalink
Browse files

Merge pull request #15 from lookfirst/master

i've done a bunch of merging, please combine into master
  • Loading branch information...
2 parents 542dacb + 74f93db commit 7ad9a195075a9ec099fd99781f03a5da492e431e @tobi committed Apr 6, 2012
View
@@ -8,6 +8,7 @@ config/config.yml.sample
lib/clarity.rb
lib/clarity/cli.rb
lib/clarity/commands/grep_command_builder.rb
+lib/clarity/commands/hostname_command_builder.rb
lib/clarity/commands/tail_command_builder.rb
lib/clarity/grep_renderer.rb
lib/clarity/process_tree.rb
View
@@ -27,7 +27,8 @@ that clarity isn't reachable by the outside world. At the very least use --usern
== USAGE:
clarity --username=admin --password=secret --port=8989 /var/log
-
+ clarity --port=8989 --include="tomcat6/*.log*" --include="apache2/*.log*" /var/log
+
== COMMANDLINE:
Specific options:
View
@@ -9,7 +9,7 @@ require 'clarity'
Hoe.plugin :newgem
-$hoe = Hoe.spec 'clarity' do
+$hoe = Hoe.spec 'edouard-clarity' do
self.developer 'Tobias Lütke', 'tobi@shopify.com'
self.developer 'John Tajima', 'john@shopify.com'
self.summary = 'Web interface for grep and tail -f'
View
@@ -13,11 +13,12 @@
require 'clarity/server'
require 'clarity/commands/grep_command_builder'
require 'clarity/commands/tail_command_builder'
+require 'clarity/commands/hostname_command_builder'
require 'clarity/renderers/log_renderer'
module Clarity
VERSION = '0.9.8'
- Templates = File.dirname(__FILE__) + '/../views'
- Public = File.dirname(__FILE__) + '/../public'
+ Templates = File.expand_path(File.dirname(__FILE__) + '/../views')
+ Public = File.expand_path(File.dirname(__FILE__) + '/../public')
end
@@ -30,16 +30,19 @@ def command
def exec_functions
- case File.extname(filename)
- when '.gz' then gzip_tools
- when '.bz2' then bzip_tools
- else default_tools
+ type = `file #{filename}`
+ if type.include?("gzip")
+ gzip_tools
+ elsif type.include?("bzip2")
+ bzip_tools
+ else
+ default_tools
end
end
def gzip_tools
- cat_tool = (ENV["PATH"].split(":").find{|d| File.exists?(File.join(d, "gzcat"))} ? "zcat" : "gzcat")
+ cat_tool = (ENV["PATH"].split(":").find{|d| File.exists?(File.join(d, "zcat"))} ? "zcat" : "gzcat")
terms.empty? ? ["#{cat_tool} filename"] : ['zgrep options -e term filename'] + ['grep options -e term'] * (terms.size-1)
end
@@ -0,0 +1,7 @@
+class HostnameCommandBuilder
+
+ def self.command
+ `hostname`
+ end
+
+end
@@ -6,6 +6,16 @@ class LogRenderer
# Thank you to http://daringfireball.net/2009/11/liberal_regex_for_matching_urls
#
UrlParser = %r{\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))}
+
+ IpParser = %r{\b(\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3})}
+ DateParser = %r{\[(\d{2}\/\w{3}\/\d{4}\:\d{2}\:\d{2}\:\d{2}\s\+\d{4})\]|^(\w{3}\s\d{2}\s\d{2}:\d{2}:\d{2})}
+ BrowserDetails = %r{;\s&quot;(.+)&quot;$}
+ EOLStatus = %r{\(.+\)\s?$}
+ HttpVerbs = %r{(GET|POST|PUT|DELETE|HEAD)}
+ Email = %r{(\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b)}
+ Emailstatus = %r{(status=\w+)}
+ KeyValue = %r(&quot;\w+&quot;=&gt;(?:&quot;[^&]+?&quot;|[0-9\.]+|true|false|nil|[A-Z].+?\s\d{4})(?:,|\}))
+
Prefix = ""
Suffix = "<br/>\n"
@@ -17,7 +27,46 @@ def render(line = {})
output.gsub!(UrlParser) do |match|
html_link(match)
end
-
+
+ # Catch for key and value
+ output.gsub!(KeyValue) do |match|
+ (key, value) = match.split("=&gt;")
+ "<span class=\"keys\">#{key}</span>=&gt;<span class=\"values\">#{value}</span>"
+ end
+ # Formats IPs
+ output.gsub!(IpParser) do |match|
+ "<span class=\"ipaddress\">#{match}</span>"
+ end
+
+ # Format Standard unix log types formats
+ output.gsub!(DateParser) do |match|
+ "<span class=\"date\">#{match}</span>"
+ end
+ # Format end of line status messages in mail.log
+ output.gsub!(EOLStatus) do |match|
+ "<span class=\"eolstatus\">#{match}</span>"
+ end
+
+ # Format Apache Browser Specifics
+ output.gsub!(BrowserDetails) do |match|
+ "<span class=\"browser\">#{match}</span>"
+ end
+
+ # Format HTTP Verbs
+ output.gsub!(HttpVerbs) do |match|
+ "<span class=\"httpverbs\">#{match}</span>"
+ end
+
+ # Format Email addresses
+ output.gsub!(Email) do |match|
+ "<span class=\"email\">#{match}</span>"
+ end
+
+ # Format Email status messages
+ output.gsub!(Emailstatus) do |match|
+ "<span class=\"emailstatus\">#{match}</span>"
+ end
+
# Return with formatting
"#{Prefix}#{output}#{Suffix}"
end
@@ -32,5 +81,4 @@ def html_link(url)
uri = URI.parse(url) rescue url
"<a href='#{uri}'>#{url}</a>"
end
-
-end
+end
View
@@ -14,12 +14,12 @@ module Server
include EventMachine::HttpServer
include Clarity::BasicAuth
include Clarity::ChunkHttp
-
+
attr_accessor :required_username, :required_password, :relative_root
- attr_accessor :log_files
-
+ attr_accessor :log_files
+
def self.run(options)
-
+
EventMachine::run do
EventMachine.epoll
EventMachine::start_server(options[:address], options[:port], self) do |a|
@@ -31,30 +31,32 @@ def self.run(options)
STDERR.puts "Clarity #{Clarity::VERSION} starting up."
STDERR.puts " * listening on #{options[:address]}:#{options[:port]}"
-
+
if options[:user]
STDERR.puts " * Running as user #{options[:user]}"
EventMachine.set_effective_user(options[:user])
end
-
+
STDERR.puts " * Log mask(s): #{options[:log_files].join(', ')}"
-
+
if options[:username].nil? or options[:password].nil?
STDERR.puts " * WARNING: No username/password specified. This is VERY insecure."
end
STDERR.puts
-
-
- end
+
+
+ end
end
- def process_http_request
+ def process_http_request
authenticate!
puts "action: #{path}"
puts "params: #{params.inspect}"
+ @hostname = HostnameCommandBuilder.command
+
case path
when '/'
respond_with(200, welcome_page)
@@ -73,21 +75,21 @@ def process_http_request
response.chunk results_page # display page header
puts "Running: #{command}"
-
+
EventMachine::popen(command, GrepRenderer) do |grepper|
- @grepper = grepper
- @grepper.response = response
+ @grepper = grepper
+ @grepper.response = response
end
end
when '/test'
response = respond_with_chunks
- EventMachine::add_periodic_timer(1) do
- response.chunk "Lorem ipsum dolor sit amet<br/>"
+ EventMachine::add_periodic_timer(1) do
+ response.chunk "Lorem ipsum dolor sit amet<br/>"
response.send_chunks
end
- else
+ else
respond_with(200, public_file(path), :content_type => Mime.for(path))
end
@@ -99,8 +101,8 @@ def process_http_request
puts "Could not authenticate user"
headers = { "WWW-Authenticate" => %(Basic realm="Clarity")}
respond_with(401, "HTTP Basic: Access denied.\n", :content_type => 'text/plain', :headers => headers)
- end
-
+ end
+
def error_page(error)
@error = error
render "error.html.erb"
@@ -124,7 +126,7 @@ def unbind
def authenticate!
login, pass = authentication_data
- if (required_username && required_username != login) || (required_password && required_password != pass)
+ if (required_username && required_username != login) || (required_password && required_password != pass)
raise NotAuthenticatedError
end
@@ -1,11 +1,11 @@
require 'erb'
module Clarity
-
+
module ChunkHttp
-
- LeadIn = ' ' * 1024
-
+
+ LeadIn = ' ' * 1024
+
def respond_with_chunks
response = EventMachine::DelegatedHttpResponse.new( self )
response.status = 200
@@ -23,7 +23,7 @@ def respond_with(status, content, options = {})
response.status = status
response.content = content
response.send_response
- end
+ end
def render(view)
@toolbar = template("_toolbar.html.erb")
@@ -32,28 +32,35 @@ def render(view)
end
def template(filename)
- content = File.read( File.join(Clarity::Templates, filename) )
+ content = File.read( File.join(Clarity::Templates, filename) )
ERB.new(content).result(binding)
end
def public_file(filename)
- File.read( File.join(Clarity::Public, filename) )
- rescue Errno::ENOENT
- raise NotFoundError
+ path = File.expand_path(File.join(Clarity::Public, filename))
+ raise NotFoundError unless path[0, Clarity::Public.length] == Clarity::Public
+ raise NotFoundError unless File.file?(path)
+ File.read(path)
end
def logfiles
log_files.map {|f| Dir[f] }.flatten.compact.uniq.select{|f| File.file?(f) }.sort
end
-
+
def params
ENV['QUERY_STRING'].split('&').inject({}) {|p,s| k,v = s.split('=');p[k.to_s] = CGI.unescape(v.to_s);p}
- end
+ end
def path
ENV["PATH_INFO"]
end
-
+
+ def json_encode(obj)
+ obj.to_json.
+ gsub('>', '\u003E').
+ gsub('<', '\u003C')
+ end
+
end
-
+
end
View
@@ -11,25 +11,19 @@ var Search = {
resultsId : 'results',
search_fields: [ 'term1', 'term2', 'term3' ], // domIds of search term fields
file_list : 'file-list', // domId of select for logfiles
- logfiles : {}, // hash of log files
past_params : null, // recent request
url : '/perform',
scroll_fnId : null,
// initialize Search form
- // { 'grep': [ log, files, for, grep], 'tail': [ 'log', 'files', 'for', 'tail']}
- init: function(logfiles, params) {
- this.logfiles = logfiles;
+ init: function(params) {
this.past_params = params;
-
- this.bind_grep_tool();
- this.bind_tail_tool();
this.bind_options();
if (!this.past_params) return; // return if no prev settings, nothing to set
// init tool selector
- (this.past_params['tool'] == 'grep') ? $('#grep-label').trigger('click') : $('#tail-tool').trigger('click');
+ (this.past_params['tool'] == 'grep') ? $('#grep-tool').attr('checked', 'checked') : $('#tail-tool').attr('checked', 'checked');
// init log file selector
$('#'+this.file_list).val(this.past_params['file']);
@@ -61,38 +55,6 @@ var Search = {
$('#auto-scroll').attr('checked', true).trigger('change'); // by default, turn on
},
- // bind change grep tool
- bind_grep_tool: function() {
- $('#grep-tool').bind('change', function(e){
- var newlist = ""
- jQuery.each(Search.logfiles['grep'], function(){
- newlist += "<option value='" + this + "'>" + this + "</option>\n"
- });
- $('#'+Search.file_list).html(newlist);
- });
- // watch clicking label as well
- $('#grep-label').bind('click', function(e){
- $('#grep-tool').attr('checked', 'checked').val('grep').trigger('change');
- });
- },
-
-
- // bind change tail tool
- bind_tail_tool: function() {
- $('#tail-tool').bind('change', function(e){
- var newlist = ""
- jQuery.each(Search.logfiles['tail'], function(){
- newlist += "<option value='" + this + "'>" + this + "</option>\n"
- });
- $('#'+ Search.file_list).html(newlist);
- });
- // watch clicking label as well
- $('#tail-label').bind('click', function(e){
- $('#tail-tool').attr('checked', 'checked').val('tail').trigger('change');
- });
- },
-
-
// clears the terms fields
clear: function() {
jQuery.each(this.search_fields, function(){
Oops, something went wrong.

0 comments on commit 7ad9a19

Please sign in to comment.