papermill-agent 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -2
- data/Rakefile +1 -2
- data/lib/papermill-agent/agent.rb +33 -23
- data/lib/papermill-agent/configurator.rb +63 -0
- data/lib/papermill-agent/logger.rb +14 -0
- data/lib/papermill-agent/response_adapters/base.rb +24 -4
- data/lib/papermill-agent/response_parser.rb +5 -9
- data/lib/papermill-agent/storage.rb +8 -0
- data/lib/papermill-agent.rb +5 -0
- data/spec/agent_spec.rb +168 -22
- data/spec/configurator_spec.rb +180 -0
- data/spec/logger_spec.rb +37 -0
- data/spec/rack/collector_spec.rb +25 -8
- data/spec/response_adapters/base_spec.rb +13 -0
- data/spec/response_parser_spec.rb +5 -3
- data/spec/spec_helper.rb +10 -0
- data/spec/storage_spec.rb +27 -0
- metadata +11 -3
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -2,31 +2,34 @@ require 'singleton'
|
|
2
2
|
require 'timeout'
|
3
3
|
require 'json'
|
4
4
|
require 'restclient'
|
5
|
-
require 'yaml'
|
6
5
|
|
7
6
|
module Papermill
|
8
7
|
|
9
8
|
class Agent
|
10
9
|
include Singleton
|
11
|
-
|
12
|
-
# papermill endpoint which will receive client requests
|
13
|
-
API_ENDPOINT = 'http://api.papermillapp.com'
|
14
10
|
|
15
11
|
# send new request data every 10 seconds
|
16
12
|
UPDATE_INTERVAL = 10
|
17
13
|
|
18
|
-
attr_reader :last_sent, :mutex, :config
|
14
|
+
attr_reader :last_sent, :mutex, :config, :logger
|
15
|
+
|
16
|
+
# request timeout threshold for sending data to papermill
|
17
|
+
@@request_timeout = 5
|
18
|
+
def self.request_timeout
|
19
|
+
@@request_timeout
|
20
|
+
end
|
19
21
|
|
20
22
|
def start
|
21
23
|
@last_sent = Time.now
|
22
24
|
@mutex = Mutex.new
|
23
|
-
@config =
|
25
|
+
@config = Configurator.new
|
26
|
+
@logger = Logger.new
|
27
|
+
logger.info "\n\n=============== Starting papermill-agent at #{Time.now} in process #{Process.pid}"
|
24
28
|
Thread.abort_on_exception = true
|
25
29
|
|
26
30
|
@worker_thread = Thread.new do
|
27
31
|
loop do
|
28
32
|
if time_since_last_sent > UPDATE_INTERVAL
|
29
|
-
p "sending #{Storage.store.count} requests to papermill..."
|
30
33
|
send_data_to_papermill
|
31
34
|
@last_sent = Time.now
|
32
35
|
end
|
@@ -36,38 +39,45 @@ module Papermill
|
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
39
|
-
def time_since_last_sent
|
40
|
-
Time.now - last_sent
|
41
|
-
end
|
42
|
-
|
43
|
-
def seconds_until_next_run
|
44
|
-
UPDATE_INTERVAL - time_since_last_sent
|
45
|
-
end
|
46
|
-
|
47
42
|
def send_data_to_papermill
|
48
43
|
begin
|
49
|
-
Timeout.timeout(
|
50
|
-
|
51
|
-
|
44
|
+
Timeout.timeout(config.setting('request_timout') || self.class.request_timeout) do
|
45
|
+
do_request if !Storage.empty? && config.live_mode
|
46
|
+
end
|
47
|
+
rescue Timeout::Error => e
|
48
|
+
logger.log_exception(e)
|
52
49
|
end
|
53
50
|
end
|
54
51
|
|
55
52
|
def do_request
|
56
53
|
begin
|
57
|
-
|
58
|
-
|
59
|
-
|
54
|
+
logger.info("#{Time.now}: Sending #{Storage.size} requests to papermill")
|
55
|
+
RestClient.post config.endpoint, { :token => config.token, :payload => jsonify_payload }
|
56
|
+
Storage.clear
|
57
|
+
rescue RestClient::Exception, Errno::ECONNREFUSED, SocketError => e
|
58
|
+
logger.log_exception e
|
60
59
|
end
|
61
60
|
end
|
62
61
|
|
62
|
+
def time_since_last_sent
|
63
|
+
Time.now - last_sent
|
64
|
+
end
|
65
|
+
|
66
|
+
def seconds_until_next_run
|
67
|
+
UPDATE_INTERVAL - time_since_last_sent
|
68
|
+
end
|
69
|
+
|
63
70
|
# Return a json version of the storage array.
|
64
71
|
# Also, we'll clear out the storage here
|
65
72
|
#
|
66
73
|
# TODO: move this to the Storage class.
|
67
74
|
def jsonify_payload
|
68
75
|
mutex.synchronize do
|
69
|
-
|
70
|
-
|
76
|
+
begin
|
77
|
+
json_data = Storage.store.flatten.to_json
|
78
|
+
rescue => e
|
79
|
+
logger.log_exception(e)
|
80
|
+
end
|
71
81
|
return json_data
|
72
82
|
end
|
73
83
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Papermill
|
4
|
+
|
5
|
+
class Configurator
|
6
|
+
attr_writer :environment
|
7
|
+
|
8
|
+
def initialize(config_source = 'config/papermill.yml')
|
9
|
+
load_config(config_source)
|
10
|
+
end
|
11
|
+
|
12
|
+
def load_config(config_source)
|
13
|
+
@config = case
|
14
|
+
when config_source.class == String
|
15
|
+
YAML.load_file(config_source)
|
16
|
+
when config_source.class == Hash
|
17
|
+
config_source
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def token
|
22
|
+
config['token']
|
23
|
+
end
|
24
|
+
|
25
|
+
def endpoint
|
26
|
+
if setting('endpoint') && environment != 'production'
|
27
|
+
config[environment]['endpoint']
|
28
|
+
else
|
29
|
+
API_ENDPOINT
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# returns the value of a setting for the current environment or nil,
|
34
|
+
# if it does not exist.
|
35
|
+
def setting(name)
|
36
|
+
config[environment] && config[environment][name]
|
37
|
+
end
|
38
|
+
|
39
|
+
def live_mode
|
40
|
+
if has_config_for_environment?('live_mode')
|
41
|
+
setting('live_mode')
|
42
|
+
else
|
43
|
+
# default to true for production environments, false otherwise
|
44
|
+
environment == 'production' ? true : false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def environment
|
49
|
+
@environment ||= (defined?(Rails) && Rails.env) || ENV['RACK_ENV'] || 'development'
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# holds the internal config hash
|
55
|
+
def config
|
56
|
+
@config
|
57
|
+
end
|
58
|
+
|
59
|
+
def has_config_for_environment?(var)
|
60
|
+
config[environment] && config[environment].has_key?(var)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Papermill
|
4
|
+
class Logger < ::Logger
|
5
|
+
def initialize(io_object = 'log/papermill.log')
|
6
|
+
FileUtils.mkdir('log') unless Dir.exists?('log')
|
7
|
+
super(io_object)
|
8
|
+
end
|
9
|
+
|
10
|
+
def log_exception(exception)
|
11
|
+
error(exception.message + exception.backtrace.join("\n"))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -3,13 +3,20 @@ module Papermill
|
|
3
3
|
module ResponseAdapters
|
4
4
|
|
5
5
|
class Base
|
6
|
+
ENV_CAPTURE_FIELDS = [
|
7
|
+
'PATH_INFO', 'QUERY_STRING', 'REMOTE_ADDR', 'REMOTE_HOST', 'REQUEST_METHOD',
|
8
|
+
'REQUEST_URI', 'SCRIPT_NAME', 'SERVER_NAME', 'SERVER_SOFTWARE', 'HTTP_HOST',
|
9
|
+
'HTTP_ACCEPT', 'HTTP_USER_AGENT', 'REQUEST_PATH', 'rack.url_scheme',
|
10
|
+
'HTTP_REFERER'
|
11
|
+
]
|
12
|
+
|
6
13
|
attr_reader :status, :headers, :response, :env
|
7
|
-
def initialize(
|
8
|
-
@status, @headers, @response, @env =
|
14
|
+
def initialize(status, headers, response, env = {})
|
15
|
+
@status, @headers, @response, @env = status, headers, response, env
|
9
16
|
end
|
10
17
|
|
11
18
|
def parse
|
12
|
-
parsed_response = { :headers =>
|
19
|
+
parsed_response = { :headers => extract_env_info, :status => status }
|
13
20
|
# if @status != 304 && @response #&& !@response.body.is_a?(Proc)
|
14
21
|
# parsed_response.merge!(prepare_extra_fields)
|
15
22
|
# end
|
@@ -19,8 +26,21 @@ module Papermill
|
|
19
26
|
|
20
27
|
private
|
21
28
|
def additional_response_data
|
22
|
-
{
|
29
|
+
{'request_time' => Time.now}
|
23
30
|
end
|
31
|
+
|
32
|
+
def extract_env_info
|
33
|
+
result_hash = env.select { |key, value| ENV_CAPTURE_FIELDS.include?(key) }
|
34
|
+
|
35
|
+
# rename rack.* keys b/c mongo does not allow key names containing a .
|
36
|
+
rack_keys = ENV_CAPTURE_FIELDS.select { |key| key =~ /^rack\./ }
|
37
|
+
rack_keys.each do |key|
|
38
|
+
new_key = key.gsub(/^rack\./, '').upcase
|
39
|
+
result_hash[new_key] = result_hash.delete(key)
|
40
|
+
end
|
41
|
+
result_hash
|
42
|
+
end
|
43
|
+
|
24
44
|
end
|
25
45
|
|
26
46
|
end
|
@@ -2,22 +2,18 @@
|
|
2
2
|
module Papermill
|
3
3
|
|
4
4
|
class ResponseParser
|
5
|
-
|
6
|
-
|
7
|
-
# 'REQUEST_URI', 'SCRIPT_NAME', 'SERVER_NAME', 'SERVER_SOFTWARE', 'HTTP_HOST',
|
8
|
-
# 'HTTP_ACCEPT', 'HTTP_USER_AGENT', 'REQUEST_PATH'
|
9
|
-
# ]
|
10
|
-
|
11
|
-
def self.parse(status, headers, response, env = {})
|
12
|
-
klass = if defined?(Rails)
|
5
|
+
def self.parse_klass
|
6
|
+
@klass ||= if defined?(Rails)
|
13
7
|
ResponseAdapters::Rails
|
14
8
|
elsif defined?(Sinatra)
|
15
9
|
ResponseAdapters::Sinatra
|
16
10
|
else
|
17
11
|
ResponseAdapters::Base
|
18
12
|
end
|
13
|
+
end
|
19
14
|
|
20
|
-
|
15
|
+
def self.parse(status, headers, response, env = {})
|
16
|
+
parse_klass.new(status, headers, response, env).parse
|
21
17
|
end
|
22
18
|
end
|
23
19
|
|
data/lib/papermill-agent.rb
CHANGED
@@ -2,8 +2,13 @@
|
|
2
2
|
$:.unshift File.dirname(File.expand_path(__FILE__))
|
3
3
|
|
4
4
|
module Papermill
|
5
|
+
# papermill endpoint which will receive client requests
|
6
|
+
API_ENDPOINT = 'http://api.papermillapp.com'
|
7
|
+
|
5
8
|
autoload :Agent, 'papermill-agent/agent'
|
6
9
|
autoload :Collector, 'papermill-agent/rack/collector'
|
10
|
+
autoload :Configurator, 'papermill-agent/configurator'
|
11
|
+
autoload :Logger, 'papermill-agent/logger'
|
7
12
|
autoload :ResponseParser, 'papermill-agent/response_parser'
|
8
13
|
autoload :Storage, 'papermill-agent/storage'
|
9
14
|
|
data/spec/agent_spec.rb
CHANGED
@@ -4,19 +4,19 @@ module Papermill
|
|
4
4
|
|
5
5
|
describe 'the agent' do
|
6
6
|
it 'has a mutex' do
|
7
|
-
|
7
|
+
agent.mutex.should be_instance_of Mutex
|
8
8
|
end
|
9
9
|
|
10
10
|
context 'when a new process is started' do
|
11
11
|
context 'when an agent process does not already exist' do
|
12
12
|
it 'should create a new agent process' do
|
13
|
-
|
13
|
+
Agent.instance.should be_instance_of Papermill::Agent
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
17
|
context 'when an agent process already exists' do
|
18
18
|
it 'should only allow one agent instance to exist' do
|
19
|
-
|
19
|
+
Agent.instance.should eql Papermill::Agent.instance
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
@@ -29,62 +29,208 @@ module Papermill
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
context 'when in a production environment' do
|
33
|
+
before { agent.config.stub(:environment => 'production') }
|
34
|
+
|
35
|
+
context 'and the storage is empty' do
|
36
|
+
before { Storage.clear }
|
37
|
+
|
38
|
+
it 'does not send anything to papermill' do
|
39
|
+
agent.should_not_receive(:do_request)
|
40
|
+
agent.send_data_to_papermill
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'and the storage is not empty' do
|
45
|
+
before { Storage.store << ['an item'] }
|
46
|
+
|
47
|
+
it 'sends data to papermill' do
|
48
|
+
agent.should_receive(:do_request)
|
49
|
+
agent.send_data_to_papermill
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when in a non-production environment' do
|
55
|
+
before { agent.config.environment = 'non-production' }
|
56
|
+
|
57
|
+
context 'and there is no config over-ride to log requests in that environment' do
|
58
|
+
context 'and the storage is not empty' do
|
59
|
+
before { Storage.store << [{:headers => {'Content-Type' => 'text/html'}, :status => 200}] }
|
60
|
+
|
61
|
+
it 'does not send data to papermill' do
|
62
|
+
agent.should_not_receive(:do_request)
|
63
|
+
agent.send_data_to_papermill
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'and the storage is empty' do
|
68
|
+
before { Storage.clear }
|
69
|
+
|
70
|
+
it 'does not send data to papermill' do
|
71
|
+
agent.should_not_receive(:do_request)
|
72
|
+
agent.send_data_to_papermill
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context 'and there is a config over-ride present for that environment' do
|
78
|
+
before { agent.config.stub!(:live_mode => true) }
|
79
|
+
|
80
|
+
context 'when the storage is empty' do
|
81
|
+
before { Storage.clear }
|
82
|
+
|
83
|
+
it 'sends data to papermill' do
|
84
|
+
agent.should_not_receive(:do_request)
|
85
|
+
agent.send_data_to_papermill
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when the storage is not empty' do
|
90
|
+
before { Storage.store << [{:headers => {'Content-Type' => 'text/html'}, :status => 200}] }
|
91
|
+
|
92
|
+
it 'sends data to papermill' do
|
93
|
+
agent.should_receive(:do_request)
|
94
|
+
agent.send_data_to_papermill
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'when in a production environment' do
|
101
|
+
before { agent.config.environment = 'production' }
|
102
|
+
|
103
|
+
context 'by default' do
|
104
|
+
context 'when requests have been logged' do
|
105
|
+
before { Storage.store << [{:headers => {'Content-Type' => 'text/html'}, :status => 200}] }
|
106
|
+
|
107
|
+
it 'sends request data to the papermill api' do
|
108
|
+
agent.should_receive(:do_request)
|
109
|
+
agent.send_data_to_papermill
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'when no requests have been logged' do
|
114
|
+
before { Storage.clear }
|
115
|
+
|
116
|
+
it 'does not send data to papermill' do
|
117
|
+
agent.should_not_receive(:do_request)
|
118
|
+
agent.send_data_to_papermill
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'if a config over-ride is specified to not send data to papermill' do
|
124
|
+
before { agent.config.stub!(:live_mode => false) }
|
125
|
+
|
126
|
+
context 'when requests have been logged' do
|
127
|
+
before { Storage.store << [{:headers => {'Content-Type' => 'text/html'}, :status => 200}] }
|
128
|
+
|
129
|
+
it 'does not send data to papermill' do
|
130
|
+
agent.should_not_receive(:do_request)
|
131
|
+
agent.send_data_to_papermill
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
context 'when no requests have been logged' do
|
136
|
+
before { Storage.clear }
|
137
|
+
|
138
|
+
it 'does not send data to papermill' do
|
139
|
+
agent.should_not_receive(:do_request)
|
140
|
+
agent.send_data_to_papermill
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
32
146
|
end
|
33
147
|
|
34
148
|
describe 'the api endpoint' do
|
35
|
-
subject {
|
149
|
+
subject { Papermill::API_ENDPOINT }
|
36
150
|
it { should == 'http://api.papermillapp.com' }
|
37
151
|
end
|
38
152
|
|
153
|
+
describe 'request timeout interval' do
|
154
|
+
it 'defaults to 5 seconds' do
|
155
|
+
Agent.request_timeout.should == 5
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
39
159
|
describe 'determining the time since the last time data was sent to papermill' do
|
40
160
|
before do
|
41
|
-
|
161
|
+
agent.stub!(:last_sent => Time.mktime(2010, 11, 11, 0, 0, 0))
|
42
162
|
Time.stub!(:now => Time.mktime(2010, 11, 11, 0, 0, 9))
|
43
163
|
end
|
44
164
|
|
45
165
|
context 'time since the last run' do
|
46
166
|
it 'subtracts the current time from @last_sent' do
|
47
|
-
|
167
|
+
agent.time_since_last_sent.should == 9
|
48
168
|
end
|
49
169
|
end
|
50
170
|
|
51
171
|
context 'the time until the next run' do
|
52
172
|
it 'time until next = interval - time elapsed since last' do
|
53
|
-
|
173
|
+
agent.seconds_until_next_run.should == 1
|
54
174
|
end
|
55
175
|
end
|
56
|
-
|
57
176
|
end
|
58
177
|
|
59
178
|
describe 'configuration' do
|
60
179
|
it 'provides access to the api token' do
|
61
|
-
|
180
|
+
agent.config.token.should == 'api-key'
|
62
181
|
end
|
63
182
|
end
|
64
183
|
|
65
|
-
describe '
|
184
|
+
describe 'given a logged request' do
|
66
185
|
before do
|
67
186
|
Storage.store << [{:headers => {'Content-Type' => 'text/html'}, :status => 200}]
|
68
187
|
end
|
69
188
|
|
70
|
-
|
71
|
-
|
189
|
+
describe 'serializing the payload' do
|
190
|
+
it 'jsonifies the payload data' do
|
191
|
+
agent.jsonify_payload.should == '[{"headers":{"Content-Type":"text/html"},"status":200}]'
|
192
|
+
end
|
72
193
|
end
|
73
194
|
|
74
|
-
|
75
|
-
|
76
|
-
|
195
|
+
describe 'sending data to papermill' do
|
196
|
+
before { agent.config.stub!(:environment => 'production') }
|
197
|
+
|
198
|
+
it 'empties the store' do
|
199
|
+
agent.send_data_to_papermill
|
200
|
+
Storage.store.should be_empty
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'sends a request to the papermill api endpoint' do
|
204
|
+
RestClient.should_receive(:post).with(Papermill::API_ENDPOINT, :token => 'api-key', :payload => '[{"headers":{"Content-Type":"text/html"},"status":200}]')
|
205
|
+
agent.send_data_to_papermill
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'does not send anything if no requests have been stored' do
|
209
|
+
Storage.clear
|
210
|
+
Agent.should_not_receive(:do_request)
|
211
|
+
agent.send_data_to_papermill
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'logs a message to the logger' do
|
215
|
+
agent.logger.should_receive(:info).with(/Sending 1 requests to papermill/)
|
216
|
+
agent.send_data_to_papermill
|
217
|
+
end
|
77
218
|
end
|
78
219
|
|
79
|
-
|
80
|
-
|
81
|
-
|
220
|
+
end
|
221
|
+
|
222
|
+
describe 'the logger' do
|
223
|
+
before { agent.start }
|
224
|
+
|
225
|
+
it 'has a logger' do
|
226
|
+
agent.logger.should be_instance_of Papermill::Logger
|
82
227
|
end
|
228
|
+
end
|
83
229
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
230
|
+
describe 'starting the agent' do
|
231
|
+
it 'loads the configurator' do
|
232
|
+
agent.start
|
233
|
+
agent.config.should be_instance_of Configurator
|
88
234
|
end
|
89
235
|
end
|
90
236
|
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Papermill
|
4
|
+
|
5
|
+
describe 'loading the configurator' do
|
6
|
+
context 'when a string is passed in' do
|
7
|
+
it 'attempts to load the yaml file by that name' do
|
8
|
+
YAML.should_receive(:load_file).with('filename')
|
9
|
+
Configurator.new('filename')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when a hash is passed in' do
|
14
|
+
it 'uses the hash as the config object' do
|
15
|
+
Configurator.new(mock_config_hash).token.should == mock_config_hash['token']
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'loads the papermill yaml config file' do
|
20
|
+
Configurator.new
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'given a configurator instance' do
|
25
|
+
let(:configurator) do
|
26
|
+
YAML.stub!(:load_file).and_return({'token' => 'token'})
|
27
|
+
Configurator.new
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'the token' do
|
31
|
+
it 'should return the token from the config' do
|
32
|
+
configurator.token.should == 'token'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'configuring the api endpoint' do
|
37
|
+
let(:configurator) { Configurator.new(
|
38
|
+
mock_config_hash.merge(
|
39
|
+
'test' => {'endpoint' => 'another endpoint'},
|
40
|
+
'production' => {'endpoint' => 'fake production endpoint'})
|
41
|
+
)
|
42
|
+
}
|
43
|
+
|
44
|
+
context 'for non-production environments' do
|
45
|
+
it 'allows the user to over-ride the endpoint' do
|
46
|
+
configurator.stub!(:environment => 'test')
|
47
|
+
configurator.endpoint.should == 'another endpoint'
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'defaults to the api endpoint if nothing else has been specified' do
|
51
|
+
configurator.stub!(:environment => 'an undefined env')
|
52
|
+
configurator.endpoint.should == Papermill::API_ENDPOINT
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'for production environments' do
|
57
|
+
before { configurator.stub!(:environment => 'production') }
|
58
|
+
|
59
|
+
it "always sets the endpoint to papermillapp's servers" do
|
60
|
+
configurator.endpoint.should == Papermill::API_ENDPOINT
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should be the fake endpoint' do
|
64
|
+
configurator.endpoint.should_not == 'fake production endpoint'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'determining the environment' do
|
70
|
+
let(:configurator) { Configurator.new(mock_config_hash) }
|
71
|
+
|
72
|
+
context 'when in a Rails app' do
|
73
|
+
before do
|
74
|
+
Papermill.class_eval do
|
75
|
+
# create a fake rails class
|
76
|
+
class Rails
|
77
|
+
def self.env; 'rails environment'; end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
# remove the fake rails class
|
82
|
+
after { Papermill.class_eval { remove_const(:Rails) } }
|
83
|
+
|
84
|
+
it 'get the environment from rails' do
|
85
|
+
configurator.environment.should == 'rails environment'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'when in a rack app' do
|
90
|
+
before do
|
91
|
+
ENV.stub!(:[]).with('RACK_ENV').and_return('rack env')
|
92
|
+
end
|
93
|
+
|
94
|
+
it "gets the environment from ENV['RACK_ENV']" do
|
95
|
+
configurator.environment.should == 'rack env'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'not in rails or a rack app' do
|
100
|
+
before { ENV['RACK_ENV'] = nil }
|
101
|
+
after { ENV['RACK_ENV'] = 'test' }
|
102
|
+
|
103
|
+
it 'defaults the environment to development' do
|
104
|
+
configurator.environment.should == 'development'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe 'setting the environment' do
|
111
|
+
let(:configurator) { Configurator.new }
|
112
|
+
before { configurator.environment = 'some environment' }
|
113
|
+
|
114
|
+
it 'allows manual setting of the envionment' do
|
115
|
+
configurator.environment.should == 'some environment'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe 'determining whether or not to send data to papermill' do
|
120
|
+
context 'for a production environment' do
|
121
|
+
let(:config) { Configurator.new }
|
122
|
+
before { config.environment = 'production' }
|
123
|
+
|
124
|
+
context 'by default' do
|
125
|
+
it 'is true' do
|
126
|
+
config.live_mode.should == true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'when a config override is specified with a value of false' do
|
131
|
+
let(:config) { Configurator.new({'production' => {'live_mode' => false}}) }
|
132
|
+
|
133
|
+
it 'does not send data to papermill' do
|
134
|
+
config.environment.should == 'production'
|
135
|
+
config.live_mode.should == false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context 'for a non-production environment' do
|
141
|
+
context 'by default' do
|
142
|
+
let(:config) { Configurator.new }
|
143
|
+
before { config.environment = 'non-production' }
|
144
|
+
|
145
|
+
it 'is false' do
|
146
|
+
config.live_mode.should == false
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'when an environment override is specified' do
|
151
|
+
let(:config) { Configurator.new('non-production' => {'live_mode' => true}) }
|
152
|
+
before { config.environment = 'non-production' }
|
153
|
+
|
154
|
+
it 'uses that setting' do
|
155
|
+
config.live_mode.should == true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe 'retrieving a setting for the current environment' do
|
162
|
+
context 'for a given environment' do
|
163
|
+
let(:config) { Configurator.new({'production' => {'my key' => 'my val'}}) }
|
164
|
+
before { config.environment = 'production' }
|
165
|
+
|
166
|
+
context 'for a setting that has a value' do
|
167
|
+
it 'returns the value of the setting identified by the name' do
|
168
|
+
config.setting('my key').should == 'my val'
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context 'for a setting that does not have a value' do
|
173
|
+
it 'returns nil' do
|
174
|
+
config.setting('does not exist').should == nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
180
|
+
end
|
data/spec/logger_spec.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Papermill
|
4
|
+
describe 'the papermill logger' do
|
5
|
+
it 'inherits from the ruby std lib logger' do
|
6
|
+
::Papermill::Logger.superclass.should == ::Logger
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'instantiating the log file' do
|
11
|
+
context 'when the log directory does not exist' do
|
12
|
+
it 'should not raise a file system error' do
|
13
|
+
lambda {
|
14
|
+
Logger.new
|
15
|
+
}.should_not raise_error(Errno::ENOENT)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'creates the log file' do
|
19
|
+
Logger.new
|
20
|
+
File.exists?('log/papermill.log').should be_true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#log_error' do
|
26
|
+
let(:logger) { Logger.new($stdout) }
|
27
|
+
let(:exception) { mock('Exception', :message => 'msg', :backtrace => ['...']) }
|
28
|
+
|
29
|
+
it 'records the message and the backtrace of the exception' do
|
30
|
+
logger.should_receive(:error)
|
31
|
+
exception.should_receive(:message)
|
32
|
+
exception.should_receive(:backtrace)
|
33
|
+
|
34
|
+
logger.log_exception(exception)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/rack/collector_spec.rb
CHANGED
@@ -4,15 +4,18 @@ require 'sinatra'
|
|
4
4
|
|
5
5
|
module Papermill
|
6
6
|
|
7
|
-
describe 'collecting statistics
|
8
|
-
|
9
|
-
@app
|
7
|
+
describe 'collecting statistics with Collector' do
|
8
|
+
def app
|
9
|
+
@app ||= Sinatra.new Sinatra::Application do
|
10
10
|
get '/' do
|
11
11
|
'<html><body>Hello, world!</body></html>'
|
12
12
|
end
|
13
|
+
|
14
|
+
use Papermill::Collector
|
13
15
|
end
|
14
|
-
|
16
|
+
end
|
15
17
|
|
18
|
+
before do
|
16
19
|
request_headers = {'HTTP_USER_AGENT' => 'agent', 'REMOTE_ADDR' => '0.0.0.0',
|
17
20
|
'QUERY_STRING' => 'q=search',
|
18
21
|
'SCRIPT_NAME' => 'my-script',
|
@@ -20,11 +23,17 @@ module Papermill
|
|
20
23
|
'HTTP_HOST' => 'localhost',
|
21
24
|
'REQUEST_URI' => 'i-requested-this',
|
22
25
|
'REMOTE_HOST' => '123.345.34.2',
|
23
|
-
'SERVER_SOFTWARE' => 'apache'
|
26
|
+
'SERVER_SOFTWARE' => 'apache',
|
27
|
+
'HTTP_REFERER' => 'previous url'
|
24
28
|
}
|
25
29
|
|
26
30
|
Time.stub(:now => Time.utc(2010, 01, 01))
|
27
|
-
@status, @headers, @body =
|
31
|
+
@status, @headers, @body = app.call(Rack::MockRequest.env_for('/', request_headers))
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'for a 304 response' do
|
35
|
+
it 'records the status code'
|
36
|
+
it 'doesnt attempt to save any headers'
|
28
37
|
end
|
29
38
|
|
30
39
|
def last_store
|
@@ -42,7 +51,7 @@ module Papermill
|
|
42
51
|
it 'records request duration'
|
43
52
|
|
44
53
|
it 'records the request time' do
|
45
|
-
last_store[
|
54
|
+
last_store['request_time'].should == Time.utc(2010, 01, 01)
|
46
55
|
end
|
47
56
|
|
48
57
|
it 'records the path info' do
|
@@ -93,8 +102,16 @@ module Papermill
|
|
93
102
|
headers['REMOTE_HOST'].should == '123.345.34.2'
|
94
103
|
end
|
95
104
|
|
105
|
+
it 'records the http referrer' do
|
106
|
+
headers['HTTP_REFERER'].should == 'previous url'
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'does not save any keys with a period in the name' do
|
110
|
+
headers.keys.detect { |k| k =~ /\./ }.should == nil
|
111
|
+
end
|
112
|
+
|
96
113
|
it 'records the url scheme' do
|
97
|
-
headers['
|
114
|
+
headers['URL_SCHEME'].should == 'http'
|
98
115
|
end
|
99
116
|
|
100
117
|
context 'for a 304 response' do
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Papermill
|
4
|
+
describe 'initializing the Base parser' do
|
5
|
+
context 'with no environment' do
|
6
|
+
let(:base) { ResponseAdapters::Base.new(200, {}, lambda {}) }
|
7
|
+
|
8
|
+
it 'sets it to an empty hash' do
|
9
|
+
base.env.should == {}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -14,12 +14,14 @@ module Papermill
|
|
14
14
|
end
|
15
15
|
|
16
16
|
context 'in a rails app' do
|
17
|
-
|
17
|
+
before do
|
18
|
+
# self.class.module_eval { remove_const(:Sinatra) if defined?(Sinatra) }
|
18
19
|
end
|
19
20
|
|
20
21
|
it 'uses the rails adapter' do
|
21
|
-
|
22
|
-
|
22
|
+
class Rails
|
23
|
+
end
|
24
|
+
ResponseParser.parse_klass.should == ResponseAdapters::Rails
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -12,8 +12,18 @@ require 'papermill-agent'
|
|
12
12
|
require 'fakeweb'
|
13
13
|
|
14
14
|
FakeWeb.allow_net_connect = false
|
15
|
+
FakeWeb.register_uri(:post, Papermill::API_ENDPOINT, {})
|
15
16
|
|
16
17
|
RSpec.configure do |config|
|
18
|
+
def mock_config_hash
|
19
|
+
{'token' => 'mock-token'}
|
20
|
+
end
|
21
|
+
|
22
|
+
def agent
|
23
|
+
Papermill::Agent.instance
|
24
|
+
end
|
25
|
+
|
26
|
+
config.mock_with :rspec
|
17
27
|
config.before(:each) do
|
18
28
|
# clear the storage before each example
|
19
29
|
Papermill::Storage.clear
|
data/spec/storage_spec.rb
CHANGED
@@ -2,6 +2,22 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
module Papermill
|
4
4
|
|
5
|
+
describe 'determining if the storage has items in it' do
|
6
|
+
subject { Storage.empty? }
|
7
|
+
|
8
|
+
context 'when a request has been added to the storage' do
|
9
|
+
before { Storage.store << ['something worth saving'] }
|
10
|
+
|
11
|
+
it { should == false }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when no requests have been added to the storage' do
|
15
|
+
before { Storage.clear }
|
16
|
+
|
17
|
+
it { should == true }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
5
21
|
describe 'adding an item to the local data storage' do
|
6
22
|
it 'acts like an array' do
|
7
23
|
Storage.store.should respond_to(:<<)
|
@@ -19,6 +35,17 @@ module Papermill
|
|
19
35
|
end
|
20
36
|
end
|
21
37
|
|
38
|
+
describe 'determine the number of records stored' do
|
39
|
+
before do
|
40
|
+
Storage.store << 'record 1'
|
41
|
+
Storage.store << 'record 2'
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should be 2' do
|
45
|
+
Storage.size.should == 2
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
22
49
|
describe 'emptying the cache' do
|
23
50
|
before { Storage.clear }
|
24
51
|
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Alex Sharp
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date:
|
17
|
+
date: 2011-01-02 00:00:00 -06:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -44,6 +44,8 @@ extra_rdoc_files:
|
|
44
44
|
- doc/ideas.md
|
45
45
|
files:
|
46
46
|
- lib/papermill-agent/agent.rb
|
47
|
+
- lib/papermill-agent/configurator.rb
|
48
|
+
- lib/papermill-agent/logger.rb
|
47
49
|
- lib/papermill-agent/rack/collector.rb
|
48
50
|
- lib/papermill-agent/response_adapters/base.rb
|
49
51
|
- lib/papermill-agent/response_adapters/rails.rb
|
@@ -58,9 +60,12 @@ files:
|
|
58
60
|
- Rakefile
|
59
61
|
- doc/ideas.md
|
60
62
|
- spec/agent_spec.rb
|
63
|
+
- spec/configurator_spec.rb
|
61
64
|
- spec/integration/rails2_spec.rb
|
62
65
|
- spec/integration/rails3_spec.rb
|
66
|
+
- spec/logger_spec.rb
|
63
67
|
- spec/rack/collector_spec.rb
|
68
|
+
- spec/response_adapters/base_spec.rb
|
64
69
|
- spec/response_parser_spec.rb
|
65
70
|
- spec/spec_helper.rb
|
66
71
|
- spec/storage_spec.rb
|
@@ -98,9 +103,12 @@ specification_version: 3
|
|
98
103
|
summary: The client agent for papermillapp.com
|
99
104
|
test_files:
|
100
105
|
- spec/agent_spec.rb
|
106
|
+
- spec/configurator_spec.rb
|
101
107
|
- spec/integration/rails2_spec.rb
|
102
108
|
- spec/integration/rails3_spec.rb
|
109
|
+
- spec/logger_spec.rb
|
103
110
|
- spec/rack/collector_spec.rb
|
111
|
+
- spec/response_adapters/base_spec.rb
|
104
112
|
- spec/response_parser_spec.rb
|
105
113
|
- spec/spec_helper.rb
|
106
114
|
- spec/storage_spec.rb
|