papermill-agent 0.0.1 → 0.0.2
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/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
|