logworm_client 0.6.2 → 0.7.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/CHANGELOG CHANGED
@@ -1,3 +1,18 @@
1
+ v0.7.0
2
+ Eliminated long logs. Instead, there's now a parameter (log_request_headers in Rack, lw_log_request_headers in Rails) to add
3
+ request and response headers to the web_log.
4
+ Cleaned up fields in the web_log table
5
+
6
+ Added option to turn off automatic logging of requests (disable_request_logging in Rack, lw_disable_request_logging in Rails)
7
+ Added option to turn on logging even if in development mode (enable_dev_logging in Rack, lw_enable_dev_logging in Rails)
8
+
9
+ Honor filter_parameter_logging switch when used with Rails
10
+
11
+ Cleaned up log and flush
12
+ Cleaned up display of elapsed time for flush
13
+ Cleaned up call from Rack and Rails, and enforce timeout (1 sec by default)
14
+ Added unit tests
15
+
1
16
  v0.6.2
2
17
  Added list_tables command
3
18
 
data/Manifest CHANGED
@@ -11,3 +11,8 @@ lib/logworm_client/rails.rb
11
11
  lib/logworm_utils.rb
12
12
  lib/logworm_utils/compute.rb
13
13
  lib/logworm_utils/tail.rb
14
+ spec/logger_spec.rb
15
+ spec/rack_spec.rb
16
+ spec/rails_spec.rb
17
+ spec/spec.opts
18
+ spec/spec_helper.rb
data/Rakefile CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'echoe'
2
- Echoe.new('logworm_client', '0.6.2') do |p|
2
+ Echoe.new('logworm_client', '0.7.0') do |p|
3
3
  p.description = "logworm client utilities"
4
4
  p.url = "http://www.logworm.com"
5
5
  p.author = "Pomelo, LLC"
@@ -4,51 +4,90 @@ module Logworm
4
4
  class LogwormException < Exception ; end
5
5
 
6
6
  class Logger
7
- # Initialize
8
- LW_SERVER = DB.from_config
9
7
  $lr_queue = []
10
8
 
9
+ ###
10
+ # Use connection to the backend servers specified in environment variable or config file
11
+ ###
12
+ def self.use_default_db
13
+ $lw_server = DB.from_config
14
+ end
15
+
16
+ ###
17
+ # Use a connection to a manually specified server
18
+ ###
19
+ def self.use_db(db)
20
+ $lw_server = db
21
+ end
22
+
23
+ ###
24
+ # Returns a reference to the current backend server
25
+ ###
26
+ def self.db
27
+ $lw_server
28
+ end
29
+
30
+
31
+ ###
32
+ # Starts a new cycle: sets a request_id variable, so that all entries (until flush) share that id
33
+ ###
11
34
  def self.start_cycle
12
35
  $request_id = "#{Thread.current.object_id}-#{(Time.now.utc.to_f * 1000).to_i}"
13
36
  end
14
37
 
38
+ ###
39
+ # Record an entry. Not sent to the servers until 'flush' is called
40
+ #
41
+ # Warning: may raise Exception if there's a problem. It's up to the called to rescue from it in order to continue the processing
42
+ # of a web request, for example.
43
+ ###
15
44
  def self.log(table, values = {})
16
- begin
17
- # Rename conflicting keys
18
- kvalues = values.delete_if {|k,v| k.to_s == ""}.map {|k,v| [k.to_sym, v]}
19
- kvalues = Hash[*kvalues.flatten]
20
- [:_ts, :_ts_utc, :_request_id].each do |k|
21
- kvalues["orig_#{k}".to_sym] = kvalues[k] if kvalues.has_key? k
22
- end
23
- # Add information
24
- ts = Time.now.utc
25
- log_details = {:_ts => ts.strftime("%Y-%m-%dT%H:%M:%SZ"), :_ts_utc => (ts.to_f * 1000).to_i}
26
- log_details[:_request_id] = $request_id if $request_id
27
- # Enqueue
28
- payload = kvalues.merge(log_details)
29
- $lr_queue << [table, payload]
30
- rescue Exception => e
31
- $stderr.puts "Error in Logworm::Logger.log: #{e}"
45
+ return unless table and (table.is_a? String or table.is_a? Symbol)
46
+ return unless values.is_a? Hash
47
+
48
+ # Turn keys into symbols, delete empty ones, rename conflicting ones
49
+ kvalues = values.delete_if {|k,v| k.to_s == ""}.map {|k,v| [k.to_sym, v]}
50
+ kvalues = Hash[*kvalues.flatten]
51
+ [:_ts, :_ts_utc, :_request_id].each do |k|
52
+ kvalues["orig_#{k}".to_sym] = kvalues[k] if kvalues.has_key? k
32
53
  end
54
+
55
+ # Add information
56
+ ts = Time.now.utc
57
+ kvalues[:_ts_utc] = (ts.to_f * 1000).to_i
58
+ kvalues[:_ts] = ts.strftime("%Y-%m-%dT%H:%M:%SZ")
59
+ kvalues[:_request_id] = $request_id if $request_id
60
+
61
+ # Enqueue
62
+ $lr_queue << [table, kvalues]
33
63
  end
34
64
 
65
+ ###
66
+ # Sends the entries to the server, if configured
67
+ # Returns the number of entries send, and the time it takes
68
+ #
69
+ # Warning: may raise Exception if there's a problem. It's up to the called to rescue from it in order to continue the processing
70
+ # of a web request, for example.
71
+ ###
35
72
  def self.flush
36
73
  to_send = $lr_queue.size
37
- return 0 if to_send == 0
38
- unless LW_SERVER
74
+
75
+ # Return if no entries
76
+ return [0,0] if to_send == 0
77
+
78
+ # Return if no server
79
+ unless $lw_server
39
80
  $stderr.puts "\t logworm not configured. #{to_send} entries dropped."
40
81
  $lr_queue = []
41
- return 0
82
+ return [0,0]
42
83
  end
43
84
 
44
- begin
45
- LW_SERVER.batch_log($lr_queue.to_json)
46
- rescue Exception => e
47
- $stderr.puts "in Logworm::Logger.flush: #{e}"
48
- $stderr.puts e.backtrace
49
- end
85
+ startTime = Time.now
86
+ $lw_server.batch_log($lr_queue.to_json)
50
87
  $lr_queue = []
51
- to_send
88
+ endTime = Time.now
89
+
90
+ [to_send, (endTime - startTime)]
52
91
  end
53
92
  end
54
93
  end
@@ -1,60 +1,62 @@
1
- require 'benchmark'
1
+ require 'rack/request'
2
2
 
3
3
  module Logworm
4
4
  class Rack
5
5
 
6
6
  def initialize(app, options = {})
7
7
  @app = app
8
- @log_requests_short = options[:log_requests].nil? or options[:log_requests] != false
9
- @log_requests_long = !options[:log_requests_long].nil? and options[:log_requests_long] == true
8
+
9
+ @log_requests = (options[:disable_request_logging].nil? or options[:disable_request_logging] != true)
10
+ @log_headers = (options[:log_request_headers] and options[:log_request_headers] == true)
11
+ @dev_logging = (options[:enable_dev_logging] and options[:enable_dev_logging] == true)
12
+ Logger.use_default_db
13
+ @timeout = 1
10
14
  end
11
15
 
12
16
  def call(env)
13
- return @app.call(env) unless ENV['RACK_ENV'] == 'production'
17
+ return @app.call(env) unless (ENV['RACK_ENV'] == 'production' or (ENV['RACK_ENV'] == 'development' and @dev_logging))
14
18
 
15
- startTime = Time.now
16
19
  Logger.start_cycle
17
- status, headers, body = @app.call(env)
18
- appTime = (Time.now - startTime)
19
- queue_size = env['HTTP_X_HEROKU_QUEUE_DEPTH'] ? env['HTTP_X_HEROKU_QUEUE_DEPTH'].to_i : -1
20
- Logger.log(:web_log_long, {:summary => "#{env['REQUEST_METHOD']} #{env['REQUEST_URI']} - #{status} #{appTime}",
21
- :request => env_to_log(env),
22
- :response => {:status => status, :headers => headers},
23
- :profiling => appTime}) if @log_requests_long
24
- Logger.log(:web_log, {:summary => "#{env['REQUEST_METHOD']} #{env['REQUEST_URI']} - #{status} #{appTime}",
25
- :request_path => env['REQUEST_PATH'], :request_ip => env['REMOTE_ADDR'],
26
- :request_method => env['REQUEST_METHOD'], :input => collect_input(env),
27
- :response_status => status,
28
- :profiling => appTime,
29
- :queue_size => queue_size}) if @log_requests_short
20
+ begin
21
+ startTime = Time.now
22
+ status, response_headers, body = @app.call(env)
23
+ appTime = (Time.now - startTime)
24
+ ensure
25
+ log_request(env, status, response_headers, appTime)
26
+ return [status, response_headers, body]
27
+ end
28
+ end
29
+
30
+ private
31
+ def log_request(env, status, response_headers, appTime)
32
+ method = env['REQUEST_METHOD']
33
+ path = (env['REQUEST_PATH'].nil? or env['REQUEST_PATH'] == "") ? "/" : env['REQUEST_PATH']
34
+ ip = env['REMOTE_ADDR']
35
+ http_headers = env.reject {|k,v| !(k.to_s =~ /^HTTP/) }
36
+ queue_size = env['HTTP_X_HEROKU_QUEUE_DEPTH'].nil? ? -1 : env['HTTP_X_HEROKU_QUEUE_DEPTH'].to_i
37
+
38
+ entry = { :summary => "#{method} #{path} - #{status} #{appTime}",
39
+ :request_method => method,
40
+ :request_path => path,
41
+ :request_ip => ip,
42
+ :input => ::Rack::Request.new(env).params,
43
+ :response_status => status,
44
+ :profiling => appTime,
45
+ :queue_size => queue_size}
46
+ entry[:headers] = http_headers if @log_headers
47
+ entry[:response_headers] = response_headers if @log_headers
48
+ Logger.log(:web_log, entry) if @log_requests
49
+
30
50
  begin
31
- Timeout::timeout(1) {
32
- sent = 0
33
- ts = Benchmark.realtime {sent = Logger.flush} # Flushes only if there are any entries. Times out after a second
34
- env['rack.errors'].puts("\t *** logworm - logs sent in #{ts} seconds") if sent > 0
51
+ Timeout::timeout(@timeout) {
52
+ sent, elapsed = Logger.flush
53
+ env['rack.errors'].puts("-- #{sent} logworm entries recorded in #{sprintf('%.4f', elapsed)} seconds") if sent > 0
35
54
  }
36
55
  rescue Exception => e
37
- # Ignore --nothing we can do
38
- # The list of logs may (and most likely will) be preserved for the next request
39
- env['rack.errors'].puts "logworm call failed: #{e}"
56
+ # Ignore --nothing we can do. The list of logs may (and most likely will) be preserved for the next request
57
+ env['rack.errors'].puts("logworm call failed: #{e}")
40
58
  end
41
- [status, headers, body]
42
59
  end
43
60
 
44
- private
45
- def env_to_log(env)
46
- to_log = {}
47
- env.each do |k,v|
48
- to_log[k.to_s.downcase.to_sym] = v unless (k.to_s =~ /rack/i or k.to_s =~ /\./i)
49
- end
50
- to_log.merge({:input => collect_input(env)})
51
- end
52
-
53
- def collect_input(env)
54
- inputstr = ""
55
- env['rack.input'].each {|content| inputstr += content }
56
- env['rack.input'].rewind
57
- inputstr
58
- end
59
61
  end
60
62
  end
@@ -2,75 +2,104 @@
2
2
  # Extemds ActionController::Base and aliases the process method, to wrap it with the standard logworm request cycle
3
3
  #
4
4
  # By default it will automatically log web requests (in a short format) into web_log
5
- # Can also log in long format if specified.
6
- #
7
- # Configuration
8
- # In ApplicationController, add
9
- # logs_requests :short, :long
10
- # logs_requests :long
11
- # logs_requests :short, :long
5
+ # Can also log headers, if specified
12
6
  ###
13
7
  if defined?(ActionController)
14
8
 
15
9
  require 'benchmark'
16
10
 
17
11
  ActionController::Base.class_eval do
18
- @@log_requests_short = true
19
- @@log_requests_long = false
12
+ ## Basic settings: log requests, without headers. Use default db, and timeout after 1 second
13
+ @@log_requests = true
14
+ @@log_headers = false
15
+ @@dev_logging = false
16
+ @@timeout = 1
17
+ Logworm::Logger.use_default_db
18
+
19
+ ###
20
+ # Disable automatic logging of requests
21
+ # Use from ApplicationController
22
+ ###
23
+ def self.lw_disable_request_logging
24
+ @@log_requests = false
25
+ end
26
+
27
+ ###
28
+ # Log headers with requests
29
+ # Use from ApplicationController
30
+ ###
31
+ def self.lw_log_request_headers
32
+ @@log_headers = true
33
+ end
20
34
 
21
- def self.logs_requests(*options)
22
- @@log_requests_short = (options.include?(:short) or options.include?("short"))
23
- @@log_requests_long = (options.include?(:long) or options.include?("long"))
35
+ ###
36
+ # Turn on logging in development mode
37
+ ###
38
+ def self.lw_enable_dev_logging
39
+ @@dev_logging = true
24
40
  end
25
41
 
42
+ ###
43
+ # Replaces (and embeds) the default Rails processing of a request.
44
+ # Call the original method, logs the request unless disabled, and flushes the logworm list
45
+ ###
26
46
  def process_with_logworm_log(request, response, method = :perform_action, *arguments)
27
- return process_without_logworm_log(request, response, method, *arguments) unless RAILS_ENV == 'production'
47
+ unless (RAILS_ENV == 'production' or (RAILS_ENV == 'development' and @@dev_logging))
48
+ return process_without_logworm_log(request, response, method, *arguments)
49
+ end
28
50
 
29
- startTime = Time.now
30
51
  Logworm::Logger.start_cycle
31
52
  begin
32
- response = process_without_logworm_log(request, response, method, *arguments)
33
- appTime = (Time.now - startTime)
53
+ startTime = Time.now
54
+ response = process_without_logworm_log(request, response, method, *arguments)
55
+ appTime = (Time.now - startTime)
34
56
  ensure
35
- env = request.env
36
- status = response.status[0..2]
37
- queue_size = env['HTTP_X_HEROKU_QUEUE_DEPTH'] ? env['HTTP_X_HEROKU_QUEUE_DEPTH'].to_i : -1
38
- Logworm::Logger.log(:web_log_long,
39
- {:summary => "#{env['REQUEST_METHOD']} #{env['REQUEST_URI']} - #{status} #{appTime}",
40
- :request => env_to_log(env).merge({:input => request.request_parameters}),
41
- :response => {:status => status, :headers => response.headers},
42
- :profiling => appTime,
43
- :queue_size => queue_size}) if @@log_requests_long
44
- Logworm::Logger.log(:web_log,
45
- {:summary => "#{env['REQUEST_METHOD']} #{env['REQUEST_URI']} - #{status} #{appTime}",
46
- :request_path => env['REQUEST_URI'], :request_ip => env['REMOTE_ADDR'],
47
- :request_method => env['REQUEST_METHOD'],
48
- :input => request.request_parameters,
49
- :response_status => status,
50
- :profiling => appTime}) if @@log_requests_short
51
- begin
52
- Timeout::timeout(1) {
53
- sent = 0
54
- ts = Benchmark.realtime {sent = Logworm::Logger.flush} # Flushes only if there are any entries. Times out after a second
55
- logger.info("\t *** logworm - logs sent in #{ts} seconds") if sent > 0
56
- }
57
- rescue Exception => e
58
- # Ignore --nothing we can do
59
- # The list of logs may (and most likely will) be preserved for the next request
60
- logger.error("logworm call failed: #{e}") if logger
61
- end
57
+ log_request(request, response, appTime)
62
58
  return response
63
59
  end
64
60
  end
61
+ alias_method_chain :process, :logworm_log
65
62
 
66
- def env_to_log(env)
67
- to_log = {}
68
- env.each do |k,v|
69
- to_log[k.to_s.downcase.to_sym] = v unless (k.to_s =~ /rack/i or k.to_s =~ /\./i)
70
- end
71
- end
63
+
64
+ private
65
+ def log_request(request, response, appTime)
66
+ method = request.env['REQUEST_METHOD']
67
+ path = request.env['REQUEST_PATH'].blank? ? "/" : request.env['REQUEST_PATH']
68
+ ip = request.env['REMOTE_ADDR']
69
+ http_headers = request.headers.reject {|k,v| !(k.to_s =~ /^HTTP/) }
70
+ status = response.status[0..2]
71
+ queue_size = request.env['HTTP_X_HEROKU_QUEUE_DEPTH'].blank? ? -1 : request.env['HTTP_X_HEROKU_QUEUE_DEPTH'].to_i
72
+
73
+ entry = { :summary => "#{method} #{path} - #{status} #{appTime}",
74
+ :request_method => method,
75
+ :request_path => path,
76
+ :request_ip => ip,
77
+ :input => cleaned_input(request),
78
+ :response_status => status,
79
+ :profiling => appTime,
80
+ :queue_size => queue_size}
81
+ entry[:headers] = http_headers if @@log_headers
82
+ entry[:response_headers] = response.headers if @@log_headers
83
+ Logworm::Logger.log(:web_log, entry) if @@log_requests
84
+
85
+ begin
86
+ Timeout::timeout(@@timeout) {
87
+ sent, elapsed = Logworm::Logger.flush
88
+ Rails.logger.info("#{sent} logworm entries recorded in #{sprintf('%.4f', elapsed)} seconds") if sent > 0
89
+ }
90
+ rescue Exception => e
91
+ # Ignore --nothing we can do. The list of logs may (and most likely will) be preserved for the next request
92
+ Rails.logger.error("logworm call failed: #{e}")
93
+ end
94
+ end
95
+
96
+ def cleaned_input(request)
97
+ pars = request.parameters.clone
98
+ pars.delete("controller")
99
+ pars.delete("action")
100
+ respond_to?(:filter_parameters) ? filter_parameters(pars) : pars
101
+ end
72
102
 
73
- alias_method_chain :process, :logworm_log
74
103
  end
75
104
 
76
105
  end
@@ -2,16 +2,16 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{logworm_client}
5
- s.version = "0.6.2"
5
+ s.version = "0.7.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Pomelo, LLC"]
9
- s.date = %q{2010-04-23}
9
+ s.date = %q{2010-06-23}
10
10
  s.description = %q{logworm client utilities}
11
11
  s.email = %q{schapira@pomelollc.com}
12
12
  s.executables = ["lw-compute", "lw-tail"]
13
13
  s.extra_rdoc_files = ["CHANGELOG", "README", "bin/lw-compute", "bin/lw-tail", "lib/logworm_client.rb", "lib/logworm_client/logger.rb", "lib/logworm_client/rack.rb", "lib/logworm_client/rails.rb", "lib/logworm_utils.rb", "lib/logworm_utils/compute.rb", "lib/logworm_utils/tail.rb"]
14
- s.files = ["CHANGELOG", "Manifest", "README", "Rakefile", "bin/lw-compute", "bin/lw-tail", "lib/logworm_client.rb", "lib/logworm_client/logger.rb", "lib/logworm_client/rack.rb", "lib/logworm_client/rails.rb", "lib/logworm_utils.rb", "lib/logworm_utils/compute.rb", "lib/logworm_utils/tail.rb", "logworm_client.gemspec"]
14
+ s.files = ["CHANGELOG", "Manifest", "README", "Rakefile", "bin/lw-compute", "bin/lw-tail", "lib/logworm_client.rb", "lib/logworm_client/logger.rb", "lib/logworm_client/rack.rb", "lib/logworm_client/rails.rb", "lib/logworm_utils.rb", "lib/logworm_utils/compute.rb", "lib/logworm_utils/tail.rb", "spec/logger_spec.rb", "spec/rack_spec.rb", "spec/rails_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "logworm_client.gemspec"]
15
15
  s.homepage = %q{http://www.logworm.com}
16
16
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Logworm_client", "--main", "README"]
17
17
  s.require_paths = ["lib"]
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'webmock'
3
+ require 'logworm'
4
+
5
+ require File.dirname(__FILE__) + '/spec_helper'
6
+
7
+ $: << File.dirname(__FILE__) + '/../lib'
8
+ require 'logworm_client/logger.rb'
9
+
10
+ describe Logworm::Logger, " flushing" do
11
+ before do
12
+ Logworm::Logger.use_db Logworm::DB.new("logworm://a:b@localhost/c/d/")
13
+ end
14
+
15
+ it "should only record if it's been initialized" do
16
+ Logworm::Logger.use_db nil
17
+ Logworm::Logger.log(:tbl1, :a => 1)
18
+ Logworm::Logger.flush.should == [0,0]
19
+ end
20
+
21
+ it "should only record if there are entries" do
22
+ Logworm::Logger.flush.should == [0,0]
23
+ end
24
+
25
+ it "should try to record it it's initialized and has entries, and then reset" do
26
+ stub_request(:post, "localhost/log").to_return(:body => {:count => 10, :inserted_at => Time.now}.to_json)
27
+ Logworm::Logger.log(:tbl1, :a => 1)
28
+ Logworm::Logger.flush[0].should == 1
29
+ Logworm::Logger.flush.should == [0,0]
30
+ end
31
+
32
+ end
33
+
data/spec/rack_spec.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'webmock'
3
+ require 'logworm'
4
+
5
+ require File.dirname(__FILE__) + '/spec_helper'
6
+
7
+ $: << File.dirname(__FILE__) + '/../lib'
8
+ require 'logworm_client.rb'
9
+
10
+ describe Logworm::Rack, " init" do
11
+ it "should initialize the logger with a default db" do
12
+ Logworm::Logger.should_receive(:use_default_db)
13
+ Logworm::Rack.new("xx")
14
+ end
15
+ end
16
+
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'webmock'
3
+ require 'logworm'
4
+
5
+ require File.dirname(__FILE__) + '/spec_helper'
6
+
7
+ $: << File.dirname(__FILE__) + '/../lib'
8
+
9
+ describe Logworm, " init" do
10
+ it "should initialize the logger with a default db" do
11
+ require 'logworm_client/logger.rb' # ==> Get Logworm::Logger defined
12
+ Logworm::Logger.should_receive(:use_default_db)
13
+
14
+ require 'action_controller' # ==> Get ActionController defined
15
+ load 'logworm_client/rails.rb' # ==> does class_eval of ActionController (use load to guarantee require)
16
+ end
17
+ end
18
+
19
+ describe Logworm, " logging" do
20
+ it "should honor the filter_parameter_logging directive" do
21
+ end
22
+
23
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format specdoc
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'spec/autorun'
4
+ require 'spec/interop/test'
5
+ require 'webmock/rspec'
6
+
7
+ include WebMock
8
+
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 6
8
- - 2
9
- version: 0.6.2
7
+ - 7
8
+ - 0
9
+ version: 0.7.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Pomelo, LLC
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-23 00:00:00 -04:00
17
+ date: 2010-06-23 00:00:00 -04:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -102,6 +102,11 @@ files:
102
102
  - lib/logworm_utils.rb
103
103
  - lib/logworm_utils/compute.rb
104
104
  - lib/logworm_utils/tail.rb
105
+ - spec/logger_spec.rb
106
+ - spec/rack_spec.rb
107
+ - spec/rails_spec.rb
108
+ - spec/spec.opts
109
+ - spec/spec_helper.rb
105
110
  - logworm_client.gemspec
106
111
  has_rdoc: true
107
112
  homepage: http://www.logworm.com