logworm_client 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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