spanx 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +20 -0
  2. data/.pairs +13 -0
  3. data/.rspec +2 -0
  4. data/.rvmrc +1 -0
  5. data/Gemfile +4 -0
  6. data/Guardfile +13 -0
  7. data/LICENSE +22 -0
  8. data/README.md +175 -0
  9. data/Rakefile +2 -0
  10. data/bin/spanx +7 -0
  11. data/conf/spanx-config.yml.example +44 -0
  12. data/conf/spanx-whitelist.txt.example +2 -0
  13. data/lib/spanx.rb +38 -0
  14. data/lib/spanx/actor/analyzer.rb +94 -0
  15. data/lib/spanx/actor/collector.rb +64 -0
  16. data/lib/spanx/actor/log_reader.rb +46 -0
  17. data/lib/spanx/actor/writer.rb +68 -0
  18. data/lib/spanx/cli.rb +47 -0
  19. data/lib/spanx/cli/analyze.rb +50 -0
  20. data/lib/spanx/cli/disable.rb +36 -0
  21. data/lib/spanx/cli/enable.rb +36 -0
  22. data/lib/spanx/cli/flush.rb +36 -0
  23. data/lib/spanx/cli/watch.rb +91 -0
  24. data/lib/spanx/config.rb +45 -0
  25. data/lib/spanx/helper.rb +8 -0
  26. data/lib/spanx/helper/exit.rb +11 -0
  27. data/lib/spanx/helper/subclassing.rb +31 -0
  28. data/lib/spanx/helper/timing.rb +9 -0
  29. data/lib/spanx/ip_checker.rb +5 -0
  30. data/lib/spanx/logger.rb +47 -0
  31. data/lib/spanx/notifier/audit_log.rb +18 -0
  32. data/lib/spanx/notifier/base.rb +22 -0
  33. data/lib/spanx/notifier/campfire.rb +47 -0
  34. data/lib/spanx/notifier/email.rb +61 -0
  35. data/lib/spanx/runner.rb +74 -0
  36. data/lib/spanx/usage.rb +9 -0
  37. data/lib/spanx/version.rb +3 -0
  38. data/lib/spanx/whitelist.rb +31 -0
  39. data/spanx.gemspec +32 -0
  40. data/spec/fixtures/access.log.1 +104 -0
  41. data/spec/fixtures/access.log.bots +7 -0
  42. data/spec/fixtures/config.yml +10 -0
  43. data/spec/fixtures/config_with_checks.yml +18 -0
  44. data/spec/fixtures/whitelist.txt +4 -0
  45. data/spec/spanx/actor/analyzer_spec.rb +114 -0
  46. data/spec/spanx/actor/collector_spec.rb +4 -0
  47. data/spec/spanx/actor/log_reader_spec.rb +68 -0
  48. data/spec/spanx/actor/writer_spec.rb +63 -0
  49. data/spec/spanx/config_spec.rb +62 -0
  50. data/spec/spanx/helper/timing_spec.rb +22 -0
  51. data/spec/spanx/notifier/base_spec.rb +16 -0
  52. data/spec/spanx/notifier/campfire_spec.rb +5 -0
  53. data/spec/spanx/notifier/email_spec.rb +121 -0
  54. data/spec/spanx/runner_spec.rb +102 -0
  55. data/spec/spanx/whitelist_spec.rb +66 -0
  56. data/spec/spec_helper.rb +25 -0
  57. data/spec/support/fakeredis.rb +1 -0
  58. data/spec/support/mail.rb +10 -0
  59. metadata +302 -0
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanx::Helper::Timing do
4
+ class TestClass
5
+ include Spanx::Helper::Timing
6
+ end
7
+
8
+ let(:tester) { TestClass.new }
9
+
10
+ describe "#period_marker" do
11
+ let(:time) { DateTime.parse('2001-02-02T21:03:26+00:00').to_time }
12
+
13
+ before { time.to_i.should == 981147806 }
14
+
15
+ it "returns unix time floored to the nearest resolution block" do
16
+ Timecop.freeze time do
17
+ tester.period_marker(10).should == 981147800
18
+ tester.period_marker(300).should == 981147600
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanx::Notifier::Base do
4
+
5
+ describe "message content" do
6
+ let(:time) { Time.now }
7
+ let(:ip_check) { Spanx::IPChecker.new("1.2.3.4") }
8
+ let(:period_check) { Pause::PeriodCheck.new(60, 100, 80)}
9
+ let(:blocked_action) { Pause::BlockedAction.new(ip_check, period_check, 500, time.to_i)}
10
+
11
+ it "should set the correct message content" do
12
+ Spanx::Notifier::Base.new.send(:generate_block_ip_message, blocked_action).should ==
13
+ "1.2.3.4 blocked @ #{time} for 1mins, for 500 requests over 1mins, with 100 allowed."
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanx::Notifier::Campfire do
4
+
5
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+ require 'mail'
3
+
4
+ describe Spanx::Notifier::Email, "#initialize" do
5
+ describe "#initialize" do
6
+ let(:config) { {
7
+ email: {
8
+ gateway: "a.b.com",
9
+ from: "me@me.com",
10
+ password: "p4ssw0rd",
11
+ domain: "my.domain.com"
12
+ }
13
+ } }
14
+
15
+ context "enabled" do
16
+ before { Spanx::Notifier::Email.any_instance.stub(:enabled?).and_return(true) }
17
+
18
+ it "should configure SMTP gateway" do
19
+ Spanx::Notifier::Email.new(config)
20
+ delivery_method = Mail::Configuration.instance.delivery_method
21
+ delivery_method.should be_an_instance_of(Mail::SMTP)
22
+ delivery_method.settings[:address].should == "a.b.com"
23
+ delivery_method.settings[:user_name].should == "me@me.com"
24
+ delivery_method.settings[:password].should == "p4ssw0rd"
25
+ delivery_method.settings[:domain].should == "my.domain.com"
26
+ end
27
+ end
28
+
29
+ context "when disabled" do
30
+ before {
31
+ Spanx::Notifier::Email.any_instance.stub(:enabled?).and_return(false)
32
+ }
33
+
34
+ it "should not configure email gateway" do
35
+ Mail.should_not_receive(:defaults)
36
+ Spanx::Notifier::Email.new(config)
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ describe Spanx::Notifier::Email, "#publish" do
43
+ include Mail::Matchers
44
+
45
+ subject { Spanx::Notifier::Email.new(config) }
46
+
47
+ let(:time_blocked) { Time.now }
48
+ let(:period) { mock() }
49
+ let(:action) { Spanx::IPChecker.new("1.2.3.4") }
50
+ let(:blocked_ip) { Pause::BlockedAction.new(action, period, 50, time_blocked) }
51
+
52
+ before { Spanx::Notifier::Email.any_instance.stub(:configure_email_gateway) }
53
+
54
+ context "when disabled" do
55
+ let(:config) { {} }
56
+
57
+ before {
58
+ Spanx::Notifier::Email.any_instance.stub(:enabled?).and_return(false)
59
+ subject.publish(blocked_ip)
60
+ }
61
+
62
+ it { should_not have_sent_email }
63
+ end
64
+
65
+ context "when enabled" do
66
+ let(:email_content) { "blocked email message" }
67
+ let(:email_subject) { nil }
68
+ let(:config) { {
69
+ email: {
70
+ to: "you@you.com",
71
+ from: "me@me.com",
72
+ subject: email_subject
73
+ }
74
+ } }
75
+
76
+ before {
77
+ Spanx::Notifier::Email.any_instance.stub(:enabled?).and_return(true)
78
+ Spanx::Notifier::Email.any_instance.stub(:generate_block_ip_message).and_return(email_content)
79
+ subject.publish(blocked_ip)
80
+ subject.thread.join
81
+ }
82
+
83
+ context "with email subject" do
84
+ let(:email_subject) { "OMG It's an email!" }
85
+
86
+ it {
87
+ should have_sent_email.
88
+ to("you@you.com").
89
+ from("me@me.com").
90
+ with_body(email_content).
91
+ with_subject("OMG It's an email! 1.2.3.4")
92
+ }
93
+ end
94
+
95
+ context "without a subject line" do
96
+ it {
97
+ should have_sent_email.
98
+ to("you@you.com").
99
+ from("me@me.com").
100
+ with_body(email_content).
101
+ with_subject("IP Blocked: 1.2.3.4")
102
+ }
103
+ end
104
+ end
105
+ end
106
+
107
+ describe Spanx::Notifier::Email, "#enabled?" do
108
+ subject { Spanx::Notifier::Email.new(config) }
109
+
110
+ context "with no email configuration" do
111
+ let(:config) { {} }
112
+
113
+ it { should_not be_enabled }
114
+ end
115
+
116
+ context "with enabled email configuration" do
117
+ let(:config) { {email: {enabled: true}} }
118
+
119
+ it { should be_enabled }
120
+ end
121
+ end
@@ -0,0 +1,102 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanx::Runner do
4
+ let(:config) { {:access_log => "logfile", :whitelist_file => "whitelist", :log_reader => {:tail_interval => 1}} }
5
+ let(:runner) { Spanx::Runner.new(config) }
6
+ let(:faker) { double() }
7
+
8
+ describe "#new" do
9
+ it "should create a new thread queue" do
10
+ runner.queue.should be_a(Queue)
11
+ end
12
+
13
+ context "actor initialization" do
14
+ let(:collector) { mock("collector") }
15
+ let(:writer) { mock("writer") }
16
+ let(:log_reader) { mock("log_reader") }
17
+ let(:analyzer) { mock("analyzer") }
18
+
19
+ before do
20
+ Spanx::Runner.any_instance.stub(:collector).and_return(collector)
21
+ Spanx::Runner.any_instance.stub(:writer).and_return(writer)
22
+ Spanx::Runner.any_instance.stub(:log_reader).and_return(log_reader)
23
+ Spanx::Runner.any_instance.stub(:analyzer).and_return(analyzer)
24
+ end
25
+
26
+ it "should match string args" do
27
+ Spanx::Runner.new("collector", config).actors.should == [collector]
28
+ Spanx::Runner.new("writer", config).actors.should == [writer]
29
+ Spanx::Runner.new("log_reader", config).actors.should == [log_reader]
30
+ Spanx::Runner.new("analyzer", config).actors.should == [analyzer]
31
+ Spanx::Runner.new("collector", "analyzer", config).actors.should == [collector, analyzer]
32
+ end
33
+
34
+ it "raises if an invalid actor is passed" do
35
+ lambda {
36
+ Spanx::Runner.new("methods", config)
37
+ }.should raise_error("Invalid actor")
38
+ end
39
+ end
40
+ end
41
+
42
+ describe "#run" do
43
+ let(:actor1) { mock("actor") }
44
+ let(:actor2) { mock("actor") }
45
+
46
+ before do
47
+ actor1.should_receive(:run).and_return(actor1)
48
+ actor2.should_receive(:run).and_return(actor2)
49
+ actor2.should_receive(:join).and_return(true)
50
+ end
51
+
52
+ it "runs all actors and joins the last one" do
53
+ runner.actors = [actor1, actor2]
54
+ runner.run
55
+ end
56
+ end
57
+
58
+ describe "#collector" do
59
+ before { Spanx::Actor::Collector.should_receive(:new).with(config, runner.queue).and_return(faker) }
60
+
61
+ it "should create a collector" do
62
+ runner.collector.should === faker
63
+ end
64
+ end
65
+
66
+ describe "#whitelist" do
67
+ before { Spanx::Whitelist.should_receive(:new).with("whitelist").and_return(faker) }
68
+
69
+ it "should create a collector" do
70
+ runner.whitelist.should === faker
71
+ end
72
+ end
73
+
74
+ describe "#log_reader" do
75
+ let(:whitelist) { double() }
76
+
77
+ before do
78
+ runner.should_receive(:whitelist).and_return(whitelist)
79
+ Spanx::Actor::LogReader.should_receive(:new).with("logfile", runner.queue, 1, whitelist).and_return(faker)
80
+ end
81
+
82
+ it "should create a log reader" do
83
+ runner.log_reader.should === faker
84
+ end
85
+ end
86
+
87
+ describe "#analyzer" do
88
+ before { Spanx::Actor::Analyzer.should_receive(:new).with(config).and_return(faker) }
89
+
90
+ it "should create an analyzer" do
91
+ runner.analyzer.should === faker
92
+ end
93
+ end
94
+
95
+ describe "#writer" do
96
+ before { Spanx::Actor::Writer.should_receive(:new).with(config).and_return(faker) }
97
+
98
+ it "should create an analyzer" do
99
+ runner.writer.should === faker
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spanx::Whitelist do
4
+ let(:whitelist) { Spanx::Whitelist.new(file) }
5
+ let(:file) { "spec/fixtures/whitelist.txt" }
6
+
7
+
8
+ describe "#new" do
9
+ context "with filename" do
10
+ it "loads whitelist patterns into memory" do
11
+ whitelist.patterns[0].should eql(/^127\.0\.0\.1/)
12
+ whitelist.patterns[1].should eql(/^10\.1\.\d{1,3}\.\d{1,3}/)
13
+ whitelist.patterns.each{ |p| p.is_a?(Regexp).should be_true }
14
+ end
15
+ end
16
+
17
+ context "without filename" do
18
+ let(:file) { nil }
19
+
20
+ it "keeps an empty whitelist table" do
21
+ whitelist.patterns.should == []
22
+ end
23
+ end
24
+
25
+ context "with non-existent file" do
26
+ let(:file) { "non-existent-whitelist" }
27
+
28
+ it "writes an error to stderr and exits" do
29
+ $stderr.should_receive(:puts).with("Error: Unable to find whitelist file at #{file}")
30
+ $stderr.should_receive(:puts).with(Spanx::USAGE)
31
+
32
+ lambda {
33
+ whitelist.patterns
34
+ }.should raise_error(SystemExit)
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ describe "#match?" do
41
+ context "IP address matching" do
42
+ it 'is true if IP address is in match list' do
43
+ whitelist.match?("127.0.0.1").should be_true
44
+ end
45
+
46
+ it 'is false if IP address does not match patterns' do
47
+ whitelist.match?("sadfasdf").should be_false
48
+ end
49
+ end
50
+
51
+ context "User agent matches pattern" do
52
+ let(:googlebot) { %Q{66.249.73.24 - - [18/Oct/2012:03:25:33 -0700] GET /p/2213071/39535615 HTTP/1.1 "200" 3943 "-" "-" "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" "2.87""Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" "-"upstream_addr 127.0.0.1:8100upstream_response_time 0.082 request_time 0.082} }
53
+ it "whitelists googlebot" do
54
+ whitelist.match?(googlebot).should be_true
55
+ end
56
+ end
57
+
58
+ context "users/me matches" do
59
+ let(:log) { %Q{66.249.73.24 - - [18/Oct/2012:03:25:33 -0700] GET /users/me HTTP/1.1 "200" 3943 "-" "-" "Mozilla/5.0 } }
60
+ it "excludes users/me" do
61
+ whitelist.match?(log).should be_true
62
+ end
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+
8
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
9
+ require 'rubygems'
10
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
11
+ require 'spanx'
12
+
13
+ Dir['spec/support/**/*.rb'].each { |filename| require_relative "../#{filename}" }
14
+
15
+ RSpec.configure do |config|
16
+ config.treat_symbols_as_metadata_keys_with_true_values = true
17
+ config.run_all_when_everything_filtered = true
18
+ config.filter_run :focus
19
+
20
+ # Run specs in random order to surface order dependencies. If you find an
21
+ # order dependency and want to debug it, you can fix the order by providing
22
+ # the seed, which is printed after each run.
23
+ # --seed 1234
24
+ config.order = 'random'
25
+ end
@@ -0,0 +1 @@
1
+ require 'fakeredis/rspec'
@@ -0,0 +1,10 @@
1
+ require 'mail'
2
+
3
+ RSpec.configure do |config|
4
+ config.before(:each) do
5
+ Mail::TestMailer.deliveries.clear
6
+ Mail.defaults do
7
+ delivery_method :test
8
+ end
9
+ end
10
+ end
metadata ADDED
@@ -0,0 +1,302 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spanx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Konstantin Gredeskoul
9
+ - Eric Saxby
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-11-14 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: pause
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 0.0.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: 0.0.3
31
+ - !ruby/object:Gem::Dependency
32
+ name: file-tail
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ! '>='
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: mixlib-cli
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: daemons
65
+ requirement: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ type: :runtime
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ - !ruby/object:Gem::Dependency
80
+ name: tinder
81
+ requirement: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: mail
97
+ requirement: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ~>
101
+ - !ruby/object:Gem::Version
102
+ version: 2.4.4
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 2.4.4
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ - !ruby/object:Gem::Dependency
128
+ name: fakeredis
129
+ requirement: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ type: :development
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ none: false
139
+ requirements:
140
+ - - ! '>='
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ - !ruby/object:Gem::Dependency
144
+ name: timecop
145
+ requirement: !ruby/object:Gem::Requirement
146
+ none: false
147
+ requirements:
148
+ - - ! '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ type: :development
152
+ prerelease: false
153
+ version_requirements: !ruby/object:Gem::Requirement
154
+ none: false
155
+ requirements:
156
+ - - ! '>='
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: guard-rspec
161
+ requirement: !ruby/object:Gem::Requirement
162
+ none: false
163
+ requirements:
164
+ - - ! '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ type: :development
168
+ prerelease: false
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ none: false
171
+ requirements:
172
+ - - ! '>='
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ - !ruby/object:Gem::Dependency
176
+ name: rb-fsevent
177
+ requirement: !ruby/object:Gem::Requirement
178
+ none: false
179
+ requirements:
180
+ - - ! '>='
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ type: :development
184
+ prerelease: false
185
+ version_requirements: !ruby/object:Gem::Requirement
186
+ none: false
187
+ requirements:
188
+ - - ! '>='
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ description: Real time IP parsing and rate detection gem for access_log files
192
+ email:
193
+ - kigster@gmail.com
194
+ - sax@livinginthepast.org
195
+ executables:
196
+ - spanx
197
+ extensions: []
198
+ extra_rdoc_files: []
199
+ files:
200
+ - .gitignore
201
+ - .pairs
202
+ - .rspec
203
+ - .rvmrc
204
+ - Gemfile
205
+ - Guardfile
206
+ - LICENSE
207
+ - README.md
208
+ - Rakefile
209
+ - bin/spanx
210
+ - conf/spanx-config.yml.example
211
+ - conf/spanx-whitelist.txt.example
212
+ - lib/spanx.rb
213
+ - lib/spanx/actor/analyzer.rb
214
+ - lib/spanx/actor/collector.rb
215
+ - lib/spanx/actor/log_reader.rb
216
+ - lib/spanx/actor/writer.rb
217
+ - lib/spanx/cli.rb
218
+ - lib/spanx/cli/analyze.rb
219
+ - lib/spanx/cli/disable.rb
220
+ - lib/spanx/cli/enable.rb
221
+ - lib/spanx/cli/flush.rb
222
+ - lib/spanx/cli/watch.rb
223
+ - lib/spanx/config.rb
224
+ - lib/spanx/helper.rb
225
+ - lib/spanx/helper/exit.rb
226
+ - lib/spanx/helper/subclassing.rb
227
+ - lib/spanx/helper/timing.rb
228
+ - lib/spanx/ip_checker.rb
229
+ - lib/spanx/logger.rb
230
+ - lib/spanx/notifier/audit_log.rb
231
+ - lib/spanx/notifier/base.rb
232
+ - lib/spanx/notifier/campfire.rb
233
+ - lib/spanx/notifier/email.rb
234
+ - lib/spanx/runner.rb
235
+ - lib/spanx/usage.rb
236
+ - lib/spanx/version.rb
237
+ - lib/spanx/whitelist.rb
238
+ - spanx.gemspec
239
+ - spec/fixtures/access.log.1
240
+ - spec/fixtures/access.log.bots
241
+ - spec/fixtures/config.yml
242
+ - spec/fixtures/config_with_checks.yml
243
+ - spec/fixtures/whitelist.txt
244
+ - spec/spanx/actor/analyzer_spec.rb
245
+ - spec/spanx/actor/collector_spec.rb
246
+ - spec/spanx/actor/log_reader_spec.rb
247
+ - spec/spanx/actor/writer_spec.rb
248
+ - spec/spanx/config_spec.rb
249
+ - spec/spanx/helper/timing_spec.rb
250
+ - spec/spanx/notifier/base_spec.rb
251
+ - spec/spanx/notifier/campfire_spec.rb
252
+ - spec/spanx/notifier/email_spec.rb
253
+ - spec/spanx/runner_spec.rb
254
+ - spec/spanx/whitelist_spec.rb
255
+ - spec/spec_helper.rb
256
+ - spec/support/fakeredis.rb
257
+ - spec/support/mail.rb
258
+ homepage: https://github.com/wanelo/spanx
259
+ licenses: []
260
+ post_install_message:
261
+ rdoc_options: []
262
+ require_paths:
263
+ - lib
264
+ required_ruby_version: !ruby/object:Gem::Requirement
265
+ none: false
266
+ requirements:
267
+ - - ! '>='
268
+ - !ruby/object:Gem::Version
269
+ version: '0'
270
+ required_rubygems_version: !ruby/object:Gem::Requirement
271
+ none: false
272
+ requirements:
273
+ - - ! '>='
274
+ - !ruby/object:Gem::Version
275
+ version: '0'
276
+ requirements: []
277
+ rubyforge_project:
278
+ rubygems_version: 1.8.24
279
+ signing_key:
280
+ specification_version: 3
281
+ summary: Real time IP parsing and rate detection gem for access_log files
282
+ test_files:
283
+ - spec/fixtures/access.log.1
284
+ - spec/fixtures/access.log.bots
285
+ - spec/fixtures/config.yml
286
+ - spec/fixtures/config_with_checks.yml
287
+ - spec/fixtures/whitelist.txt
288
+ - spec/spanx/actor/analyzer_spec.rb
289
+ - spec/spanx/actor/collector_spec.rb
290
+ - spec/spanx/actor/log_reader_spec.rb
291
+ - spec/spanx/actor/writer_spec.rb
292
+ - spec/spanx/config_spec.rb
293
+ - spec/spanx/helper/timing_spec.rb
294
+ - spec/spanx/notifier/base_spec.rb
295
+ - spec/spanx/notifier/campfire_spec.rb
296
+ - spec/spanx/notifier/email_spec.rb
297
+ - spec/spanx/runner_spec.rb
298
+ - spec/spanx/whitelist_spec.rb
299
+ - spec/spec_helper.rb
300
+ - spec/support/fakeredis.rb
301
+ - spec/support/mail.rb
302
+ has_rdoc: