clarity 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/History.txt +4 -0
- data/Manifest.txt +37 -0
- data/PostInstall.txt +8 -0
- data/README.rdoc +56 -0
- data/Rakefile +27 -0
- data/bin/clarity +10 -0
- data/config/config.yml.sample +7 -0
- data/lib/clarity.rb +20 -0
- data/lib/clarity/cli.rb +80 -0
- data/lib/clarity/commands/command_builder.rb +56 -0
- data/lib/clarity/commands/tail_command_builder.rb +27 -0
- data/lib/clarity/parsers/hostname_parser.rb +43 -0
- data/lib/clarity/parsers/shop_parser.rb +48 -0
- data/lib/clarity/parsers/time_parser.rb +93 -0
- data/lib/clarity/renderers/log_renderer.rb +47 -0
- data/lib/clarity/server.rb +143 -0
- data/lib/clarity/server/basic_auth.rb +24 -0
- data/lib/clarity/server/chunk_http.rb +57 -0
- data/lib/clarity/server/mime_types.rb +23 -0
- data/public/images/spinner_big.gif +0 -0
- data/public/javascripts/app.js +154 -0
- data/public/stylesheets/app.css +54 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/commands/command_builder_test.rb +100 -0
- data/test/commands/tail_command_builder_test.rb +43 -0
- data/test/files/logfile.log +17 -0
- data/test/parsers/hostname_parser_test.rb +40 -0
- data/test/parsers/shop_parser_test.rb +54 -0
- data/test/parsers/time_parser_test.rb +84 -0
- data/test/test_helper.rb +3 -0
- data/test/test_string_scanner.rb +17 -0
- data/views/_header.html.erb +5 -0
- data/views/_toolbar.html.erb +89 -0
- data/views/error.html.erb +11 -0
- data/views/index.html.erb +9 -0
- metadata +134 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
History.txt
|
2
|
+
Manifest.txt
|
3
|
+
PostInstall.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
bin/clarity
|
7
|
+
config/config.yml.sample
|
8
|
+
lib/clarity.rb
|
9
|
+
lib/clarity/cli.rb
|
10
|
+
lib/clarity/commands/command_builder.rb
|
11
|
+
lib/clarity/commands/tail_command_builder.rb
|
12
|
+
lib/clarity/parsers/hostname_parser.rb
|
13
|
+
lib/clarity/parsers/shop_parser.rb
|
14
|
+
lib/clarity/parsers/time_parser.rb
|
15
|
+
lib/clarity/renderers/log_renderer.rb
|
16
|
+
lib/clarity/server.rb
|
17
|
+
lib/clarity/server/basic_auth.rb
|
18
|
+
lib/clarity/server/chunk_http.rb
|
19
|
+
lib/clarity/server/mime_types.rb
|
20
|
+
public/images/spinner_big.gif
|
21
|
+
public/javascripts/app.js
|
22
|
+
public/stylesheets/app.css
|
23
|
+
script/console
|
24
|
+
script/destroy
|
25
|
+
script/generate
|
26
|
+
test/commands/command_builder_test.rb
|
27
|
+
test/commands/tail_command_builder_test.rb
|
28
|
+
test/files/logfile.log
|
29
|
+
test/parsers/hostname_parser_test.rb
|
30
|
+
test/parsers/shop_parser_test.rb
|
31
|
+
test/parsers/time_parser_test.rb
|
32
|
+
test/test_helper.rb
|
33
|
+
test/test_string_scanner.rb
|
34
|
+
views/_header.html.erb
|
35
|
+
views/_toolbar.html.erb
|
36
|
+
views/error.html.erb
|
37
|
+
views/index.html.erb
|
data/PostInstall.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
= Clarity
|
2
|
+
|
3
|
+
* http://github.com/#{github_username}/#{project_name}
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
Clarity - a log search tool
|
8
|
+
By John Tajima & Tobi Lütke
|
9
|
+
|
10
|
+
Clarity is an eventmachine-based web application that is used at Shopify to
|
11
|
+
search log files on production servers.
|
12
|
+
|
13
|
+
We wrote Clarity to allow authorized users to use a simple interface to look
|
14
|
+
through the various log files in our server farm, without requiring access to
|
15
|
+
production servers.
|
16
|
+
|
17
|
+
Clarity requires eventmachine and eventmachine/evma_httpserver.
|
18
|
+
|
19
|
+
sudo gem install eventmachine eventmachine_httpserver
|
20
|
+
|
21
|
+
|
22
|
+
== REQUIREMENTS:
|
23
|
+
|
24
|
+
* eventmachine
|
25
|
+
* eventmachine_httpserver
|
26
|
+
|
27
|
+
== INSTALL:
|
28
|
+
|
29
|
+
* sudo gem install clarity
|
30
|
+
|
31
|
+
== LICENSE:
|
32
|
+
|
33
|
+
(The MIT License)
|
34
|
+
|
35
|
+
Copyright (c) 2009 Tobias Lütke
|
36
|
+
|
37
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
38
|
+
a copy of this software and associated documentation files (the
|
39
|
+
'Software'), to deal in the Software without restriction, including
|
40
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
41
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
42
|
+
permit persons to whom the Software is furnished to do so, subject to
|
43
|
+
the following conditions:
|
44
|
+
|
45
|
+
The above copyright notice and this permission notice shall be
|
46
|
+
included in all copies or substantial portions of the Software.
|
47
|
+
|
48
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
49
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
50
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
51
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
52
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
53
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
54
|
+
SOFTWARE OR THE USE OR OT
|
55
|
+
|
56
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
require 'hoe'
|
4
|
+
require 'fileutils'
|
5
|
+
require './lib/clarity'
|
6
|
+
|
7
|
+
Hoe.plugin :newgem
|
8
|
+
# Hoe.plugin :website
|
9
|
+
# Hoe.plugin :cucumberfeatures
|
10
|
+
|
11
|
+
# Generate all the Rake tasks
|
12
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
13
|
+
$hoe = Hoe.spec 'clarity' do
|
14
|
+
self.developer 'Tobias Lütke', 'tobi@shopify.com'
|
15
|
+
self.developer 'John Tajima', 'john@shopify.com'
|
16
|
+
self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
|
17
|
+
# self.rubyforge_name = self.name # TODO this is default value
|
18
|
+
|
19
|
+
self.extra_deps = [['eventmachine','>= 0.12.10'], ['eventmachine_httpserver','>= 0.2.0']]
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'newgem/tasks'
|
23
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
24
|
+
|
25
|
+
# TODO - want other tests/tasks run by default? Add them to the list
|
26
|
+
# remove_task :default
|
27
|
+
# task :default => [:spec, :features]
|
data/bin/clarity
ADDED
data/lib/clarity.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
2
|
+
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'evma_httpserver'
|
5
|
+
require 'yaml'
|
6
|
+
require 'base64'
|
7
|
+
require 'clarity/server'
|
8
|
+
require 'clarity/commands/command_builder'
|
9
|
+
require 'clarity/commands/tail_command_builder'
|
10
|
+
require 'clarity/parsers/time_parser'
|
11
|
+
require 'clarity/parsers/hostname_parser'
|
12
|
+
require 'clarity/parsers/shop_parser'
|
13
|
+
require 'clarity/renderers/log_renderer'
|
14
|
+
|
15
|
+
module Clarity
|
16
|
+
VERSION = '0.9.0'
|
17
|
+
|
18
|
+
Templates = File.dirname(__FILE__) + '/../views'
|
19
|
+
Public = File.dirname(__FILE__) + '/../public'
|
20
|
+
end
|
data/lib/clarity/cli.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
#require File.dirname(__FILE__) + '/../clarity'
|
3
|
+
|
4
|
+
|
5
|
+
module Clarity
|
6
|
+
class CLI
|
7
|
+
def self.execute(stdout, arguments=[])
|
8
|
+
|
9
|
+
options = {
|
10
|
+
:username => nil,
|
11
|
+
:password => nil,
|
12
|
+
:log_files => ['**/*.log*'],
|
13
|
+
:port => 8080,
|
14
|
+
:address => "0.0.0.0"
|
15
|
+
}
|
16
|
+
|
17
|
+
mandatory_options = %w( )
|
18
|
+
|
19
|
+
ARGV.options do |opts|
|
20
|
+
opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] directory"
|
21
|
+
|
22
|
+
opts.separator " "
|
23
|
+
opts.separator "Specific options:"
|
24
|
+
|
25
|
+
opts.on( "-f", "--config=FILE", String, "Config file (yml)" ) do |opt|
|
26
|
+
options.update YAML.load_file( opt )
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on( "-p", "--port=PORT", Integer, "Port to listen on" ) do |opt|
|
30
|
+
options[:port] = opt
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on( "-b", "--address=ADDRESS", String, "Address to bind to (default 0.0.0.0)" ) do |opt|
|
34
|
+
options[:address] = opt
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on( "--include=MASK", String, "File mask of logs to add (default: **/*.log*)" ) do |opt|
|
38
|
+
options[:log_files] ||= []
|
39
|
+
options[:log_files] += opt
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.separator " "
|
43
|
+
opts.separator "Password protection:"
|
44
|
+
|
45
|
+
opts.on( "--username=USER", String, "Enable httpauth username" ) do |opt|
|
46
|
+
options[:username] = opt
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on( "--password=PASS", String, "Enable httpauth password" ) do |opt|
|
50
|
+
options[:password] = opt
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.separator " "
|
54
|
+
opts.separator "Misc:"
|
55
|
+
|
56
|
+
opts.on( "-h", "--help", "Show this message." ) do
|
57
|
+
puts opts
|
58
|
+
exit
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.separator " "
|
62
|
+
|
63
|
+
begin
|
64
|
+
opts.parse!(arguments)
|
65
|
+
|
66
|
+
if arguments.first
|
67
|
+
Dir.chdir(arguments.first)
|
68
|
+
end
|
69
|
+
|
70
|
+
::Clarity::Server.run(options)
|
71
|
+
|
72
|
+
#rescue
|
73
|
+
# puts opts
|
74
|
+
# exit
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class CommandBuilder
|
2
|
+
|
3
|
+
# parameter names
|
4
|
+
TermParameters = ['term1', 'term2', 'term3']
|
5
|
+
FileParameter = 'file'
|
6
|
+
|
7
|
+
attr_accessor :params
|
8
|
+
attr_reader :terms, :filename, :options
|
9
|
+
|
10
|
+
def initialize(params)
|
11
|
+
@params = params
|
12
|
+
@filename = params.fetch(FileParameter)
|
13
|
+
@terms = TermParameters.map {|term| params.fetch(term, nil) }.compact.reject {|term| term.empty? }
|
14
|
+
@options = ""
|
15
|
+
valid?
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
raise InvalidParameterError, "Log file parameter not supplied" unless filename && !filename.empty?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def command
|
24
|
+
results = []
|
25
|
+
exec_functions.each_with_index do |cmd, index|
|
26
|
+
results << cmd.gsub('filename', filename.to_s).gsub('options', options.to_s).gsub('term', terms[index].to_s)
|
27
|
+
end
|
28
|
+
%[sh -c '#{results.join(" | ")}']
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def exec_functions
|
33
|
+
case File.extname(filename)
|
34
|
+
when '.gz' then gzip_tools
|
35
|
+
when '.bz2' then bzip_tools
|
36
|
+
else default_tools
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def gzip_tools
|
42
|
+
terms.empty? ? ['gzcat filename'] : ['zgrep options -e term filename'] + ['grep options -e term'] * (terms.size-1)
|
43
|
+
end
|
44
|
+
|
45
|
+
def bzip_tools
|
46
|
+
terms.empty? ? ['bzcat filename'] : ['bzgrep options -e term filename'] + ['grep options -e term'] * (terms.size-1)
|
47
|
+
end
|
48
|
+
|
49
|
+
def default_tools
|
50
|
+
terms.empty? ? ['cat filename'] : ['grep options -e term filename'] + ['grep options -e term']* (terms.size-1)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
class InvalidParameterError < StandardError; end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#
|
2
|
+
# Handles tailing of log files
|
3
|
+
#
|
4
|
+
class TailCommandBuilder < CommandBuilder
|
5
|
+
|
6
|
+
def valid?
|
7
|
+
raise InvalidParameterError, "Log file parameter not supplied or invalid log file" unless filename && !filename.empty? && File.extname(filename) == ".log"
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
def command
|
12
|
+
results = []
|
13
|
+
exec_functions.each_with_index do |cmd, index|
|
14
|
+
if index == 0
|
15
|
+
results << cmd.gsub('filename', filename.to_s)
|
16
|
+
else
|
17
|
+
results << cmd.gsub('filename', filename.to_s).gsub('options', options.to_s).gsub('term', terms[index-1].to_s)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
%[sh -c '#{results.join(" | ")}']
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def default_tools
|
25
|
+
terms.empty? ? ['tail -n 250 -f filename'] : ['tail -n 250 -f filename'] + ['grep options -e term'] * (terms.size)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
class HostnameParser
|
3
|
+
|
4
|
+
# given a string in format:
|
5
|
+
#
|
6
|
+
# app3 rails.shopify[9855]: [wadedemt.myshopify.com] Processing ShopController#products (for 192.168.1.230 at 2009-07-24 14:58:21) [GET]
|
7
|
+
# 129.123.2.1 rails.shopify[9855]: [wadedemt.myshopify.com] Processing ShopController#products (for 192.168.1.230 at 2009-07-24 14:58:21) [GET]
|
8
|
+
#
|
9
|
+
# strips out the hostname/IP and appname
|
10
|
+
#
|
11
|
+
# result => [wadedemt.myshopify.com] Processing ShopController#products (for 192.168.1.230 at 2009-07-24 14:58:21) [GET]
|
12
|
+
|
13
|
+
|
14
|
+
LineRegexp = /^([\w-]+|\d+\.\d+\.\d+\.\d+)\s([^:]*):\s*(.*)/
|
15
|
+
|
16
|
+
attr_accessor :elements, :next_parser
|
17
|
+
|
18
|
+
def initialize(next_renderer = nil)
|
19
|
+
@next_renderer = next_renderer
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse(line, elements = {})
|
23
|
+
@elements = elements
|
24
|
+
# parse line into elements and put into element
|
25
|
+
next_line = parse_line(line)
|
26
|
+
if @next_renderer && next_line
|
27
|
+
@elements = @next_renderer.parse(next_line, @elements)
|
28
|
+
end
|
29
|
+
@elements
|
30
|
+
end
|
31
|
+
|
32
|
+
# parse line and break into pieces
|
33
|
+
def parse_line(line)
|
34
|
+
results = LineRegexp.match(line)
|
35
|
+
if results
|
36
|
+
@elements[:line] = results[-1]
|
37
|
+
results[-1] # remaining line
|
38
|
+
else
|
39
|
+
@elements[:line] = line
|
40
|
+
line
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class ShopParser
|
2
|
+
|
3
|
+
# given a string in format:
|
4
|
+
#
|
5
|
+
# [wadedemt.myshopify.com] Processing ShopController#products (for 192.168.1.230 at 2009-07-24 14:58:21) [GET]
|
6
|
+
#
|
7
|
+
# strips out the shop name
|
8
|
+
#
|
9
|
+
# result => :shop => wadedemt.myshopify.com
|
10
|
+
# :line => Processing ShopController#products (for 192.168.1.230 at 2009-07-24 14:58:21) [GET]
|
11
|
+
|
12
|
+
|
13
|
+
LineRegexp = /^\s*\[([a-zA-Z0-9\-.]+)\]\s*(.*)/
|
14
|
+
|
15
|
+
attr_accessor :elements
|
16
|
+
|
17
|
+
def initialize(next_renderer = nil)
|
18
|
+
@next_renderer = next_renderer
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse(line, elements = {})
|
22
|
+
@elements = elements
|
23
|
+
# parse line into elements and put into element
|
24
|
+
next_line = parse_line(line)
|
25
|
+
if @next_renderer && next_line
|
26
|
+
@elements = @next_renderer.parse(next_line, @elements)
|
27
|
+
end
|
28
|
+
@elements
|
29
|
+
end
|
30
|
+
|
31
|
+
# parse line and break into pieces
|
32
|
+
def parse_line(line)
|
33
|
+
results = LineRegexp.match(line)
|
34
|
+
if results
|
35
|
+
if results[1] =~ /\./
|
36
|
+
@elements[:shop] = results[1]
|
37
|
+
@elements[:line] = results[-1]
|
38
|
+
results[-1]
|
39
|
+
else
|
40
|
+
@elements[:line] = line
|
41
|
+
line
|
42
|
+
end
|
43
|
+
else
|
44
|
+
@elements[:line] = line
|
45
|
+
line
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
|
2
|
+
class TimeParser
|
3
|
+
|
4
|
+
# strips out timestamp and if start/end times are defined, will reject lines that don't fall within proper time periods
|
5
|
+
#
|
6
|
+
# entry format:
|
7
|
+
# Jul 24 14:58:21 app3 rails.shopify[9855]: [wadedemt.myshopify.com] Processing ShopController#products (for 192.168.1.230 at 2009-07-24 14:58:21) [GET]
|
8
|
+
#
|
9
|
+
# params = {
|
10
|
+
# 'sh' => start hour
|
11
|
+
# 'sm' => start minute
|
12
|
+
# 'ss' => start second
|
13
|
+
# 'eh' => end hour
|
14
|
+
# 'em' => end minute
|
15
|
+
# 'es' => end second
|
16
|
+
# }
|
17
|
+
#
|
18
|
+
# if 'sh' is defined, reject any lines where timestamp is earlier than start time
|
19
|
+
# if 'eh' is defined, reject any lines where timestamp is later than end time
|
20
|
+
# if 'sh' && 'eh' is defined, reject any lines where timestamp is not between start time and end time
|
21
|
+
|
22
|
+
LineRegexp = /^(\w+\s+\d+\s\d\d:\d\d:\d\d)\s(.*)/
|
23
|
+
|
24
|
+
attr_accessor :elements, :params
|
25
|
+
|
26
|
+
def initialize(next_renderer = nil, params = {})
|
27
|
+
@next_renderer = next_renderer
|
28
|
+
@params = params
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse(line, elements = {})
|
32
|
+
@elements = elements
|
33
|
+
next_line = parse_line(line)
|
34
|
+
|
35
|
+
# reject line if we filter by time
|
36
|
+
if check_time?
|
37
|
+
if !start_time_valid? || !end_time_valid?
|
38
|
+
# reject this entry
|
39
|
+
@elements = {}
|
40
|
+
return @elements
|
41
|
+
end
|
42
|
+
else
|
43
|
+
if @next_renderer && next_line
|
44
|
+
@elements = @next_renderer.parse(next_line, @elements)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
@elements
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_time?
|
51
|
+
(params['sh'] && !params['sh'].empty?) || (params['eh'] && !params['eh'].empty?)
|
52
|
+
end
|
53
|
+
|
54
|
+
# check if current line's time is >= start time, if it was set
|
55
|
+
def start_time_valid?
|
56
|
+
line_time = parse_time_from_string(@elements[:timestamp])
|
57
|
+
start_time = Time.utc(line_time.year, line_time.month, line_time.day, params.fetch('sh',0).to_i, params.fetch('sm', 0).to_i, params.fetch('ss', 0).to_i )
|
58
|
+
line_time >= start_time ? true : false
|
59
|
+
rescue Exception => e
|
60
|
+
puts "Error! #{e}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def end_time_valid?
|
64
|
+
line_time = parse_time_from_string(@elements[:timestamp])
|
65
|
+
end_time = Time.utc(line_time.year, line_time.month, line_time.day, params.fetch('eh',23).to_i, params.fetch('em', 59).to_i, params.fetch('es', 59).to_i )
|
66
|
+
line_time <= end_time ? true : false
|
67
|
+
rescue Exception => e
|
68
|
+
puts "Error! #{e}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_time_from_string(text)
|
72
|
+
# Jul 24 14:58:21
|
73
|
+
time = nil
|
74
|
+
if text =~ /(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)/
|
75
|
+
time = Time.utc(Time.now.year, $1, $2, $3, $4, $5)
|
76
|
+
end
|
77
|
+
time
|
78
|
+
end
|
79
|
+
|
80
|
+
# parse line and break into pieces
|
81
|
+
def parse_line(line)
|
82
|
+
results = LineRegexp.match(line)
|
83
|
+
if results
|
84
|
+
@elements[:timestamp] = results[1]
|
85
|
+
@elements[:line] = results[-1]
|
86
|
+
results[-1] # remaining line
|
87
|
+
else
|
88
|
+
@elements[:line] = line
|
89
|
+
line
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|